blob: a8db314a484af388ce60709bbd9880ea6a745d89 [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/reorder_program.h"
32
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080033#include <random>
Austin Schuh3de38b02024-06-25 18:25:10 -070034#include <vector>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080035
Austin Schuh3de38b02024-06-25 18:25:10 -070036#include "ceres/internal/config.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080037#include "ceres/parameter_block.h"
38#include "ceres/problem_impl.h"
39#include "ceres/program.h"
40#include "ceres/sized_cost_function.h"
41#include "ceres/solver.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080042#include "gmock/gmock.h"
43#include "gtest/gtest.h"
44
45namespace ceres {
46namespace internal {
47
Austin Schuh70cc9552019-01-21 19:46:48 -080048// Templated base class for the CostFunction signatures.
49template <int kNumResiduals, int... Ns>
50class MockCostFunctionBase : public SizedCostFunction<kNumResiduals, Ns...> {
51 public:
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080052 bool Evaluate(double const* const* parameters,
53 double* residuals,
54 double** jacobians) const final {
Austin Schuh70cc9552019-01-21 19:46:48 -080055 // Do nothing. This is never called.
56 return true;
57 }
58};
59
60class UnaryCostFunction : public MockCostFunctionBase<2, 1> {};
61class BinaryCostFunction : public MockCostFunctionBase<2, 1, 1> {};
62class TernaryCostFunction : public MockCostFunctionBase<2, 1, 1, 1> {};
63
64TEST(_, ReorderResidualBlockNormalFunction) {
65 ProblemImpl problem;
66 double x;
67 double y;
68 double z;
69
70 problem.AddParameterBlock(&x, 1);
71 problem.AddParameterBlock(&y, 1);
72 problem.AddParameterBlock(&z, 1);
73
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080074 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
75 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &x);
76 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &y);
77 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &z);
78 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
79 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &y);
Austin Schuh70cc9552019-01-21 19:46:48 -080080
Austin Schuh3de38b02024-06-25 18:25:10 -070081 auto linear_solver_ordering = std::make_shared<ParameterBlockOrdering>();
Austin Schuh70cc9552019-01-21 19:46:48 -080082 linear_solver_ordering->AddElementToGroup(&x, 0);
83 linear_solver_ordering->AddElementToGroup(&y, 0);
84 linear_solver_ordering->AddElementToGroup(&z, 1);
85
86 Solver::Options options;
87 options.linear_solver_type = DENSE_SCHUR;
Austin Schuh3de38b02024-06-25 18:25:10 -070088 options.linear_solver_ordering = linear_solver_ordering;
Austin Schuh70cc9552019-01-21 19:46:48 -080089
Austin Schuh3de38b02024-06-25 18:25:10 -070090 const std::vector<ResidualBlock*>& residual_blocks =
Austin Schuh70cc9552019-01-21 19:46:48 -080091 problem.program().residual_blocks();
92
Austin Schuh3de38b02024-06-25 18:25:10 -070093 std::vector<ResidualBlock*> expected_residual_blocks;
Austin Schuh70cc9552019-01-21 19:46:48 -080094
95 // This is a bit fragile, but it serves the purpose. We know the
96 // bucketing algorithm that the reordering function uses, so we
97 // expect the order for residual blocks for each e_block to be
98 // filled in reverse.
99 expected_residual_blocks.push_back(residual_blocks[4]);
100 expected_residual_blocks.push_back(residual_blocks[1]);
101 expected_residual_blocks.push_back(residual_blocks[0]);
102 expected_residual_blocks.push_back(residual_blocks[5]);
103 expected_residual_blocks.push_back(residual_blocks[2]);
104 expected_residual_blocks.push_back(residual_blocks[3]);
105
106 Program* program = problem.mutable_program();
107 program->SetParameterOffsetsAndIndex();
108
109 std::string message;
110 EXPECT_TRUE(LexicographicallyOrderResidualBlocks(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800111 2, problem.mutable_program(), &message));
Austin Schuh70cc9552019-01-21 19:46:48 -0800112 EXPECT_EQ(residual_blocks.size(), expected_residual_blocks.size());
113 for (int i = 0; i < expected_residual_blocks.size(); ++i) {
114 EXPECT_EQ(residual_blocks[i], expected_residual_blocks[i]);
115 }
116}
117
118TEST(_, ApplyOrderingOrderingTooSmall) {
119 ProblemImpl problem;
120 double x;
121 double y;
122 double z;
123
124 problem.AddParameterBlock(&x, 1);
125 problem.AddParameterBlock(&y, 1);
126 problem.AddParameterBlock(&z, 1);
127
128 ParameterBlockOrdering linear_solver_ordering;
129 linear_solver_ordering.AddElementToGroup(&x, 0);
130 linear_solver_ordering.AddElementToGroup(&y, 1);
131
132 Program program(problem.program());
133 std::string message;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800134 EXPECT_FALSE(ApplyOrdering(
135 problem.parameter_map(), linear_solver_ordering, &program, &message));
Austin Schuh70cc9552019-01-21 19:46:48 -0800136}
137
138TEST(_, ApplyOrderingNormal) {
139 ProblemImpl problem;
140 double x;
141 double y;
142 double z;
143
144 problem.AddParameterBlock(&x, 1);
145 problem.AddParameterBlock(&y, 1);
146 problem.AddParameterBlock(&z, 1);
147
148 ParameterBlockOrdering linear_solver_ordering;
149 linear_solver_ordering.AddElementToGroup(&x, 0);
150 linear_solver_ordering.AddElementToGroup(&y, 2);
151 linear_solver_ordering.AddElementToGroup(&z, 1);
152
153 Program* program = problem.mutable_program();
154 std::string message;
155
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800156 EXPECT_TRUE(ApplyOrdering(
157 problem.parameter_map(), linear_solver_ordering, program, &message));
Austin Schuh3de38b02024-06-25 18:25:10 -0700158 const std::vector<ParameterBlock*>& parameter_blocks =
159 program->parameter_blocks();
Austin Schuh70cc9552019-01-21 19:46:48 -0800160
161 EXPECT_EQ(parameter_blocks.size(), 3);
162 EXPECT_EQ(parameter_blocks[0]->user_state(), &x);
163 EXPECT_EQ(parameter_blocks[1]->user_state(), &z);
164 EXPECT_EQ(parameter_blocks[2]->user_state(), &y);
165}
166
167#ifndef CERES_NO_SUITESPARSE
Austin Schuh3de38b02024-06-25 18:25:10 -0700168class ReorderProgramForSparseCholeskyUsingSuiteSparseTest
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800169 : public ::testing::Test {
Austin Schuh70cc9552019-01-21 19:46:48 -0800170 protected:
Austin Schuh3de38b02024-06-25 18:25:10 -0700171 void SetUp() override {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800172 problem_.AddResidualBlock(new UnaryCostFunction(), nullptr, &x_);
173 problem_.AddResidualBlock(new BinaryCostFunction(), nullptr, &z_, &x_);
174 problem_.AddResidualBlock(new BinaryCostFunction(), nullptr, &z_, &y_);
175 problem_.AddResidualBlock(new UnaryCostFunction(), nullptr, &z_);
176 problem_.AddResidualBlock(new BinaryCostFunction(), nullptr, &x_, &y_);
177 problem_.AddResidualBlock(new UnaryCostFunction(), nullptr, &y_);
Austin Schuh70cc9552019-01-21 19:46:48 -0800178 }
179
180 void ComputeAndValidateOrdering(
181 const ParameterBlockOrdering& linear_solver_ordering) {
182 Program* program = problem_.mutable_program();
Austin Schuh3de38b02024-06-25 18:25:10 -0700183 std::vector<ParameterBlock*> unordered_parameter_blocks =
Austin Schuh70cc9552019-01-21 19:46:48 -0800184 program->parameter_blocks();
185
186 std::string error;
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800187 EXPECT_TRUE(ReorderProgramForSparseCholesky(ceres::SUITE_SPARSE,
Austin Schuh3de38b02024-06-25 18:25:10 -0700188 ceres::AMD,
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800189 linear_solver_ordering,
190 0, /* use all rows */
191 program,
192 &error));
Austin Schuh3de38b02024-06-25 18:25:10 -0700193 const std::vector<ParameterBlock*>& ordered_parameter_blocks =
Austin Schuh70cc9552019-01-21 19:46:48 -0800194 program->parameter_blocks();
195 EXPECT_EQ(ordered_parameter_blocks.size(),
196 unordered_parameter_blocks.size());
197
198 EXPECT_THAT(unordered_parameter_blocks,
199 ::testing::UnorderedElementsAreArray(ordered_parameter_blocks));
200 }
201
202 ProblemImpl problem_;
203 double x_;
204 double y_;
205 double z_;
206};
207
Austin Schuh3de38b02024-06-25 18:25:10 -0700208TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest,
Austin Schuh70cc9552019-01-21 19:46:48 -0800209 EverythingInGroupZero) {
210 ParameterBlockOrdering linear_solver_ordering;
211 linear_solver_ordering.AddElementToGroup(&x_, 0);
212 linear_solver_ordering.AddElementToGroup(&y_, 0);
213 linear_solver_ordering.AddElementToGroup(&z_, 0);
214
215 ComputeAndValidateOrdering(linear_solver_ordering);
216}
217
Austin Schuh3de38b02024-06-25 18:25:10 -0700218TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest, ContiguousGroups) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800219 ParameterBlockOrdering linear_solver_ordering;
220 linear_solver_ordering.AddElementToGroup(&x_, 0);
221 linear_solver_ordering.AddElementToGroup(&y_, 1);
222 linear_solver_ordering.AddElementToGroup(&z_, 2);
223
224 ComputeAndValidateOrdering(linear_solver_ordering);
225}
226
Austin Schuh3de38b02024-06-25 18:25:10 -0700227TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest, GroupsWithGaps) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800228 ParameterBlockOrdering linear_solver_ordering;
229 linear_solver_ordering.AddElementToGroup(&x_, 0);
230 linear_solver_ordering.AddElementToGroup(&y_, 2);
231 linear_solver_ordering.AddElementToGroup(&z_, 2);
232
233 ComputeAndValidateOrdering(linear_solver_ordering);
234}
235
Austin Schuh3de38b02024-06-25 18:25:10 -0700236TEST_F(ReorderProgramForSparseCholeskyUsingSuiteSparseTest,
Austin Schuh70cc9552019-01-21 19:46:48 -0800237 NonContiguousStartingAtTwo) {
238 ParameterBlockOrdering linear_solver_ordering;
239 linear_solver_ordering.AddElementToGroup(&x_, 2);
240 linear_solver_ordering.AddElementToGroup(&y_, 4);
241 linear_solver_ordering.AddElementToGroup(&z_, 4);
242
243 ComputeAndValidateOrdering(linear_solver_ordering);
244}
245#endif // CERES_NO_SUITESPARSE
246
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800247TEST(_, ReorderResidualBlocksbyPartition) {
248 ProblemImpl problem;
249 double x;
250 double y;
251 double z;
252
253 problem.AddParameterBlock(&x, 1);
254 problem.AddParameterBlock(&y, 1);
255 problem.AddParameterBlock(&z, 1);
256
257 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &x);
258 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &x);
259 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &z, &y);
260 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &z);
261 problem.AddResidualBlock(new BinaryCostFunction(), nullptr, &x, &y);
262 problem.AddResidualBlock(new UnaryCostFunction(), nullptr, &y);
263
264 std::vector<ResidualBlockId> residual_block_ids;
265 problem.GetResidualBlocks(&residual_block_ids);
266 std::vector<ResidualBlock*> residual_blocks =
267 problem.program().residual_blocks();
Austin Schuh3de38b02024-06-25 18:25:10 -0700268 auto rng = std::mt19937{};
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800269 for (int i = 1; i < 6; ++i) {
270 std::shuffle(
271 std::begin(residual_block_ids), std::end(residual_block_ids), rng);
272 std::unordered_set<ResidualBlockId> bottom(residual_block_ids.begin(),
273 residual_block_ids.begin() + i);
274 const int start_bottom =
275 ReorderResidualBlocksByPartition(bottom, problem.mutable_program());
276 std::vector<ResidualBlock*> actual_residual_blocks =
277 problem.program().residual_blocks();
278 EXPECT_THAT(actual_residual_blocks,
279 testing::UnorderedElementsAreArray(residual_blocks));
280 EXPECT_EQ(start_bottom, residual_blocks.size() - i);
281 for (int j = start_bottom; j < residual_blocks.size(); ++j) {
282 EXPECT_THAT(bottom, ::testing::Contains(actual_residual_blocks[j]));
283 }
284 }
285}
286
Austin Schuh70cc9552019-01-21 19:46:48 -0800287} // namespace internal
288} // namespace ceres