blob: ebf15cdc2f1b54c30c958691621b0ef604b95b73 [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// keir@google.com (Keir Mierle)
31
32#include "ceres/problem.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080033
34#include <memory>
Austin Schuh3de38b02024-06-25 18:25:10 -070035#include <string>
36#include <vector>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080037
38#include "ceres/autodiff_cost_function.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080039#include "ceres/casts.h"
40#include "ceres/cost_function.h"
41#include "ceres/crs_matrix.h"
42#include "ceres/evaluator_test_utils.h"
43#include "ceres/internal/eigen.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080044#include "ceres/loss_function.h"
45#include "ceres/map_util.h"
46#include "ceres/parameter_block.h"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080047#include "ceres/problem_impl.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080048#include "ceres/program.h"
49#include "ceres/sized_cost_function.h"
50#include "ceres/sparse_matrix.h"
51#include "ceres/types.h"
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080052#include "gmock/gmock.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080053#include "gtest/gtest.h"
54
Austin Schuh3de38b02024-06-25 18:25:10 -070055namespace ceres::internal {
Austin Schuh70cc9552019-01-21 19:46:48 -080056
57// The following three classes are for the purposes of defining
58// function signatures. They have dummy Evaluate functions.
59
60// Trivial cost function that accepts a single argument.
61class UnaryCostFunction : public CostFunction {
62 public:
63 UnaryCostFunction(int num_residuals, int32_t parameter_block_size) {
64 set_num_residuals(num_residuals);
65 mutable_parameter_block_sizes()->push_back(parameter_block_size);
66 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080067
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080068 bool Evaluate(double const* const* parameters,
69 double* residuals,
70 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080071 for (int i = 0; i < num_residuals(); ++i) {
72 residuals[i] = 1;
73 }
74 return true;
75 }
76};
77
78// Trivial cost function that accepts two arguments.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080079class BinaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -080080 public:
81 BinaryCostFunction(int num_residuals,
82 int32_t parameter_block1_size,
83 int32_t parameter_block2_size) {
84 set_num_residuals(num_residuals);
85 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
86 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
87 }
88
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080089 bool Evaluate(double const* const* parameters,
90 double* residuals,
91 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080092 for (int i = 0; i < num_residuals(); ++i) {
93 residuals[i] = 2;
94 }
95 return true;
96 }
97};
98
99// Trivial cost function that accepts three arguments.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800100class TernaryCostFunction : public CostFunction {
Austin Schuh70cc9552019-01-21 19:46:48 -0800101 public:
102 TernaryCostFunction(int num_residuals,
103 int32_t parameter_block1_size,
104 int32_t parameter_block2_size,
105 int32_t parameter_block3_size) {
106 set_num_residuals(num_residuals);
107 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
108 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
109 mutable_parameter_block_sizes()->push_back(parameter_block3_size);
110 }
111
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800112 bool Evaluate(double const* const* parameters,
113 double* residuals,
114 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800115 for (int i = 0; i < num_residuals(); ++i) {
116 residuals[i] = 3;
117 }
118 return true;
119 }
120};
121
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800122TEST(Problem, MoveConstructor) {
123 Problem src;
124 double x;
125 src.AddParameterBlock(&x, 1);
126 Problem dst(std::move(src));
127 EXPECT_TRUE(dst.HasParameterBlock(&x));
128}
129
130TEST(Problem, MoveAssignment) {
131 Problem src;
132 double x;
133 src.AddParameterBlock(&x, 1);
134 Problem dst;
135 dst = std::move(src);
136 EXPECT_TRUE(dst.HasParameterBlock(&x));
137}
138
Austin Schuh70cc9552019-01-21 19:46:48 -0800139TEST(Problem, AddResidualWithNullCostFunctionDies) {
140 double x[3], y[4], z[5];
141
142 Problem problem;
143 problem.AddParameterBlock(x, 3);
144 problem.AddParameterBlock(y, 4);
145 problem.AddParameterBlock(z, 5);
146
Austin Schuh3de38b02024-06-25 18:25:10 -0700147 EXPECT_DEATH_IF_SUPPORTED(problem.AddResidualBlock(nullptr, nullptr, x),
Austin Schuh70cc9552019-01-21 19:46:48 -0800148 "cost_function != nullptr");
149}
150
151TEST(Problem, AddResidualWithIncorrectNumberOfParameterBlocksDies) {
152 double x[3], y[4], z[5];
153
154 Problem problem;
155 problem.AddParameterBlock(x, 3);
156 problem.AddParameterBlock(y, 4);
157 problem.AddParameterBlock(z, 5);
158
159 // UnaryCostFunction takes only one parameter, but two are passed.
160 EXPECT_DEATH_IF_SUPPORTED(
Austin Schuh3de38b02024-06-25 18:25:10 -0700161 problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x, y),
Austin Schuh70cc9552019-01-21 19:46:48 -0800162 "num_parameter_blocks");
163}
164
165TEST(Problem, AddResidualWithDifferentSizesOnTheSameVariableDies) {
166 double x[3];
167
168 Problem problem;
Austin Schuh3de38b02024-06-25 18:25:10 -0700169 problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800170 EXPECT_DEATH_IF_SUPPORTED(
171 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700172 new UnaryCostFunction(2, 4 /* 4 != 3 */), nullptr, x),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800173 "different block sizes");
Austin Schuh70cc9552019-01-21 19:46:48 -0800174}
175
176TEST(Problem, AddResidualWithDuplicateParametersDies) {
177 double x[3], z[5];
178
179 Problem problem;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800180 EXPECT_DEATH_IF_SUPPORTED(
Austin Schuh3de38b02024-06-25 18:25:10 -0700181 problem.AddResidualBlock(new BinaryCostFunction(2, 3, 3), nullptr, x, x),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800182 "Duplicate parameter blocks");
183 EXPECT_DEATH_IF_SUPPORTED(
184 problem.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -0700185 new TernaryCostFunction(1, 5, 3, 5), nullptr, z, x, z),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800186 "Duplicate parameter blocks");
Austin Schuh70cc9552019-01-21 19:46:48 -0800187}
188
189TEST(Problem, AddResidualWithIncorrectSizesOfParameterBlockDies) {
190 double x[3], y[4], z[5];
191
192 Problem problem;
193 problem.AddParameterBlock(x, 3);
194 problem.AddParameterBlock(y, 4);
195 problem.AddParameterBlock(z, 5);
196
197 // The cost function expects the size of the second parameter, z, to be 4
198 // instead of 5 as declared above. This is fatal.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800199 EXPECT_DEATH_IF_SUPPORTED(
Austin Schuh3de38b02024-06-25 18:25:10 -0700200 problem.AddResidualBlock(new BinaryCostFunction(2, 3, 4), nullptr, x, z),
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800201 "different block sizes");
Austin Schuh70cc9552019-01-21 19:46:48 -0800202}
203
204TEST(Problem, AddResidualAddsDuplicatedParametersOnlyOnce) {
205 double x[3], y[4], z[5];
206
207 Problem problem;
Austin Schuh3de38b02024-06-25 18:25:10 -0700208 problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
209 problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
210 problem.AddResidualBlock(new UnaryCostFunction(2, 4), nullptr, y);
211 problem.AddResidualBlock(new UnaryCostFunction(2, 5), nullptr, z);
Austin Schuh70cc9552019-01-21 19:46:48 -0800212
213 EXPECT_EQ(3, problem.NumParameterBlocks());
214 EXPECT_EQ(12, problem.NumParameters());
215}
216
217TEST(Problem, AddParameterWithDifferentSizesOnTheSameVariableDies) {
218 double x[3], y[4];
219
220 Problem problem;
221 problem.AddParameterBlock(x, 3);
222 problem.AddParameterBlock(y, 4);
223
224 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(x, 4),
225 "different block sizes");
226}
227
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800228static double* IntToPtr(int i) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800229 return reinterpret_cast<double*>(sizeof(double) * i); // NOLINT
230}
231
232TEST(Problem, AddParameterWithAliasedParametersDies) {
233 // Layout is
234 //
235 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
236 // [x] x x x x [y] y y
237 // o==o==o o==o==o o==o
238 // o--o--o o--o--o o--o o--o--o
239 //
240 // Parameter block additions are tested as listed above; expected successful
241 // ones marked with o==o and aliasing ones marked with o--o.
242
243 Problem problem;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800244 problem.AddParameterBlock(IntToPtr(5), 5); // x
Austin Schuh70cc9552019-01-21 19:46:48 -0800245 problem.AddParameterBlock(IntToPtr(13), 3); // y
246
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800247 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 2),
Austin Schuh70cc9552019-01-21 19:46:48 -0800248 "Aliasing detected");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800249 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 3),
Austin Schuh70cc9552019-01-21 19:46:48 -0800250 "Aliasing detected");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800251 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(4), 9),
Austin Schuh70cc9552019-01-21 19:46:48 -0800252 "Aliasing detected");
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800253 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(8), 3),
Austin Schuh70cc9552019-01-21 19:46:48 -0800254 "Aliasing detected");
255 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(12), 2),
256 "Aliasing detected");
257 EXPECT_DEATH_IF_SUPPORTED(problem.AddParameterBlock(IntToPtr(14), 3),
258 "Aliasing detected");
259
260 // These ones should work.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800261 problem.AddParameterBlock(IntToPtr(2), 3);
Austin Schuh70cc9552019-01-21 19:46:48 -0800262 problem.AddParameterBlock(IntToPtr(10), 3);
263 problem.AddParameterBlock(IntToPtr(16), 2);
264
265 ASSERT_EQ(5, problem.NumParameterBlocks());
266}
267
268TEST(Problem, AddParameterIgnoresDuplicateCalls) {
269 double x[3], y[4];
270
271 Problem problem;
272 problem.AddParameterBlock(x, 3);
273 problem.AddParameterBlock(y, 4);
274
275 // Creating parameter blocks multiple times is ignored.
276 problem.AddParameterBlock(x, 3);
Austin Schuh3de38b02024-06-25 18:25:10 -0700277 problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800278
279 // ... even repeatedly.
280 problem.AddParameterBlock(x, 3);
Austin Schuh3de38b02024-06-25 18:25:10 -0700281 problem.AddResidualBlock(new UnaryCostFunction(2, 3), nullptr, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800282
283 // More parameters are fine.
284 problem.AddParameterBlock(y, 4);
Austin Schuh3de38b02024-06-25 18:25:10 -0700285 problem.AddResidualBlock(new UnaryCostFunction(2, 4), nullptr, y);
Austin Schuh70cc9552019-01-21 19:46:48 -0800286
287 EXPECT_EQ(2, problem.NumParameterBlocks());
288 EXPECT_EQ(7, problem.NumParameters());
289}
290
Austin Schuh70cc9552019-01-21 19:46:48 -0800291class DestructorCountingCostFunction : public SizedCostFunction<3, 4, 5> {
292 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800293 explicit DestructorCountingCostFunction(int* num_destructions)
Austin Schuh70cc9552019-01-21 19:46:48 -0800294 : num_destructions_(num_destructions) {}
295
Austin Schuh3de38b02024-06-25 18:25:10 -0700296 ~DestructorCountingCostFunction() override { *num_destructions_ += 1; }
Austin Schuh70cc9552019-01-21 19:46:48 -0800297
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800298 bool Evaluate(double const* const* parameters,
299 double* residuals,
300 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -0800301 return true;
302 }
303
304 private:
305 int* num_destructions_;
306};
307
308TEST(Problem, ReusedCostFunctionsAreOnlyDeletedOnce) {
309 double y[4], z[5];
310 int num_destructions = 0;
311
312 // Add a cost function multiple times and check to make sure that
313 // the destructor on the cost function is only called once.
314 {
315 Problem problem;
316 problem.AddParameterBlock(y, 4);
317 problem.AddParameterBlock(z, 5);
318
319 CostFunction* cost = new DestructorCountingCostFunction(&num_destructions);
Austin Schuh3de38b02024-06-25 18:25:10 -0700320 problem.AddResidualBlock(cost, nullptr, y, z);
321 problem.AddResidualBlock(cost, nullptr, y, z);
322 problem.AddResidualBlock(cost, nullptr, y, z);
Austin Schuh70cc9552019-01-21 19:46:48 -0800323 EXPECT_EQ(3, problem.NumResidualBlocks());
324 }
325
326 // Check that the destructor was called only once.
327 CHECK_EQ(num_destructions, 1);
328}
329
330TEST(Problem, GetCostFunctionForResidualBlock) {
331 double x[3];
332 Problem problem;
333 CostFunction* cost_function = new UnaryCostFunction(2, 3);
334 const ResidualBlockId residual_block =
Austin Schuh3de38b02024-06-25 18:25:10 -0700335 problem.AddResidualBlock(cost_function, nullptr, x);
Austin Schuh70cc9552019-01-21 19:46:48 -0800336 EXPECT_EQ(problem.GetCostFunctionForResidualBlock(residual_block),
337 cost_function);
Austin Schuh3de38b02024-06-25 18:25:10 -0700338 EXPECT_TRUE(problem.GetLossFunctionForResidualBlock(residual_block) ==
339 nullptr);
Austin Schuh70cc9552019-01-21 19:46:48 -0800340}
341
342TEST(Problem, GetLossFunctionForResidualBlock) {
343 double x[3];
344 Problem problem;
345 CostFunction* cost_function = new UnaryCostFunction(2, 3);
346 LossFunction* loss_function = new TrivialLoss();
347 const ResidualBlockId residual_block =
348 problem.AddResidualBlock(cost_function, loss_function, x);
349 EXPECT_EQ(problem.GetCostFunctionForResidualBlock(residual_block),
350 cost_function);
351 EXPECT_EQ(problem.GetLossFunctionForResidualBlock(residual_block),
352 loss_function);
353}
354
355TEST(Problem, CostFunctionsAreDeletedEvenWithRemovals) {
356 double y[4], z[5], w[4];
357 int num_destructions = 0;
358 {
359 Problem problem;
360 problem.AddParameterBlock(y, 4);
361 problem.AddParameterBlock(z, 5);
362
363 CostFunction* cost_yz =
364 new DestructorCountingCostFunction(&num_destructions);
365 CostFunction* cost_wz =
366 new DestructorCountingCostFunction(&num_destructions);
Austin Schuh3de38b02024-06-25 18:25:10 -0700367 ResidualBlock* r_yz = problem.AddResidualBlock(cost_yz, nullptr, y, z);
368 ResidualBlock* r_wz = problem.AddResidualBlock(cost_wz, nullptr, w, z);
Austin Schuh70cc9552019-01-21 19:46:48 -0800369 EXPECT_EQ(2, problem.NumResidualBlocks());
370
371 problem.RemoveResidualBlock(r_yz);
372 CHECK_EQ(num_destructions, 1);
373 problem.RemoveResidualBlock(r_wz);
374 CHECK_EQ(num_destructions, 2);
375
376 EXPECT_EQ(0, problem.NumResidualBlocks());
377 }
378 CHECK_EQ(num_destructions, 2);
379}
380
381// Make the dynamic problem tests (e.g. for removing residual blocks)
382// parameterized on whether the low-latency mode is enabled or not.
383//
384// This tests against ProblemImpl instead of Problem in order to inspect the
385// state of the resulting Program; this is difficult with only the thin Problem
386// interface.
387struct DynamicProblem : public ::testing::TestWithParam<bool> {
388 DynamicProblem() {
389 Problem::Options options;
390 options.enable_fast_removal = GetParam();
Austin Schuh3de38b02024-06-25 18:25:10 -0700391 problem = std::make_unique<ProblemImpl>(options);
Austin Schuh70cc9552019-01-21 19:46:48 -0800392 }
393
394 ParameterBlock* GetParameterBlock(int block) {
395 return problem->program().parameter_blocks()[block];
396 }
397 ResidualBlock* GetResidualBlock(int block) {
398 return problem->program().residual_blocks()[block];
399 }
400
401 bool HasResidualBlock(ResidualBlock* residual_block) {
402 bool have_residual_block = true;
403 if (GetParam()) {
404 have_residual_block &=
405 (problem->residual_block_set().find(residual_block) !=
406 problem->residual_block_set().end());
407 }
408 have_residual_block &=
409 find(problem->program().residual_blocks().begin(),
410 problem->program().residual_blocks().end(),
411 residual_block) != problem->program().residual_blocks().end();
412 return have_residual_block;
413 }
414
415 int NumResidualBlocks() {
416 // Verify that the hash set of residuals is maintained consistently.
417 if (GetParam()) {
418 EXPECT_EQ(problem->residual_block_set().size(),
419 problem->NumResidualBlocks());
420 }
421 return problem->NumResidualBlocks();
422 }
423
424 // The next block of functions until the end are only for testing the
425 // residual block removals.
426 void ExpectParameterBlockContainsResidualBlock(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800427 double* values, ResidualBlock* residual_block) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800428 ParameterBlock* parameter_block =
429 FindOrDie(problem->parameter_map(), values);
430 EXPECT_TRUE(ContainsKey(*(parameter_block->mutable_residual_blocks()),
431 residual_block));
432 }
433
434 void ExpectSize(double* values, int size) {
435 ParameterBlock* parameter_block =
436 FindOrDie(problem->parameter_map(), values);
437 EXPECT_EQ(size, parameter_block->mutable_residual_blocks()->size());
438 }
439
440 // Degenerate case.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800441 void ExpectParameterBlockContains(double* values) { ExpectSize(values, 0); }
Austin Schuh70cc9552019-01-21 19:46:48 -0800442
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800443 void ExpectParameterBlockContains(double* values, ResidualBlock* r1) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800444 ExpectSize(values, 1);
445 ExpectParameterBlockContainsResidualBlock(values, r1);
446 }
447
448 void ExpectParameterBlockContains(double* values,
449 ResidualBlock* r1,
450 ResidualBlock* r2) {
451 ExpectSize(values, 2);
452 ExpectParameterBlockContainsResidualBlock(values, r1);
453 ExpectParameterBlockContainsResidualBlock(values, r2);
454 }
455
456 void ExpectParameterBlockContains(double* values,
457 ResidualBlock* r1,
458 ResidualBlock* r2,
459 ResidualBlock* r3) {
460 ExpectSize(values, 3);
461 ExpectParameterBlockContainsResidualBlock(values, r1);
462 ExpectParameterBlockContainsResidualBlock(values, r2);
463 ExpectParameterBlockContainsResidualBlock(values, r3);
464 }
465
466 void ExpectParameterBlockContains(double* values,
467 ResidualBlock* r1,
468 ResidualBlock* r2,
469 ResidualBlock* r3,
470 ResidualBlock* r4) {
471 ExpectSize(values, 4);
472 ExpectParameterBlockContainsResidualBlock(values, r1);
473 ExpectParameterBlockContainsResidualBlock(values, r2);
474 ExpectParameterBlockContainsResidualBlock(values, r3);
475 ExpectParameterBlockContainsResidualBlock(values, r4);
476 }
477
478 std::unique_ptr<ProblemImpl> problem;
479 double y[4], z[5], w[3];
480};
481
482TEST(Problem, SetParameterBlockConstantWithUnknownPtrDies) {
483 double x[3];
484 double y[2];
485
486 Problem problem;
487 problem.AddParameterBlock(x, 3);
488
489 EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockConstant(y),
490 "Parameter block not found:");
491}
492
493TEST(Problem, SetParameterBlockVariableWithUnknownPtrDies) {
494 double x[3];
495 double y[2];
496
497 Problem problem;
498 problem.AddParameterBlock(x, 3);
499
500 EXPECT_DEATH_IF_SUPPORTED(problem.SetParameterBlockVariable(y),
501 "Parameter block not found:");
502}
503
504TEST(Problem, IsParameterBlockConstant) {
505 double x1[3];
506 double x2[3];
507
508 Problem problem;
509 problem.AddParameterBlock(x1, 3);
510 problem.AddParameterBlock(x2, 3);
511
512 EXPECT_FALSE(problem.IsParameterBlockConstant(x1));
513 EXPECT_FALSE(problem.IsParameterBlockConstant(x2));
514
515 problem.SetParameterBlockConstant(x1);
516 EXPECT_TRUE(problem.IsParameterBlockConstant(x1));
517 EXPECT_FALSE(problem.IsParameterBlockConstant(x2));
518
519 problem.SetParameterBlockConstant(x2);
520 EXPECT_TRUE(problem.IsParameterBlockConstant(x1));
521 EXPECT_TRUE(problem.IsParameterBlockConstant(x2));
522
523 problem.SetParameterBlockVariable(x1);
524 EXPECT_FALSE(problem.IsParameterBlockConstant(x1));
525 EXPECT_TRUE(problem.IsParameterBlockConstant(x2));
526}
527
528TEST(Problem, IsParameterBlockConstantWithUnknownPtrDies) {
529 double x[3];
530 double y[2];
531
532 Problem problem;
533 problem.AddParameterBlock(x, 3);
534
535 EXPECT_DEATH_IF_SUPPORTED(problem.IsParameterBlockConstant(y),
536 "Parameter block not found:");
537}
538
Austin Schuh3de38b02024-06-25 18:25:10 -0700539TEST(Problem, SetManifoldWithUnknownPtrDies) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800540 double x[3];
541 double y[2];
542
543 Problem problem;
544 problem.AddParameterBlock(x, 3);
545
Austin Schuh3de38b02024-06-25 18:25:10 -0700546 EXPECT_DEATH_IF_SUPPORTED(problem.SetManifold(y, new EuclideanManifold<3>),
547 "Parameter block not found:");
Austin Schuh70cc9552019-01-21 19:46:48 -0800548}
549
550TEST(Problem, RemoveParameterBlockWithUnknownPtrDies) {
551 double x[3];
552 double y[2];
553
554 Problem problem;
555 problem.AddParameterBlock(x, 3);
556
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800557 EXPECT_DEATH_IF_SUPPORTED(problem.RemoveParameterBlock(y),
558 "Parameter block not found:");
Austin Schuh70cc9552019-01-21 19:46:48 -0800559}
560
Austin Schuh3de38b02024-06-25 18:25:10 -0700561TEST(Problem, GetManifold) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800562 double x[3];
563 double y[2];
564
565 Problem problem;
566 problem.AddParameterBlock(x, 3);
567 problem.AddParameterBlock(y, 2);
568
Austin Schuh3de38b02024-06-25 18:25:10 -0700569 Manifold* manifold = new EuclideanManifold<3>;
570 problem.SetManifold(x, manifold);
571 EXPECT_EQ(problem.GetManifold(x), manifold);
572 EXPECT_TRUE(problem.GetManifold(y) == nullptr);
573}
574
575TEST(Problem, HasManifold) {
576 double x[3];
577 double y[2];
578
579 Problem problem;
580 problem.AddParameterBlock(x, 3);
581 problem.AddParameterBlock(y, 2);
582
583 Manifold* manifold = new EuclideanManifold<3>;
584 problem.SetManifold(x, manifold);
585 EXPECT_TRUE(problem.HasManifold(x));
586 EXPECT_FALSE(problem.HasManifold(y));
587}
588
589TEST(Problem, RepeatedAddParameterBlockResetsManifold) {
590 double x[4];
591 double y[2];
592
593 Problem problem;
594 problem.AddParameterBlock(x, 4, new SubsetManifold(4, {0, 1}));
595 problem.AddParameterBlock(y, 2);
596
597 EXPECT_FALSE(problem.HasManifold(y));
598
599 EXPECT_TRUE(problem.HasManifold(x));
600 EXPECT_EQ(problem.ParameterBlockSize(x), 4);
601 EXPECT_EQ(problem.ParameterBlockTangentSize(x), 2);
602 EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 4);
603 EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 2);
604
605 problem.AddParameterBlock(x, 4, static_cast<Manifold*>(nullptr));
606 EXPECT_FALSE(problem.HasManifold(x));
607 EXPECT_EQ(problem.ParameterBlockSize(x), 4);
608 EXPECT_EQ(problem.ParameterBlockTangentSize(x), 4);
609 EXPECT_EQ(problem.GetManifold(x), nullptr);
610
611 problem.AddParameterBlock(x, 4, new SubsetManifold(4, {0, 1, 2}));
612 problem.AddParameterBlock(y, 2);
613 EXPECT_TRUE(problem.HasManifold(x));
614 EXPECT_EQ(problem.ParameterBlockSize(x), 4);
615 EXPECT_EQ(problem.ParameterBlockTangentSize(x), 1);
616 EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 4);
617 EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 1);
618}
619
620TEST(Problem, ParameterBlockQueryTestUsingManifold) {
621 double x[3];
622 double y[4];
623 Problem problem;
624 problem.AddParameterBlock(x, 3);
625 problem.AddParameterBlock(y, 4);
626
627 std::vector<int> constant_parameters;
628 constant_parameters.push_back(0);
629 problem.SetManifold(x, new SubsetManifold(3, constant_parameters));
630 EXPECT_EQ(problem.ParameterBlockSize(x), 3);
631 EXPECT_EQ(problem.ParameterBlockTangentSize(x), 2);
632 EXPECT_EQ(problem.ParameterBlockTangentSize(y), 4);
633
634 std::vector<double*> parameter_blocks;
635 problem.GetParameterBlocks(&parameter_blocks);
636 EXPECT_EQ(parameter_blocks.size(), 2);
637 EXPECT_NE(parameter_blocks[0], parameter_blocks[1]);
638 EXPECT_TRUE(parameter_blocks[0] == x || parameter_blocks[0] == y);
639 EXPECT_TRUE(parameter_blocks[1] == x || parameter_blocks[1] == y);
640
641 EXPECT_TRUE(problem.HasParameterBlock(x));
642 problem.RemoveParameterBlock(x);
643 EXPECT_FALSE(problem.HasParameterBlock(x));
644 problem.GetParameterBlocks(&parameter_blocks);
645 EXPECT_EQ(parameter_blocks.size(), 1);
646 EXPECT_TRUE(parameter_blocks[0] == y);
Austin Schuh70cc9552019-01-21 19:46:48 -0800647}
648
649TEST(Problem, ParameterBlockQueryTest) {
650 double x[3];
651 double y[4];
652 Problem problem;
653 problem.AddParameterBlock(x, 3);
654 problem.AddParameterBlock(y, 4);
655
Austin Schuh3de38b02024-06-25 18:25:10 -0700656 std::vector<int> constant_parameters;
Austin Schuh70cc9552019-01-21 19:46:48 -0800657 constant_parameters.push_back(0);
Austin Schuh3de38b02024-06-25 18:25:10 -0700658 problem.SetManifold(x, new SubsetManifold(3, constant_parameters));
Austin Schuh70cc9552019-01-21 19:46:48 -0800659 EXPECT_EQ(problem.ParameterBlockSize(x), 3);
Austin Schuh3de38b02024-06-25 18:25:10 -0700660 EXPECT_EQ(problem.ParameterBlockTangentSize(x), 2);
661 EXPECT_EQ(problem.ParameterBlockTangentSize(y), 4);
Austin Schuh70cc9552019-01-21 19:46:48 -0800662
Austin Schuh3de38b02024-06-25 18:25:10 -0700663 std::vector<double*> parameter_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -0800664 problem.GetParameterBlocks(&parameter_blocks);
665 EXPECT_EQ(parameter_blocks.size(), 2);
666 EXPECT_NE(parameter_blocks[0], parameter_blocks[1]);
667 EXPECT_TRUE(parameter_blocks[0] == x || parameter_blocks[0] == y);
668 EXPECT_TRUE(parameter_blocks[1] == x || parameter_blocks[1] == y);
669
670 EXPECT_TRUE(problem.HasParameterBlock(x));
671 problem.RemoveParameterBlock(x);
672 EXPECT_FALSE(problem.HasParameterBlock(x));
673 problem.GetParameterBlocks(&parameter_blocks);
674 EXPECT_EQ(parameter_blocks.size(), 1);
675 EXPECT_TRUE(parameter_blocks[0] == y);
676}
677
678TEST_P(DynamicProblem, RemoveParameterBlockWithNoResiduals) {
679 problem->AddParameterBlock(y, 4);
680 problem->AddParameterBlock(z, 5);
681 problem->AddParameterBlock(w, 3);
682 ASSERT_EQ(3, problem->NumParameterBlocks());
683 ASSERT_EQ(0, NumResidualBlocks());
684 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
685 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
686 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
687
688 // w is at the end, which might break the swapping logic so try adding and
689 // removing it.
690 problem->RemoveParameterBlock(w);
691 ASSERT_EQ(2, problem->NumParameterBlocks());
692 ASSERT_EQ(0, NumResidualBlocks());
693 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
694 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
695 problem->AddParameterBlock(w, 3);
696 ASSERT_EQ(3, problem->NumParameterBlocks());
697 ASSERT_EQ(0, NumResidualBlocks());
698 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
699 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
700 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
701
702 // Now remove z, which is in the middle, and add it back.
703 problem->RemoveParameterBlock(z);
704 ASSERT_EQ(2, problem->NumParameterBlocks());
705 ASSERT_EQ(0, NumResidualBlocks());
706 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
707 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
708 problem->AddParameterBlock(z, 5);
709 ASSERT_EQ(3, problem->NumParameterBlocks());
710 ASSERT_EQ(0, NumResidualBlocks());
711 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
712 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
713 EXPECT_EQ(z, GetParameterBlock(2)->user_state());
714
715 // Now remove everything.
716 // y
717 problem->RemoveParameterBlock(y);
718 ASSERT_EQ(2, problem->NumParameterBlocks());
719 ASSERT_EQ(0, NumResidualBlocks());
720 EXPECT_EQ(z, GetParameterBlock(0)->user_state());
721 EXPECT_EQ(w, GetParameterBlock(1)->user_state());
722
723 // z
724 problem->RemoveParameterBlock(z);
725 ASSERT_EQ(1, problem->NumParameterBlocks());
726 ASSERT_EQ(0, NumResidualBlocks());
727 EXPECT_EQ(w, GetParameterBlock(0)->user_state());
728
729 // w
730 problem->RemoveParameterBlock(w);
731 EXPECT_EQ(0, problem->NumParameterBlocks());
732 EXPECT_EQ(0, NumResidualBlocks());
733}
734
735TEST_P(DynamicProblem, RemoveParameterBlockWithResiduals) {
736 problem->AddParameterBlock(y, 4);
737 problem->AddParameterBlock(z, 5);
738 problem->AddParameterBlock(w, 3);
739 ASSERT_EQ(3, problem->NumParameterBlocks());
740 ASSERT_EQ(0, NumResidualBlocks());
741 EXPECT_EQ(y, GetParameterBlock(0)->user_state());
742 EXPECT_EQ(z, GetParameterBlock(1)->user_state());
743 EXPECT_EQ(w, GetParameterBlock(2)->user_state());
744
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800745 // clang-format off
746
Austin Schuh70cc9552019-01-21 19:46:48 -0800747 // Add all combinations of cost functions.
748 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
749 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
750 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
751 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
752 CostFunction* cost_y = new UnaryCostFunction (1, 4);
753 CostFunction* cost_z = new UnaryCostFunction (1, 5);
754 CostFunction* cost_w = new UnaryCostFunction (1, 3);
755
Austin Schuh3de38b02024-06-25 18:25:10 -0700756 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
757 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
758 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
759 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
760 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
761 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
762 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
Austin Schuh70cc9552019-01-21 19:46:48 -0800763
764 EXPECT_EQ(3, problem->NumParameterBlocks());
765 EXPECT_EQ(7, NumResidualBlocks());
766
767 // Remove w, which should remove r_yzw, r_yw, r_zw, r_w.
768 problem->RemoveParameterBlock(w);
769 ASSERT_EQ(2, problem->NumParameterBlocks());
770 ASSERT_EQ(3, NumResidualBlocks());
771
772 ASSERT_FALSE(HasResidualBlock(r_yzw));
773 ASSERT_TRUE (HasResidualBlock(r_yz ));
774 ASSERT_FALSE(HasResidualBlock(r_yw ));
775 ASSERT_FALSE(HasResidualBlock(r_zw ));
776 ASSERT_TRUE (HasResidualBlock(r_y ));
777 ASSERT_TRUE (HasResidualBlock(r_z ));
778 ASSERT_FALSE(HasResidualBlock(r_w ));
779
780 // Remove z, which will remove almost everything else.
781 problem->RemoveParameterBlock(z);
782 ASSERT_EQ(1, problem->NumParameterBlocks());
783 ASSERT_EQ(1, NumResidualBlocks());
784
785 ASSERT_FALSE(HasResidualBlock(r_yzw));
786 ASSERT_FALSE(HasResidualBlock(r_yz ));
787 ASSERT_FALSE(HasResidualBlock(r_yw ));
788 ASSERT_FALSE(HasResidualBlock(r_zw ));
789 ASSERT_TRUE (HasResidualBlock(r_y ));
790 ASSERT_FALSE(HasResidualBlock(r_z ));
791 ASSERT_FALSE(HasResidualBlock(r_w ));
792
793 // Remove y; all gone.
794 problem->RemoveParameterBlock(y);
795 EXPECT_EQ(0, problem->NumParameterBlocks());
796 EXPECT_EQ(0, NumResidualBlocks());
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800797
798 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800799}
800
801TEST_P(DynamicProblem, RemoveResidualBlock) {
802 problem->AddParameterBlock(y, 4);
803 problem->AddParameterBlock(z, 5);
804 problem->AddParameterBlock(w, 3);
805
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800806 // clang-format off
807
Austin Schuh70cc9552019-01-21 19:46:48 -0800808 // Add all combinations of cost functions.
809 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
810 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
811 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
812 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
813 CostFunction* cost_y = new UnaryCostFunction (1, 4);
814 CostFunction* cost_z = new UnaryCostFunction (1, 5);
815 CostFunction* cost_w = new UnaryCostFunction (1, 3);
816
Austin Schuh3de38b02024-06-25 18:25:10 -0700817 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
818 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
819 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
820 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
821 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
822 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
823 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
Austin Schuh70cc9552019-01-21 19:46:48 -0800824
825 if (GetParam()) {
826 // In this test parameterization, there should be back-pointers from the
827 // parameter blocks to the residual blocks.
828 ExpectParameterBlockContains(y, r_yzw, r_yz, r_yw, r_y);
829 ExpectParameterBlockContains(z, r_yzw, r_yz, r_zw, r_z);
830 ExpectParameterBlockContains(w, r_yzw, r_yw, r_zw, r_w);
831 } else {
832 // Otherwise, nothing.
Austin Schuh3de38b02024-06-25 18:25:10 -0700833 EXPECT_TRUE(GetParameterBlock(0)->mutable_residual_blocks() == nullptr);
834 EXPECT_TRUE(GetParameterBlock(1)->mutable_residual_blocks() == nullptr);
835 EXPECT_TRUE(GetParameterBlock(2)->mutable_residual_blocks() == nullptr);
Austin Schuh70cc9552019-01-21 19:46:48 -0800836 }
837 EXPECT_EQ(3, problem->NumParameterBlocks());
838 EXPECT_EQ(7, NumResidualBlocks());
839
840 // Remove each residual and check the state after each removal.
841
842 // Remove r_yzw.
843 problem->RemoveResidualBlock(r_yzw);
844 ASSERT_EQ(3, problem->NumParameterBlocks());
845 ASSERT_EQ(6, NumResidualBlocks());
846 if (GetParam()) {
847 ExpectParameterBlockContains(y, r_yz, r_yw, r_y);
848 ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
849 ExpectParameterBlockContains(w, r_yw, r_zw, r_w);
850 }
851 ASSERT_TRUE (HasResidualBlock(r_yz ));
852 ASSERT_TRUE (HasResidualBlock(r_yw ));
853 ASSERT_TRUE (HasResidualBlock(r_zw ));
854 ASSERT_TRUE (HasResidualBlock(r_y ));
855 ASSERT_TRUE (HasResidualBlock(r_z ));
856 ASSERT_TRUE (HasResidualBlock(r_w ));
857
858 // Remove r_yw.
859 problem->RemoveResidualBlock(r_yw);
860 ASSERT_EQ(3, problem->NumParameterBlocks());
861 ASSERT_EQ(5, NumResidualBlocks());
862 if (GetParam()) {
863 ExpectParameterBlockContains(y, r_yz, r_y);
864 ExpectParameterBlockContains(z, r_yz, r_zw, r_z);
865 ExpectParameterBlockContains(w, r_zw, r_w);
866 }
867 ASSERT_TRUE (HasResidualBlock(r_yz ));
868 ASSERT_TRUE (HasResidualBlock(r_zw ));
869 ASSERT_TRUE (HasResidualBlock(r_y ));
870 ASSERT_TRUE (HasResidualBlock(r_z ));
871 ASSERT_TRUE (HasResidualBlock(r_w ));
872
873 // Remove r_zw.
874 problem->RemoveResidualBlock(r_zw);
875 ASSERT_EQ(3, problem->NumParameterBlocks());
876 ASSERT_EQ(4, NumResidualBlocks());
877 if (GetParam()) {
878 ExpectParameterBlockContains(y, r_yz, r_y);
879 ExpectParameterBlockContains(z, r_yz, r_z);
880 ExpectParameterBlockContains(w, r_w);
881 }
882 ASSERT_TRUE (HasResidualBlock(r_yz ));
883 ASSERT_TRUE (HasResidualBlock(r_y ));
884 ASSERT_TRUE (HasResidualBlock(r_z ));
885 ASSERT_TRUE (HasResidualBlock(r_w ));
886
887 // Remove r_w.
888 problem->RemoveResidualBlock(r_w);
889 ASSERT_EQ(3, problem->NumParameterBlocks());
890 ASSERT_EQ(3, NumResidualBlocks());
891 if (GetParam()) {
892 ExpectParameterBlockContains(y, r_yz, r_y);
893 ExpectParameterBlockContains(z, r_yz, r_z);
894 ExpectParameterBlockContains(w);
895 }
896 ASSERT_TRUE (HasResidualBlock(r_yz ));
897 ASSERT_TRUE (HasResidualBlock(r_y ));
898 ASSERT_TRUE (HasResidualBlock(r_z ));
899
900 // Remove r_yz.
901 problem->RemoveResidualBlock(r_yz);
902 ASSERT_EQ(3, problem->NumParameterBlocks());
903 ASSERT_EQ(2, NumResidualBlocks());
904 if (GetParam()) {
905 ExpectParameterBlockContains(y, r_y);
906 ExpectParameterBlockContains(z, r_z);
907 ExpectParameterBlockContains(w);
908 }
909 ASSERT_TRUE (HasResidualBlock(r_y ));
910 ASSERT_TRUE (HasResidualBlock(r_z ));
911
912 // Remove the last two.
913 problem->RemoveResidualBlock(r_z);
914 problem->RemoveResidualBlock(r_y);
915 ASSERT_EQ(3, problem->NumParameterBlocks());
916 ASSERT_EQ(0, NumResidualBlocks());
917 if (GetParam()) {
918 ExpectParameterBlockContains(y);
919 ExpectParameterBlockContains(z);
920 ExpectParameterBlockContains(w);
921 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800922
923 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -0800924}
925
926TEST_P(DynamicProblem, RemoveInvalidResidualBlockDies) {
927 problem->AddParameterBlock(y, 4);
928 problem->AddParameterBlock(z, 5);
929 problem->AddParameterBlock(w, 3);
930
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800931 // clang-format off
932
Austin Schuh70cc9552019-01-21 19:46:48 -0800933 // Add all combinations of cost functions.
934 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
935 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
936 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
937 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
938 CostFunction* cost_y = new UnaryCostFunction (1, 4);
939 CostFunction* cost_z = new UnaryCostFunction (1, 5);
940 CostFunction* cost_w = new UnaryCostFunction (1, 3);
941
Austin Schuh3de38b02024-06-25 18:25:10 -0700942 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
943 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
944 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
945 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
946 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
947 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
948 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
Austin Schuh70cc9552019-01-21 19:46:48 -0800949
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800950 // clang-format on
951
Austin Schuh70cc9552019-01-21 19:46:48 -0800952 // Remove r_yzw.
953 problem->RemoveResidualBlock(r_yzw);
954 ASSERT_EQ(3, problem->NumParameterBlocks());
955 ASSERT_EQ(6, NumResidualBlocks());
956 // Attempt to remove r_yzw again.
957 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yzw), "not found");
958
959 // Attempt to remove a cast pointer never added as a residual.
960 int trash_memory = 1234;
Austin Schuh3de38b02024-06-25 18:25:10 -0700961 auto* invalid_residual = reinterpret_cast<ResidualBlock*>(&trash_memory);
Austin Schuh70cc9552019-01-21 19:46:48 -0800962 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(invalid_residual),
963 "not found");
964
965 // Remove a parameter block, which in turn removes the dependent residuals
966 // then attempt to remove them directly.
967 problem->RemoveParameterBlock(z);
968 ASSERT_EQ(2, problem->NumParameterBlocks());
969 ASSERT_EQ(3, NumResidualBlocks());
970 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_yz), "not found");
971 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_zw), "not found");
972 EXPECT_DEATH_IF_SUPPORTED(problem->RemoveResidualBlock(r_z), "not found");
973
974 problem->RemoveResidualBlock(r_yw);
975 problem->RemoveResidualBlock(r_w);
976 problem->RemoveResidualBlock(r_y);
977}
978
979// Check that a null-terminated array, a, has the same elements as b.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800980template <typename T>
Austin Schuh3de38b02024-06-25 18:25:10 -0700981void ExpectVectorContainsUnordered(const T* a, const std::vector<T>& b) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800982 // Compute the size of a.
983 int size = 0;
984 while (a[size]) {
985 ++size;
986 }
987 ASSERT_EQ(size, b.size());
988
989 // Sort a.
Austin Schuh3de38b02024-06-25 18:25:10 -0700990 std::vector<T> a_sorted(size);
Austin Schuh70cc9552019-01-21 19:46:48 -0800991 copy(a, a + size, a_sorted.begin());
992 sort(a_sorted.begin(), a_sorted.end());
993
994 // Sort b.
Austin Schuh3de38b02024-06-25 18:25:10 -0700995 std::vector<T> b_sorted(b);
Austin Schuh70cc9552019-01-21 19:46:48 -0800996 sort(b_sorted.begin(), b_sorted.end());
997
998 // Compare.
999 for (int i = 0; i < size; ++i) {
1000 EXPECT_EQ(a_sorted[i], b_sorted[i]);
1001 }
1002}
1003
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001004static void ExpectProblemHasResidualBlocks(
1005 const ProblemImpl& problem,
1006 const ResidualBlockId* expected_residual_blocks) {
Austin Schuh3de38b02024-06-25 18:25:10 -07001007 std::vector<ResidualBlockId> residual_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -08001008 problem.GetResidualBlocks(&residual_blocks);
1009 ExpectVectorContainsUnordered(expected_residual_blocks, residual_blocks);
1010}
1011
1012TEST_P(DynamicProblem, GetXXXBlocksForYYYBlock) {
1013 problem->AddParameterBlock(y, 4);
1014 problem->AddParameterBlock(z, 5);
1015 problem->AddParameterBlock(w, 3);
1016
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001017 // clang-format off
1018
Austin Schuh70cc9552019-01-21 19:46:48 -08001019 // Add all combinations of cost functions.
1020 CostFunction* cost_yzw = new TernaryCostFunction(1, 4, 5, 3);
1021 CostFunction* cost_yz = new BinaryCostFunction (1, 4, 5);
1022 CostFunction* cost_yw = new BinaryCostFunction (1, 4, 3);
1023 CostFunction* cost_zw = new BinaryCostFunction (1, 5, 3);
1024 CostFunction* cost_y = new UnaryCostFunction (1, 4);
1025 CostFunction* cost_z = new UnaryCostFunction (1, 5);
1026 CostFunction* cost_w = new UnaryCostFunction (1, 3);
1027
Austin Schuh3de38b02024-06-25 18:25:10 -07001028 ResidualBlock* r_yzw = problem->AddResidualBlock(cost_yzw, nullptr, y, z, w);
Austin Schuh70cc9552019-01-21 19:46:48 -08001029 {
Austin Schuh3de38b02024-06-25 18:25:10 -07001030 ResidualBlockId expected_residuals[] = {r_yzw, nullptr};
Austin Schuh70cc9552019-01-21 19:46:48 -08001031 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1032 }
Austin Schuh3de38b02024-06-25 18:25:10 -07001033 ResidualBlock* r_yz = problem->AddResidualBlock(cost_yz, nullptr, y, z);
Austin Schuh70cc9552019-01-21 19:46:48 -08001034 {
Austin Schuh3de38b02024-06-25 18:25:10 -07001035 ResidualBlockId expected_residuals[] = {r_yzw, r_yz, nullptr};
Austin Schuh70cc9552019-01-21 19:46:48 -08001036 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1037 }
Austin Schuh3de38b02024-06-25 18:25:10 -07001038 ResidualBlock* r_yw = problem->AddResidualBlock(cost_yw, nullptr, y, w);
Austin Schuh70cc9552019-01-21 19:46:48 -08001039 {
Austin Schuh3de38b02024-06-25 18:25:10 -07001040 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, nullptr};
Austin Schuh70cc9552019-01-21 19:46:48 -08001041 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1042 }
Austin Schuh3de38b02024-06-25 18:25:10 -07001043 ResidualBlock* r_zw = problem->AddResidualBlock(cost_zw, nullptr, z, w);
Austin Schuh70cc9552019-01-21 19:46:48 -08001044 {
Austin Schuh3de38b02024-06-25 18:25:10 -07001045 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, nullptr};
Austin Schuh70cc9552019-01-21 19:46:48 -08001046 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1047 }
Austin Schuh3de38b02024-06-25 18:25:10 -07001048 ResidualBlock* r_y = problem->AddResidualBlock(cost_y, nullptr, y);
Austin Schuh70cc9552019-01-21 19:46:48 -08001049 {
Austin Schuh3de38b02024-06-25 18:25:10 -07001050 ResidualBlock *expected_residuals[] = {r_yzw, r_yz, r_yw, r_zw, r_y, nullptr};
Austin Schuh70cc9552019-01-21 19:46:48 -08001051 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1052 }
Austin Schuh3de38b02024-06-25 18:25:10 -07001053 ResidualBlock* r_z = problem->AddResidualBlock(cost_z, nullptr, z);
Austin Schuh70cc9552019-01-21 19:46:48 -08001054 {
1055 ResidualBlock *expected_residuals[] = {
Austin Schuh3de38b02024-06-25 18:25:10 -07001056 r_yzw, r_yz, r_yw, r_zw, r_y, r_z, nullptr
Austin Schuh70cc9552019-01-21 19:46:48 -08001057 };
1058 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1059 }
Austin Schuh3de38b02024-06-25 18:25:10 -07001060 ResidualBlock* r_w = problem->AddResidualBlock(cost_w, nullptr, w);
Austin Schuh70cc9552019-01-21 19:46:48 -08001061 {
1062 ResidualBlock *expected_residuals[] = {
Austin Schuh3de38b02024-06-25 18:25:10 -07001063 r_yzw, r_yz, r_yw, r_zw, r_y, r_z, r_w, nullptr
Austin Schuh70cc9552019-01-21 19:46:48 -08001064 };
1065 ExpectProblemHasResidualBlocks(*problem, expected_residuals);
1066 }
1067
Austin Schuh3de38b02024-06-25 18:25:10 -07001068 std::vector<double*> parameter_blocks;
1069 std::vector<ResidualBlockId> residual_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -08001070
1071 // Check GetResidualBlocksForParameterBlock() for all parameter blocks.
1072 struct GetResidualBlocksForParameterBlockTestCase {
1073 double* parameter_block;
1074 ResidualBlockId expected_residual_blocks[10];
1075 };
1076 GetResidualBlocksForParameterBlockTestCase get_residual_blocks_cases[] = {
Austin Schuh3de38b02024-06-25 18:25:10 -07001077 { y, { r_yzw, r_yz, r_yw, r_y, nullptr} },
1078 { z, { r_yzw, r_yz, r_zw, r_z, nullptr} },
1079 { w, { r_yzw, r_yw, r_zw, r_w, nullptr} },
1080 { nullptr, { nullptr } }
Austin Schuh70cc9552019-01-21 19:46:48 -08001081 };
1082 for (int i = 0; get_residual_blocks_cases[i].parameter_block; ++i) {
1083 problem->GetResidualBlocksForParameterBlock(
1084 get_residual_blocks_cases[i].parameter_block,
1085 &residual_blocks);
1086 ExpectVectorContainsUnordered(
1087 get_residual_blocks_cases[i].expected_residual_blocks,
1088 residual_blocks);
1089 }
1090
1091 // Check GetParameterBlocksForResidualBlock() for all residual blocks.
1092 struct GetParameterBlocksForResidualBlockTestCase {
1093 ResidualBlockId residual_block;
1094 double* expected_parameter_blocks[10];
1095 };
1096 GetParameterBlocksForResidualBlockTestCase get_parameter_blocks_cases[] = {
Austin Schuh3de38b02024-06-25 18:25:10 -07001097 { r_yzw, { y, z, w, nullptr } },
1098 { r_yz , { y, z, nullptr } },
1099 { r_yw , { y, w, nullptr } },
1100 { r_zw , { z, w, nullptr } },
1101 { r_y , { y, nullptr } },
1102 { r_z , { z, nullptr } },
1103 { r_w , { w, nullptr } },
1104 { nullptr, { nullptr } }
Austin Schuh70cc9552019-01-21 19:46:48 -08001105 };
1106 for (int i = 0; get_parameter_blocks_cases[i].residual_block; ++i) {
1107 problem->GetParameterBlocksForResidualBlock(
1108 get_parameter_blocks_cases[i].residual_block,
1109 &parameter_blocks);
1110 ExpectVectorContainsUnordered(
1111 get_parameter_blocks_cases[i].expected_parameter_blocks,
1112 parameter_blocks);
1113 }
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001114
1115 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001116}
1117
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001118INSTANTIATE_TEST_SUITE_P(OptionsInstantiation,
1119 DynamicProblem,
1120 ::testing::Values(true, false));
Austin Schuh70cc9552019-01-21 19:46:48 -08001121
1122// Test for Problem::Evaluate
1123
1124// r_i = i - (j + 1) * x_ij^2
1125template <int kNumResiduals, int kNumParameterBlocks>
1126class QuadraticCostFunction : public CostFunction {
1127 public:
1128 QuadraticCostFunction() {
1129 CHECK_GT(kNumResiduals, 0);
1130 CHECK_GT(kNumParameterBlocks, 0);
1131 set_num_residuals(kNumResiduals);
1132 for (int i = 0; i < kNumParameterBlocks; ++i) {
1133 mutable_parameter_block_sizes()->push_back(kNumResiduals);
1134 }
1135 }
1136
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001137 bool Evaluate(double const* const* parameters,
1138 double* residuals,
1139 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -08001140 for (int i = 0; i < kNumResiduals; ++i) {
1141 residuals[i] = i;
1142 for (int j = 0; j < kNumParameterBlocks; ++j) {
1143 residuals[i] -= (j + 1.0) * parameters[j][i] * parameters[j][i];
1144 }
1145 }
1146
Austin Schuh3de38b02024-06-25 18:25:10 -07001147 if (jacobians == nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001148 return true;
1149 }
1150
1151 for (int j = 0; j < kNumParameterBlocks; ++j) {
Austin Schuh3de38b02024-06-25 18:25:10 -07001152 if (jacobians[j] != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001153 MatrixRef(jacobians[j], kNumResiduals, kNumResiduals) =
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001154 (-2.0 * (j + 1.0) * ConstVectorRef(parameters[j], kNumResiduals))
1155 .asDiagonal();
Austin Schuh70cc9552019-01-21 19:46:48 -08001156 }
1157 }
1158
1159 return true;
1160 }
1161};
1162
1163// Convert a CRSMatrix to a dense Eigen matrix.
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001164static void CRSToDenseMatrix(const CRSMatrix& input, Matrix* output) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001165 CHECK(output != nullptr);
1166 Matrix& m = *output;
1167 m.resize(input.num_rows, input.num_cols);
1168 m.setZero();
1169 for (int row = 0; row < input.num_rows; ++row) {
1170 for (int j = input.rows[row]; j < input.rows[row + 1]; ++j) {
1171 const int col = input.cols[j];
1172 m(row, col) = input.values[j];
1173 }
1174 }
1175}
1176
1177class ProblemEvaluateTest : public ::testing::Test {
1178 protected:
Austin Schuh3de38b02024-06-25 18:25:10 -07001179 void SetUp() override {
Austin Schuh70cc9552019-01-21 19:46:48 -08001180 for (int i = 0; i < 6; ++i) {
1181 parameters_[i] = static_cast<double>(i + 1);
1182 }
1183
1184 parameter_blocks_.push_back(parameters_);
1185 parameter_blocks_.push_back(parameters_ + 2);
1186 parameter_blocks_.push_back(parameters_ + 4);
1187
Austin Schuh70cc9552019-01-21 19:46:48 -08001188 CostFunction* cost_function = new QuadraticCostFunction<2, 2>;
1189
1190 // f(x, y)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001191 residual_blocks_.push_back(problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -07001192 cost_function, nullptr, parameters_, parameters_ + 2));
Austin Schuh70cc9552019-01-21 19:46:48 -08001193 // g(y, z)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001194 residual_blocks_.push_back(problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -07001195 cost_function, nullptr, parameters_ + 2, parameters_ + 4));
Austin Schuh70cc9552019-01-21 19:46:48 -08001196 // h(z, x)
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001197 residual_blocks_.push_back(problem_.AddResidualBlock(
Austin Schuh3de38b02024-06-25 18:25:10 -07001198 cost_function, nullptr, parameters_ + 4, parameters_));
Austin Schuh70cc9552019-01-21 19:46:48 -08001199 }
1200
Austin Schuh3de38b02024-06-25 18:25:10 -07001201 void TearDown() override { EXPECT_TRUE(problem_.program().IsValid()); }
Austin Schuh70cc9552019-01-21 19:46:48 -08001202
1203 void EvaluateAndCompare(const Problem::EvaluateOptions& options,
1204 const int expected_num_rows,
1205 const int expected_num_cols,
1206 const double expected_cost,
1207 const double* expected_residuals,
1208 const double* expected_gradient,
1209 const double* expected_jacobian) {
1210 double cost;
Austin Schuh3de38b02024-06-25 18:25:10 -07001211 std::vector<double> residuals;
1212 std::vector<double> gradient;
Austin Schuh70cc9552019-01-21 19:46:48 -08001213 CRSMatrix jacobian;
1214
1215 EXPECT_TRUE(
1216 problem_.Evaluate(options,
1217 &cost,
Austin Schuh3de38b02024-06-25 18:25:10 -07001218 expected_residuals != nullptr ? &residuals : nullptr,
1219 expected_gradient != nullptr ? &gradient : nullptr,
1220 expected_jacobian != nullptr ? &jacobian : nullptr));
Austin Schuh70cc9552019-01-21 19:46:48 -08001221
Austin Schuh3de38b02024-06-25 18:25:10 -07001222 if (expected_residuals != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001223 EXPECT_EQ(residuals.size(), expected_num_rows);
1224 }
1225
Austin Schuh3de38b02024-06-25 18:25:10 -07001226 if (expected_gradient != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001227 EXPECT_EQ(gradient.size(), expected_num_cols);
1228 }
1229
Austin Schuh3de38b02024-06-25 18:25:10 -07001230 if (expected_jacobian != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001231 EXPECT_EQ(jacobian.num_rows, expected_num_rows);
1232 EXPECT_EQ(jacobian.num_cols, expected_num_cols);
1233 }
1234
1235 Matrix dense_jacobian;
Austin Schuh3de38b02024-06-25 18:25:10 -07001236 if (expected_jacobian != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -08001237 CRSToDenseMatrix(jacobian, &dense_jacobian);
1238 }
1239
1240 CompareEvaluations(expected_num_rows,
1241 expected_num_cols,
1242 expected_cost,
1243 expected_residuals,
1244 expected_gradient,
1245 expected_jacobian,
1246 cost,
Austin Schuh3de38b02024-06-25 18:25:10 -07001247 !residuals.empty() ? &residuals[0] : nullptr,
1248 !gradient.empty() ? &gradient[0] : nullptr,
Austin Schuh70cc9552019-01-21 19:46:48 -08001249 dense_jacobian.data());
1250 }
1251
1252 void CheckAllEvaluationCombinations(const Problem::EvaluateOptions& options,
1253 const ExpectedEvaluation& expected) {
1254 for (int i = 0; i < 8; ++i) {
1255 EvaluateAndCompare(options,
1256 expected.num_rows,
1257 expected.num_cols,
1258 expected.cost,
Austin Schuh3de38b02024-06-25 18:25:10 -07001259 (i & 1) ? expected.residuals : nullptr,
1260 (i & 2) ? expected.gradient : nullptr,
1261 (i & 4) ? expected.jacobian : nullptr);
Austin Schuh70cc9552019-01-21 19:46:48 -08001262 }
1263 }
1264
1265 ProblemImpl problem_;
1266 double parameters_[6];
Austin Schuh3de38b02024-06-25 18:25:10 -07001267 std::vector<double*> parameter_blocks_;
1268 std::vector<ResidualBlockId> residual_blocks_;
Austin Schuh70cc9552019-01-21 19:46:48 -08001269};
1270
Austin Schuh70cc9552019-01-21 19:46:48 -08001271TEST_F(ProblemEvaluateTest, MultipleParameterAndResidualBlocks) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001272 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001273 ExpectedEvaluation expected = {
1274 // Rows/columns
1275 6, 6,
1276 // Cost
1277 7607.0,
1278 // Residuals
1279 { -19.0, -35.0, // f
1280 -59.0, -87.0, // g
1281 -27.0, -43.0 // h
1282 },
1283 // Gradient
1284 { 146.0, 484.0, // x
1285 582.0, 1256.0, // y
1286 1450.0, 2604.0, // z
1287 },
1288 // Jacobian
1289 // x y z
1290 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1291 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1292 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1293 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
1294 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1295 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1296 }
1297 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001298 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001299
1300 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1301}
1302
1303TEST_F(ProblemEvaluateTest, ParameterAndResidualBlocksPassedInOptions) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001304 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001305 ExpectedEvaluation expected = {
1306 // Rows/columns
1307 6, 6,
1308 // Cost
1309 7607.0,
1310 // Residuals
1311 { -19.0, -35.0, // f
1312 -59.0, -87.0, // g
1313 -27.0, -43.0 // h
1314 },
1315 // Gradient
1316 { 146.0, 484.0, // x
1317 582.0, 1256.0, // y
1318 1450.0, 2604.0, // z
1319 },
1320 // Jacobian
1321 // x y z
1322 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1323 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1324 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1325 0.0, 0.0, 0.0, -8.0, 0.0, -24.0,
1326 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1327 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1328 }
1329 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001330 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001331
1332 Problem::EvaluateOptions evaluate_options;
1333 evaluate_options.parameter_blocks = parameter_blocks_;
1334 evaluate_options.residual_blocks = residual_blocks_;
1335 CheckAllEvaluationCombinations(evaluate_options, expected);
1336}
1337
1338TEST_F(ProblemEvaluateTest, ReorderedResidualBlocks) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001339 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001340 ExpectedEvaluation expected = {
1341 // Rows/columns
1342 6, 6,
1343 // Cost
1344 7607.0,
1345 // Residuals
1346 { -19.0, -35.0, // f
1347 -27.0, -43.0, // h
1348 -59.0, -87.0 // g
1349 },
1350 // Gradient
1351 { 146.0, 484.0, // x
1352 582.0, 1256.0, // y
1353 1450.0, 2604.0, // z
1354 },
1355 // Jacobian
1356 // x y z
1357 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1358 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1359 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1360 0.0, -8.0, 0.0, 0.0, 0.0, -12.0,
1361 /* g(y, z) */ 0.0, 0.0, -6.0, 0.0, -20.0, 0.0,
1362 0.0, 0.0, 0.0, -8.0, 0.0, -24.0
1363 }
1364 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001365 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001366
1367 Problem::EvaluateOptions evaluate_options;
1368 evaluate_options.parameter_blocks = parameter_blocks_;
1369
1370 // f, h, g
1371 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1372 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1373 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1374
1375 CheckAllEvaluationCombinations(evaluate_options, expected);
1376}
1377
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001378TEST_F(ProblemEvaluateTest,
1379 ReorderedResidualBlocksAndReorderedParameterBlocks) {
1380 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001381 ExpectedEvaluation expected = {
1382 // Rows/columns
1383 6, 6,
1384 // Cost
1385 7607.0,
1386 // Residuals
1387 { -19.0, -35.0, // f
1388 -27.0, -43.0, // h
1389 -59.0, -87.0 // g
1390 },
1391 // Gradient
1392 { 1450.0, 2604.0, // z
1393 582.0, 1256.0, // y
1394 146.0, 484.0, // x
1395 },
1396 // Jacobian
1397 // z y x
1398 { /* f(x, y) */ 0.0, 0.0, -12.0, 0.0, -2.0, 0.0,
1399 0.0, 0.0, 0.0, -16.0, 0.0, -4.0,
1400 /* h(z, x) */ -10.0, 0.0, 0.0, 0.0, -4.0, 0.0,
1401 0.0, -12.0, 0.0, 0.0, 0.0, -8.0,
1402 /* g(y, z) */ -20.0, 0.0, -6.0, 0.0, 0.0, 0.0,
1403 0.0, -24.0, 0.0, -8.0, 0.0, 0.0
1404 }
1405 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001406 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001407
1408 Problem::EvaluateOptions evaluate_options;
1409 // z, y, x
1410 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1411 evaluate_options.parameter_blocks.push_back(parameter_blocks_[1]);
1412 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1413
1414 // f, h, g
1415 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1416 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1417 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1418
1419 CheckAllEvaluationCombinations(evaluate_options, expected);
1420}
1421
1422TEST_F(ProblemEvaluateTest, ConstantParameterBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001423 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001424 ExpectedEvaluation expected = {
1425 // Rows/columns
1426 6, 6,
1427 // Cost
1428 7607.0,
1429 // Residuals
1430 { -19.0, -35.0, // f
1431 -59.0, -87.0, // g
1432 -27.0, -43.0 // h
1433 },
1434
1435 // Gradient
1436 { 146.0, 484.0, // x
1437 0.0, 0.0, // y
1438 1450.0, 2604.0, // z
1439 },
1440
1441 // Jacobian
1442 // x y z
1443 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1444 0.0, -4.0, 0.0, 0.0, 0.0, 0.0,
1445 /* g(y, z) */ 0.0, 0.0, 0.0, 0.0, -20.0, 0.0,
1446 0.0, 0.0, 0.0, 0.0, 0.0, -24.0,
1447 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1448 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1449 }
1450 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001451 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001452
1453 problem_.SetParameterBlockConstant(parameters_ + 2);
1454 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1455}
1456
1457TEST_F(ProblemEvaluateTest, ExcludedAResidualBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001458 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001459 ExpectedEvaluation expected = {
1460 // Rows/columns
1461 4, 6,
1462 // Cost
1463 2082.0,
1464 // Residuals
1465 { -19.0, -35.0, // f
1466 -27.0, -43.0 // h
1467 },
1468 // Gradient
1469 { 146.0, 484.0, // x
1470 228.0, 560.0, // y
1471 270.0, 516.0, // z
1472 },
1473 // Jacobian
1474 // x y z
1475 { /* f(x, y) */ -2.0, 0.0, -12.0, 0.0, 0.0, 0.0,
1476 0.0, -4.0, 0.0, -16.0, 0.0, 0.0,
1477 /* h(z, x) */ -4.0, 0.0, 0.0, 0.0, -10.0, 0.0,
1478 0.0, -8.0, 0.0, 0.0, 0.0, -12.0
1479 }
1480 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001481 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001482
1483 Problem::EvaluateOptions evaluate_options;
1484 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1485 evaluate_options.residual_blocks.push_back(residual_blocks_[2]);
1486
1487 CheckAllEvaluationCombinations(evaluate_options, expected);
1488}
1489
1490TEST_F(ProblemEvaluateTest, ExcludedParameterBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001491 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001492 ExpectedEvaluation expected = {
1493 // Rows/columns
1494 6, 4,
1495 // Cost
1496 7607.0,
1497 // Residuals
1498 { -19.0, -35.0, // f
1499 -59.0, -87.0, // g
1500 -27.0, -43.0 // h
1501 },
1502
1503 // Gradient
1504 { 146.0, 484.0, // x
1505 1450.0, 2604.0, // z
1506 },
1507
1508 // Jacobian
1509 // x z
1510 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
1511 0.0, -4.0, 0.0, 0.0,
1512 /* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
1513 0.0, 0.0, 0.0, -24.0,
1514 /* h(z, x) */ -4.0, 0.0, -10.0, 0.0,
1515 0.0, -8.0, 0.0, -12.0
1516 }
1517 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001518 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001519
1520 Problem::EvaluateOptions evaluate_options;
1521 // x, z
1522 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1523 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1524 evaluate_options.residual_blocks = residual_blocks_;
1525 CheckAllEvaluationCombinations(evaluate_options, expected);
1526}
1527
1528TEST_F(ProblemEvaluateTest, ExcludedParameterBlockAndExcludedResidualBlock) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001529 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001530 ExpectedEvaluation expected = {
1531 // Rows/columns
1532 4, 4,
1533 // Cost
1534 6318.0,
1535 // Residuals
1536 { -19.0, -35.0, // f
1537 -59.0, -87.0, // g
1538 },
1539
1540 // Gradient
1541 { 38.0, 140.0, // x
1542 1180.0, 2088.0, // z
1543 },
1544
1545 // Jacobian
1546 // x z
1547 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0,
1548 0.0, -4.0, 0.0, 0.0,
1549 /* g(y, z) */ 0.0, 0.0, -20.0, 0.0,
1550 0.0, 0.0, 0.0, -24.0,
1551 }
1552 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001553 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001554
1555 Problem::EvaluateOptions evaluate_options;
1556 // x, z
1557 evaluate_options.parameter_blocks.push_back(parameter_blocks_[0]);
1558 evaluate_options.parameter_blocks.push_back(parameter_blocks_[2]);
1559 evaluate_options.residual_blocks.push_back(residual_blocks_[0]);
1560 evaluate_options.residual_blocks.push_back(residual_blocks_[1]);
1561
1562 CheckAllEvaluationCombinations(evaluate_options, expected);
1563}
1564
Austin Schuh3de38b02024-06-25 18:25:10 -07001565TEST_F(ProblemEvaluateTest, Manifold) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001566 // clang-format off
Austin Schuh70cc9552019-01-21 19:46:48 -08001567 ExpectedEvaluation expected = {
1568 // Rows/columns
1569 6, 5,
1570 // Cost
1571 7607.0,
1572 // Residuals
1573 { -19.0, -35.0, // f
1574 -59.0, -87.0, // g
1575 -27.0, -43.0 // h
1576 },
1577 // Gradient
1578 { 146.0, 484.0, // x
Austin Schuh3de38b02024-06-25 18:25:10 -07001579 1256.0, // y with SubsetManifold
Austin Schuh70cc9552019-01-21 19:46:48 -08001580 1450.0, 2604.0, // z
1581 },
1582 // Jacobian
1583 // x y z
1584 { /* f(x, y) */ -2.0, 0.0, 0.0, 0.0, 0.0,
1585 0.0, -4.0, -16.0, 0.0, 0.0,
1586 /* g(y, z) */ 0.0, 0.0, 0.0, -20.0, 0.0,
1587 0.0, 0.0, -8.0, 0.0, -24.0,
1588 /* h(z, x) */ -4.0, 0.0, 0.0, -10.0, 0.0,
1589 0.0, -8.0, 0.0, 0.0, -12.0
1590 }
1591 };
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001592 // clang-format on
Austin Schuh70cc9552019-01-21 19:46:48 -08001593
Austin Schuh3de38b02024-06-25 18:25:10 -07001594 std::vector<int> constant_parameters;
Austin Schuh70cc9552019-01-21 19:46:48 -08001595 constant_parameters.push_back(0);
Austin Schuh3de38b02024-06-25 18:25:10 -07001596 problem_.SetManifold(parameters_ + 2,
1597 new SubsetManifold(2, constant_parameters));
Austin Schuh70cc9552019-01-21 19:46:48 -08001598
1599 CheckAllEvaluationCombinations(Problem::EvaluateOptions(), expected);
1600}
1601
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001602struct IdentityFunctor {
1603 template <typename T>
1604 bool operator()(const T* x, const T* y, T* residuals) const {
1605 residuals[0] = x[0];
1606 residuals[1] = x[1];
1607 residuals[2] = y[0];
1608 residuals[3] = y[1];
1609 residuals[4] = y[2];
1610 return true;
1611 }
1612
1613 static CostFunction* Create() {
1614 return new AutoDiffCostFunction<IdentityFunctor, 5, 2, 3>(
1615 new IdentityFunctor);
1616 }
1617};
1618
1619class ProblemEvaluateResidualBlockTest : public ::testing::Test {
1620 public:
1621 static constexpr bool kApplyLossFunction = true;
1622 static constexpr bool kDoNotApplyLossFunction = false;
1623 static constexpr bool kNewPoint = true;
1624 static constexpr bool kNotNewPoint = false;
1625 static double loss_function_scale_;
1626
1627 protected:
1628 ProblemImpl problem_;
1629 double x_[2] = {1, 2};
1630 double y_[3] = {1, 2, 3};
1631};
1632
1633double ProblemEvaluateResidualBlockTest::loss_function_scale_ = 2.0;
1634
1635TEST_F(ProblemEvaluateResidualBlockTest,
1636 OneResidualBlockNoLossFunctionFullEval) {
1637 ResidualBlockId residual_block_id =
1638 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1639 Vector expected_f(5);
1640 expected_f << 1, 2, 1, 2, 3;
1641 Matrix expected_dfdx = Matrix::Zero(5, 2);
1642 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1643 Matrix expected_dfdy = Matrix::Zero(5, 3);
1644 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1645 double expected_cost = expected_f.squaredNorm() / 2.0;
1646
1647 double actual_cost;
1648 Vector actual_f(5);
1649 Matrix actual_dfdx(5, 2);
1650 Matrix actual_dfdy(5, 3);
1651 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1652 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1653 kApplyLossFunction,
1654 kNewPoint,
1655 &actual_cost,
1656 actual_f.data(),
1657 jacobians));
1658
1659 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1660 0,
1661 std::numeric_limits<double>::epsilon())
1662 << actual_cost;
1663 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1664 0,
1665 std::numeric_limits<double>::epsilon())
1666 << actual_f;
1667 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1668 0,
1669 std::numeric_limits<double>::epsilon())
1670 << actual_dfdx;
1671 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1672 0,
1673 std::numeric_limits<double>::epsilon())
1674 << actual_dfdy;
1675}
1676
1677TEST_F(ProblemEvaluateResidualBlockTest,
1678 OneResidualBlockNoLossFunctionNullEval) {
1679 ResidualBlockId residual_block_id =
1680 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1681 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1682 kApplyLossFunction,
1683 kNewPoint,
1684 nullptr,
1685 nullptr,
1686 nullptr));
1687}
1688
1689TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockNoLossFunctionCost) {
1690 ResidualBlockId residual_block_id =
1691 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1692 Vector expected_f(5);
1693 expected_f << 1, 2, 1, 2, 3;
1694 double expected_cost = expected_f.squaredNorm() / 2.0;
1695
1696 double actual_cost;
1697 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1698 kApplyLossFunction,
1699 kNewPoint,
1700 &actual_cost,
1701 nullptr,
1702 nullptr));
1703
1704 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1705 0,
1706 std::numeric_limits<double>::epsilon())
1707 << actual_cost;
1708}
1709
1710TEST_F(ProblemEvaluateResidualBlockTest,
1711 OneResidualBlockNoLossFunctionCostAndResidual) {
1712 ResidualBlockId residual_block_id =
1713 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1714 Vector expected_f(5);
1715 expected_f << 1, 2, 1, 2, 3;
1716 double expected_cost = expected_f.squaredNorm() / 2.0;
1717
1718 double actual_cost;
1719 Vector actual_f(5);
1720 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1721 kApplyLossFunction,
1722 kNewPoint,
1723 &actual_cost,
1724 actual_f.data(),
1725 nullptr));
1726
1727 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1728 0,
1729 std::numeric_limits<double>::epsilon())
1730 << actual_cost;
1731 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1732 0,
1733 std::numeric_limits<double>::epsilon())
1734 << actual_f;
1735}
1736
1737TEST_F(ProblemEvaluateResidualBlockTest,
1738 OneResidualBlockNoLossFunctionCostResidualAndOneJacobian) {
1739 ResidualBlockId residual_block_id =
1740 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1741 Vector expected_f(5);
1742 expected_f << 1, 2, 1, 2, 3;
1743 Matrix expected_dfdx = Matrix::Zero(5, 2);
1744 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1745 double expected_cost = expected_f.squaredNorm() / 2.0;
1746
1747 double actual_cost;
1748 Vector actual_f(5);
1749 Matrix actual_dfdx(5, 2);
1750 double* jacobians[2] = {actual_dfdx.data(), nullptr};
1751 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1752 kApplyLossFunction,
1753 kNewPoint,
1754 &actual_cost,
1755 actual_f.data(),
1756 jacobians));
1757
1758 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1759 0,
1760 std::numeric_limits<double>::epsilon())
1761 << actual_cost;
1762 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1763 0,
1764 std::numeric_limits<double>::epsilon())
1765 << actual_f;
1766 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1767 0,
1768 std::numeric_limits<double>::epsilon())
1769 << actual_dfdx;
1770}
1771
1772TEST_F(ProblemEvaluateResidualBlockTest,
1773 OneResidualBlockNoLossFunctionResidual) {
1774 ResidualBlockId residual_block_id =
1775 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1776 Vector expected_f(5);
1777 expected_f << 1, 2, 1, 2, 3;
1778 Vector actual_f(5);
1779 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1780 kApplyLossFunction,
1781 kNewPoint,
1782 nullptr,
1783 actual_f.data(),
1784 nullptr));
1785
1786 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1787 0,
1788 std::numeric_limits<double>::epsilon())
1789 << actual_f;
1790}
1791
1792TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockWithLossFunction) {
1793 ResidualBlockId residual_block_id =
1794 problem_.AddResidualBlock(IdentityFunctor::Create(),
1795 new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP),
1796 x_,
1797 y_);
1798 Vector expected_f(5);
1799 expected_f << 1, 2, 1, 2, 3;
1800 expected_f *= std::sqrt(loss_function_scale_);
1801 Matrix expected_dfdx = Matrix::Zero(5, 2);
1802 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1803 expected_dfdx *= std::sqrt(loss_function_scale_);
1804 Matrix expected_dfdy = Matrix::Zero(5, 3);
1805 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1806 expected_dfdy *= std::sqrt(loss_function_scale_);
1807 double expected_cost = expected_f.squaredNorm() / 2.0;
1808
1809 double actual_cost;
1810 Vector actual_f(5);
1811 Matrix actual_dfdx(5, 2);
1812 Matrix actual_dfdy(5, 3);
1813 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1814 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1815 kApplyLossFunction,
1816 kNewPoint,
1817 &actual_cost,
1818 actual_f.data(),
1819 jacobians));
1820
1821 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1822 0,
1823 std::numeric_limits<double>::epsilon())
1824 << actual_cost;
1825 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1826 0,
1827 std::numeric_limits<double>::epsilon())
1828 << actual_f;
1829 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1830 0,
1831 std::numeric_limits<double>::epsilon())
1832 << actual_dfdx;
1833 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1834 0,
1835 std::numeric_limits<double>::epsilon())
1836 << actual_dfdy;
1837}
1838
1839TEST_F(ProblemEvaluateResidualBlockTest,
1840 OneResidualBlockWithLossFunctionDisabled) {
1841 ResidualBlockId residual_block_id =
1842 problem_.AddResidualBlock(IdentityFunctor::Create(),
1843 new ScaledLoss(nullptr, 2.0, TAKE_OWNERSHIP),
1844 x_,
1845 y_);
1846 Vector expected_f(5);
1847 expected_f << 1, 2, 1, 2, 3;
1848 Matrix expected_dfdx = Matrix::Zero(5, 2);
1849 expected_dfdx.block(0, 0, 2, 2) = Matrix::Identity(2, 2);
1850 Matrix expected_dfdy = Matrix::Zero(5, 3);
1851 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1852 double expected_cost = expected_f.squaredNorm() / 2.0;
1853
1854 double actual_cost;
1855 Vector actual_f(5);
1856 Matrix actual_dfdx(5, 2);
1857 Matrix actual_dfdy(5, 3);
1858 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1859 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1860 kDoNotApplyLossFunction,
1861 kNewPoint,
1862 &actual_cost,
1863 actual_f.data(),
1864 jacobians));
1865
1866 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1867 0,
1868 std::numeric_limits<double>::epsilon())
1869 << actual_cost;
1870 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1871 0,
1872 std::numeric_limits<double>::epsilon())
1873 << actual_f;
1874 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1875 0,
1876 std::numeric_limits<double>::epsilon())
1877 << actual_dfdx;
1878 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1879 0,
1880 std::numeric_limits<double>::epsilon())
1881 << actual_dfdy;
1882}
1883
Austin Schuh3de38b02024-06-25 18:25:10 -07001884TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockWithOneManifold) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001885 ResidualBlockId residual_block_id =
1886 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
Austin Schuh3de38b02024-06-25 18:25:10 -07001887 problem_.SetManifold(x_, new SubsetManifold(2, {1}));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001888
1889 Vector expected_f(5);
1890 expected_f << 1, 2, 1, 2, 3;
1891 Matrix expected_dfdx = Matrix::Zero(5, 1);
1892 expected_dfdx.block(0, 0, 1, 1) = Matrix::Identity(1, 1);
1893 Matrix expected_dfdy = Matrix::Zero(5, 3);
1894 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1895 double expected_cost = expected_f.squaredNorm() / 2.0;
1896
1897 double actual_cost;
1898 Vector actual_f(5);
1899 Matrix actual_dfdx(5, 1);
1900 Matrix actual_dfdy(5, 3);
1901 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1902 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1903 kApplyLossFunction,
1904 kNewPoint,
1905 &actual_cost,
1906 actual_f.data(),
1907 jacobians));
1908
1909 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1910 0,
1911 std::numeric_limits<double>::epsilon())
1912 << actual_cost;
1913 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1914 0,
1915 std::numeric_limits<double>::epsilon())
1916 << actual_f;
1917 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1918 0,
1919 std::numeric_limits<double>::epsilon())
1920 << actual_dfdx;
1921 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1922 0,
1923 std::numeric_limits<double>::epsilon())
1924 << actual_dfdy;
1925}
1926
Austin Schuh3de38b02024-06-25 18:25:10 -07001927TEST_F(ProblemEvaluateResidualBlockTest, OneResidualBlockWithTwoManifolds) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001928 ResidualBlockId residual_block_id =
1929 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
Austin Schuh3de38b02024-06-25 18:25:10 -07001930 problem_.SetManifold(x_, new SubsetManifold(2, {1}));
1931 problem_.SetManifold(y_, new SubsetManifold(3, {2}));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08001932
1933 Vector expected_f(5);
1934 expected_f << 1, 2, 1, 2, 3;
1935 Matrix expected_dfdx = Matrix::Zero(5, 1);
1936 expected_dfdx.block(0, 0, 1, 1) = Matrix::Identity(1, 1);
1937 Matrix expected_dfdy = Matrix::Zero(5, 2);
1938 expected_dfdy.block(2, 0, 2, 2) = Matrix::Identity(2, 2);
1939 double expected_cost = expected_f.squaredNorm() / 2.0;
1940
1941 double actual_cost;
1942 Vector actual_f(5);
1943 Matrix actual_dfdx(5, 1);
1944 Matrix actual_dfdy(5, 2);
1945 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1946 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1947 kApplyLossFunction,
1948 kNewPoint,
1949 &actual_cost,
1950 actual_f.data(),
1951 jacobians));
1952
1953 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
1954 0,
1955 std::numeric_limits<double>::epsilon())
1956 << actual_cost;
1957 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
1958 0,
1959 std::numeric_limits<double>::epsilon())
1960 << actual_f;
1961 EXPECT_NEAR((expected_dfdx - actual_dfdx).norm() / actual_dfdx.norm(),
1962 0,
1963 std::numeric_limits<double>::epsilon())
1964 << actual_dfdx;
1965 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
1966 0,
1967 std::numeric_limits<double>::epsilon())
1968 << actual_dfdy;
1969}
1970
1971TEST_F(ProblemEvaluateResidualBlockTest,
1972 OneResidualBlockWithOneConstantParameterBlock) {
1973 ResidualBlockId residual_block_id =
1974 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
1975 problem_.SetParameterBlockConstant(x_);
1976
1977 Vector expected_f(5);
1978 expected_f << 1, 2, 1, 2, 3;
1979 Matrix expected_dfdy = Matrix::Zero(5, 3);
1980 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
1981 double expected_cost = expected_f.squaredNorm() / 2.0;
1982
1983 double actual_cost;
1984 Vector actual_f(5);
1985 Matrix actual_dfdx(5, 2);
1986 Matrix actual_dfdy(5, 3);
1987
1988 // Try evaluating both Jacobians, this should fail.
1989 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
1990 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
1991 kApplyLossFunction,
1992 kNewPoint,
1993 &actual_cost,
1994 actual_f.data(),
1995 jacobians));
1996
1997 jacobians[0] = nullptr;
1998 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
1999 kApplyLossFunction,
2000 kNewPoint,
2001 &actual_cost,
2002 actual_f.data(),
2003 jacobians));
2004
2005 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
2006 0,
2007 std::numeric_limits<double>::epsilon())
2008 << actual_cost;
2009 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
2010 0,
2011 std::numeric_limits<double>::epsilon())
2012 << actual_f;
2013 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
2014 0,
2015 std::numeric_limits<double>::epsilon())
2016 << actual_dfdy;
2017}
2018
2019TEST_F(ProblemEvaluateResidualBlockTest,
2020 OneResidualBlockWithAllConstantParameterBlocks) {
2021 ResidualBlockId residual_block_id =
2022 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2023 problem_.SetParameterBlockConstant(x_);
2024 problem_.SetParameterBlockConstant(y_);
2025
2026 Vector expected_f(5);
2027 expected_f << 1, 2, 1, 2, 3;
2028 double expected_cost = expected_f.squaredNorm() / 2.0;
2029
2030 double actual_cost;
2031 Vector actual_f(5);
2032 Matrix actual_dfdx(5, 2);
2033 Matrix actual_dfdy(5, 3);
2034
2035 // Try evaluating with one or more Jacobians, this should fail.
2036 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2037 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
2038 kApplyLossFunction,
2039 kNewPoint,
2040 &actual_cost,
2041 actual_f.data(),
2042 jacobians));
2043
2044 jacobians[0] = nullptr;
2045 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
2046 kApplyLossFunction,
2047 kNewPoint,
2048 &actual_cost,
2049 actual_f.data(),
2050 jacobians));
2051 jacobians[1] = nullptr;
2052 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
2053 kApplyLossFunction,
2054 kNewPoint,
2055 &actual_cost,
2056 actual_f.data(),
2057 jacobians));
2058
2059 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
2060 0,
2061 std::numeric_limits<double>::epsilon())
2062 << actual_cost;
2063 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
2064 0,
2065 std::numeric_limits<double>::epsilon())
2066 << actual_f;
2067}
2068
2069TEST_F(ProblemEvaluateResidualBlockTest,
2070 OneResidualBlockWithOneParameterBlockConstantAndParameterBlockChanged) {
2071 ResidualBlockId residual_block_id =
2072 problem_.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2073 problem_.SetParameterBlockConstant(x_);
2074
2075 x_[0] = 2;
2076 y_[2] = 1;
2077 Vector expected_f(5);
2078 expected_f << 2, 2, 1, 2, 1;
2079 Matrix expected_dfdy = Matrix::Zero(5, 3);
2080 expected_dfdy.block(2, 0, 3, 3) = Matrix::Identity(3, 3);
2081 double expected_cost = expected_f.squaredNorm() / 2.0;
2082
2083 double actual_cost;
2084 Vector actual_f(5);
2085 Matrix actual_dfdx(5, 2);
2086 Matrix actual_dfdy(5, 3);
2087
2088 // Try evaluating with one or more Jacobians, this should fail.
2089 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2090 EXPECT_FALSE(problem_.EvaluateResidualBlock(residual_block_id,
2091 kApplyLossFunction,
2092 kNewPoint,
2093 &actual_cost,
2094 actual_f.data(),
2095 jacobians));
2096
2097 jacobians[0] = nullptr;
2098 EXPECT_TRUE(problem_.EvaluateResidualBlock(residual_block_id,
2099 kApplyLossFunction,
2100 kNewPoint,
2101 &actual_cost,
2102 actual_f.data(),
2103 jacobians));
2104 EXPECT_NEAR(std::abs(expected_cost - actual_cost) / actual_cost,
2105 0,
2106 std::numeric_limits<double>::epsilon())
2107 << actual_cost;
2108 EXPECT_NEAR((expected_f - actual_f).norm() / actual_f.norm(),
2109 0,
2110 std::numeric_limits<double>::epsilon())
2111 << actual_f;
2112 EXPECT_NEAR((expected_dfdy - actual_dfdy).norm() / actual_dfdy.norm(),
2113 0,
2114 std::numeric_limits<double>::epsilon())
2115 << actual_dfdy;
2116}
2117
Austin Schuh70cc9552019-01-21 19:46:48 -08002118TEST(Problem, SetAndGetParameterLowerBound) {
2119 Problem problem;
2120 double x[] = {1.0, 2.0};
2121 problem.AddParameterBlock(x, 2);
2122
2123 EXPECT_EQ(problem.GetParameterLowerBound(x, 0),
2124 -std::numeric_limits<double>::max());
2125 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2126 -std::numeric_limits<double>::max());
2127
2128 problem.SetParameterLowerBound(x, 0, -1.0);
2129 EXPECT_EQ(problem.GetParameterLowerBound(x, 0), -1.0);
2130 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2131 -std::numeric_limits<double>::max());
2132
2133 problem.SetParameterLowerBound(x, 0, -2.0);
2134 EXPECT_EQ(problem.GetParameterLowerBound(x, 0), -2.0);
2135 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2136 -std::numeric_limits<double>::max());
2137
2138 problem.SetParameterLowerBound(x, 0, -std::numeric_limits<double>::max());
2139 EXPECT_EQ(problem.GetParameterLowerBound(x, 0),
2140 -std::numeric_limits<double>::max());
2141 EXPECT_EQ(problem.GetParameterLowerBound(x, 1),
2142 -std::numeric_limits<double>::max());
2143}
2144
2145TEST(Problem, SetAndGetParameterUpperBound) {
2146 Problem problem;
2147 double x[] = {1.0, 2.0};
2148 problem.AddParameterBlock(x, 2);
2149
2150 EXPECT_EQ(problem.GetParameterUpperBound(x, 0),
2151 std::numeric_limits<double>::max());
2152 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2153 std::numeric_limits<double>::max());
2154
2155 problem.SetParameterUpperBound(x, 0, -1.0);
2156 EXPECT_EQ(problem.GetParameterUpperBound(x, 0), -1.0);
2157 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2158 std::numeric_limits<double>::max());
2159
2160 problem.SetParameterUpperBound(x, 0, -2.0);
2161 EXPECT_EQ(problem.GetParameterUpperBound(x, 0), -2.0);
2162 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2163 std::numeric_limits<double>::max());
2164
2165 problem.SetParameterUpperBound(x, 0, std::numeric_limits<double>::max());
2166 EXPECT_EQ(problem.GetParameterUpperBound(x, 0),
2167 std::numeric_limits<double>::max());
2168 EXPECT_EQ(problem.GetParameterUpperBound(x, 1),
2169 std::numeric_limits<double>::max());
2170}
2171
Austin Schuh3de38b02024-06-25 18:25:10 -07002172TEST(Problem, SetManifoldTwice) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002173 Problem problem;
2174 double x[] = {1.0, 2.0, 3.0};
2175 problem.AddParameterBlock(x, 3);
Austin Schuh3de38b02024-06-25 18:25:10 -07002176 problem.SetManifold(x, new SubsetManifold(3, {1}));
2177 EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 3);
2178 EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 2);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002179
Austin Schuh3de38b02024-06-25 18:25:10 -07002180 problem.SetManifold(x, new SubsetManifold(3, {0, 1}));
2181 EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 3);
2182 EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 1);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002183}
2184
Austin Schuh3de38b02024-06-25 18:25:10 -07002185TEST(Problem, SetManifoldAndThenClearItWithNull) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002186 Problem problem;
2187 double x[] = {1.0, 2.0, 3.0};
2188 problem.AddParameterBlock(x, 3);
Austin Schuh3de38b02024-06-25 18:25:10 -07002189 problem.SetManifold(x, new SubsetManifold(3, {1}));
2190 EXPECT_EQ(problem.GetManifold(x)->AmbientSize(), 3);
2191 EXPECT_EQ(problem.GetManifold(x)->TangentSize(), 2);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002192
Austin Schuh3de38b02024-06-25 18:25:10 -07002193 problem.SetManifold(x, nullptr);
2194 EXPECT_EQ(problem.GetManifold(x), nullptr);
2195 EXPECT_EQ(problem.ParameterBlockTangentSize(x), 3);
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002196 EXPECT_EQ(problem.ParameterBlockSize(x), 3);
2197}
2198
Austin Schuh3de38b02024-06-25 18:25:10 -07002199TEST(Solver, ZeroTangentSizedManifoldMeansParameterBlockIsConstant) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002200 double x = 0.0;
2201 double y = 1.0;
2202 Problem problem;
2203 problem.AddResidualBlock(new BinaryCostFunction(1, 1, 1), nullptr, &x, &y);
Austin Schuh3de38b02024-06-25 18:25:10 -07002204 problem.SetManifold(&y, new SubsetManifold(1, {0}));
Austin Schuh1d1e6ea2020-12-23 21:56:30 -08002205 EXPECT_TRUE(problem.IsParameterBlockConstant(&y));
2206}
2207
2208class MockEvaluationCallback : public EvaluationCallback {
2209 public:
2210 MOCK_METHOD2(PrepareForEvaluation, void(bool, bool));
2211};
2212
2213TEST(ProblemEvaluate, CallsEvaluationCallbackWithoutJacobian) {
2214 constexpr bool kDoNotComputeJacobians = false;
2215 constexpr bool kNewPoint = true;
2216
2217 MockEvaluationCallback evaluation_callback;
2218 EXPECT_CALL(evaluation_callback,
2219 PrepareForEvaluation(kDoNotComputeJacobians, kNewPoint))
2220 .Times(1);
2221
2222 Problem::Options options;
2223 options.evaluation_callback = &evaluation_callback;
2224 ProblemImpl problem(options);
2225 double x_[2] = {1, 2};
2226 double y_[3] = {1, 2, 3};
2227 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2228
2229 double actual_cost;
2230 EXPECT_TRUE(problem.Evaluate(
2231 Problem::EvaluateOptions(), &actual_cost, nullptr, nullptr, nullptr));
2232}
2233
2234TEST(ProblemEvaluate, CallsEvaluationCallbackWithJacobian) {
2235 constexpr bool kComputeJacobians = true;
2236 constexpr bool kNewPoint = true;
2237
2238 MockEvaluationCallback evaluation_callback;
2239 EXPECT_CALL(evaluation_callback,
2240 PrepareForEvaluation(kComputeJacobians, kNewPoint))
2241 .Times(1);
2242
2243 Problem::Options options;
2244 options.evaluation_callback = &evaluation_callback;
2245 ProblemImpl problem(options);
2246 double x_[2] = {1, 2};
2247 double y_[3] = {1, 2, 3};
2248 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2249
2250 double actual_cost;
2251 ceres::CRSMatrix jacobian;
2252 EXPECT_TRUE(problem.Evaluate(
2253 Problem::EvaluateOptions(), &actual_cost, nullptr, nullptr, &jacobian));
2254}
2255
2256TEST(ProblemEvaluateResidualBlock, NewPointCallsEvaluationCallback) {
2257 constexpr bool kComputeJacobians = true;
2258 constexpr bool kNewPoint = true;
2259
2260 MockEvaluationCallback evaluation_callback;
2261 EXPECT_CALL(evaluation_callback,
2262 PrepareForEvaluation(kComputeJacobians, kNewPoint))
2263 .Times(1);
2264
2265 Problem::Options options;
2266 options.evaluation_callback = &evaluation_callback;
2267 ProblemImpl problem(options);
2268 double x_[2] = {1, 2};
2269 double y_[3] = {1, 2, 3};
2270 ResidualBlockId residual_block_id =
2271 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2272
2273 double actual_cost;
2274 Vector actual_f(5);
2275 Matrix actual_dfdx(5, 2);
2276 Matrix actual_dfdy(5, 3);
2277 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2278 EXPECT_TRUE(problem.EvaluateResidualBlock(
2279 residual_block_id, true, true, &actual_cost, actual_f.data(), jacobians));
2280}
2281
2282TEST(ProblemEvaluateResidualBlock, OldPointCallsEvaluationCallback) {
2283 constexpr bool kComputeJacobians = true;
2284 constexpr bool kOldPoint = false;
2285
2286 MockEvaluationCallback evaluation_callback;
2287 EXPECT_CALL(evaluation_callback,
2288 PrepareForEvaluation(kComputeJacobians, kOldPoint))
2289 .Times(1);
2290
2291 Problem::Options options;
2292 options.evaluation_callback = &evaluation_callback;
2293 ProblemImpl problem(options);
2294 double x_[2] = {1, 2};
2295 double y_[3] = {1, 2, 3};
2296 ResidualBlockId residual_block_id =
2297 problem.AddResidualBlock(IdentityFunctor::Create(), nullptr, x_, y_);
2298
2299 double actual_cost;
2300 Vector actual_f(5);
2301 Matrix actual_dfdx(5, 2);
2302 Matrix actual_dfdy(5, 3);
2303 double* jacobians[2] = {actual_dfdx.data(), actual_dfdy.data()};
2304 EXPECT_TRUE(problem.EvaluateResidualBlock(residual_block_id,
2305 true,
2306 false,
2307 &actual_cost,
2308 actual_f.data(),
2309 jacobians));
2310}
2311
Austin Schuh3de38b02024-06-25 18:25:10 -07002312} // namespace ceres::internal