blob: 999024ad1360ba8f7c0e701ca8330e3c23c16371 [file] [log] [blame]
Keir Mierle8ebb0732012-04-30 23:09:08 -07001// Ceres Solver - A fast non-linear least squares minimizer
2// Copyright 2010, 2011, 2012 Google Inc. All rights reserved.
3// http://code.google.com/p/ceres-solver/
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#ifndef CERES_NO_SUITESPARSE
32
33#include "ceres/visibility_based_preconditioner.h"
34
Keir Mierle8ebb0732012-04-30 23:09:08 -070035#include "Eigen/Dense"
36#include "ceres/block_random_access_dense_matrix.h"
37#include "ceres/block_random_access_sparse_matrix.h"
38#include "ceres/block_sparse_matrix.h"
39#include "ceres/casts.h"
40#include "ceres/collections_port.h"
Sameer Agarwal0beab862012-08-13 15:12:01 -070041#include "ceres/file.h"
42#include "ceres/internal/eigen.h"
43#include "ceres/internal/scoped_ptr.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070044#include "ceres/linear_least_squares_problems.h"
45#include "ceres/schur_eliminator.h"
46#include "ceres/stringprintf.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070047#include "ceres/types.h"
Sameer Agarwalc6bbecf2012-08-14 11:16:03 -070048#include "ceres/test_util.h"
Sameer Agarwal0beab862012-08-13 15:12:01 -070049#include "glog/logging.h"
50#include "gtest/gtest.h"
Keir Mierle8ebb0732012-04-30 23:09:08 -070051
Keir Mierle8ebb0732012-04-30 23:09:08 -070052namespace ceres {
53namespace internal {
54
55using testing::AssertionResult;
56using testing::AssertionSuccess;
57using testing::AssertionFailure;
58
59static const double kTolerance = 1e-12;
60
61class VisibilityBasedPreconditionerTest : public ::testing::Test {
62 public:
63 static const int kCameraSize = 9;
64
65 protected:
66 void SetUp() {
Sameer Agarwalc6bbecf2012-08-14 11:16:03 -070067 string input_file = TestFileAbsolutePath("problem-6-1384-000.lsqp");
Keir Mierle8ebb0732012-04-30 23:09:08 -070068
69 scoped_ptr<LinearLeastSquaresProblem> problem(
70 CHECK_NOTNULL(CreateLinearLeastSquaresProblemFromFile(input_file)));
71 A_.reset(down_cast<BlockSparseMatrix*>(problem->A.release()));
72 b_.reset(problem->b.release());
73 D_.reset(problem->D.release());
74
75 const CompressedRowBlockStructure* bs =
76 CHECK_NOTNULL(A_->block_structure());
77 const int num_col_blocks = bs->cols.size();
78
79 num_cols_ = A_->num_cols();
80 num_rows_ = A_->num_rows();
81 num_eliminate_blocks_ = problem->num_eliminate_blocks;
82 num_camera_blocks_ = num_col_blocks - num_eliminate_blocks_;
Sameer Agarwal0c52f1e2012-09-17 11:30:14 -070083 options_.elimination_groups.push_back(num_eliminate_blocks_);
84 options_.elimination_groups.push_back(
85 A_->block_structure()->cols.size() - num_eliminate_blocks_);
Keir Mierle8ebb0732012-04-30 23:09:08 -070086
87 vector<int> blocks(num_col_blocks - num_eliminate_blocks_, 0);
88 for (int i = num_eliminate_blocks_; i < num_col_blocks; ++i) {
89 blocks[i - num_eliminate_blocks_] = bs->cols[i].size;
90 }
91
92 // The input matrix is a real jacobian and fairly poorly
93 // conditioned. Setting D to a large constant makes the normal
94 // equations better conditioned and makes the tests below better
95 // conditioned.
96 VectorRef(D_.get(), num_cols_).setConstant(10.0);
97
98 schur_complement_.reset(new BlockRandomAccessDenseMatrix(blocks));
99 Vector rhs(schur_complement_->num_rows());
100
101 scoped_ptr<SchurEliminatorBase> eliminator;
Sameer Agarwal290b9752013-02-17 16:50:37 -0800102 LinearSolver::Options eliminator_options;
103 eliminator_options.elimination_groups = options_.elimination_groups;
104 eliminator_options.num_threads = options_.num_threads;
105
106 eliminator.reset(SchurEliminatorBase::Create(eliminator_options));
Keir Mierle8ebb0732012-04-30 23:09:08 -0700107 eliminator->Init(num_eliminate_blocks_, bs);
108 eliminator->Eliminate(A_.get(), b_.get(), D_.get(),
109 schur_complement_.get(), rhs.data());
110 }
111
112
113 AssertionResult IsSparsityStructureValid() {
114 preconditioner_->InitStorage(*A_->block_structure());
115 const HashSet<pair<int, int> >& cluster_pairs = get_cluster_pairs();
116 const vector<int>& cluster_membership = get_cluster_membership();
117
118 for (int i = 0; i < num_camera_blocks_; ++i) {
119 for (int j = i; j < num_camera_blocks_; ++j) {
120 if (cluster_pairs.count(make_pair(cluster_membership[i],
121 cluster_membership[j]))) {
122 if (!IsBlockPairInPreconditioner(i, j)) {
123 return AssertionFailure()
124 << "block pair (" << i << "," << j << "missing";
125 }
126 } else {
127 if (IsBlockPairInPreconditioner(i, j)) {
128 return AssertionFailure()
129 << "block pair (" << i << "," << j << "should not be present";
130 }
131 }
132 }
133 }
134 return AssertionSuccess();
135 }
136
137 AssertionResult PreconditionerValuesMatch() {
Sameer Agarwala9d8ef82012-05-14 02:28:05 -0700138 preconditioner_->Update(*A_, D_.get());
Keir Mierle8ebb0732012-04-30 23:09:08 -0700139 const HashSet<pair<int, int> >& cluster_pairs = get_cluster_pairs();
140 const BlockRandomAccessSparseMatrix* m = get_m();
141 Matrix preconditioner_matrix;
142 m->matrix()->ToDenseMatrix(&preconditioner_matrix);
143 ConstMatrixRef full_schur_complement(schur_complement_->values(),
144 m->num_rows(),
145 m->num_rows());
146 const int num_clusters = get_num_clusters();
147 const int kDiagonalBlockSize =
148 kCameraSize * num_camera_blocks_ / num_clusters;
149
150 for (int i = 0; i < num_clusters; ++i) {
151 for (int j = i; j < num_clusters; ++j) {
152 double diff = 0.0;
153 if (cluster_pairs.count(make_pair(i, j))) {
154 diff =
155 (preconditioner_matrix.block(kDiagonalBlockSize * i,
156 kDiagonalBlockSize * j,
157 kDiagonalBlockSize,
158 kDiagonalBlockSize) -
159 full_schur_complement.block(kDiagonalBlockSize * i,
160 kDiagonalBlockSize * j,
161 kDiagonalBlockSize,
162 kDiagonalBlockSize)).norm();
163 } else {
164 diff = preconditioner_matrix.block(kDiagonalBlockSize * i,
165 kDiagonalBlockSize * j,
166 kDiagonalBlockSize,
167 kDiagonalBlockSize).norm();
168 }
169 if (diff > kTolerance) {
170 return AssertionFailure()
171 << "Preconditioner block " << i << " " << j << " differs "
172 << "from expected value by " << diff;
173 }
174 }
175 }
176 return AssertionSuccess();
177 }
178
179 // Accessors
180 int get_num_blocks() { return preconditioner_->num_blocks_; }
181
182 int get_num_clusters() { return preconditioner_->num_clusters_; }
183 int* get_mutable_num_clusters() { return &preconditioner_->num_clusters_; }
184
185 const vector<int>& get_block_size() {
186 return preconditioner_->block_size_; }
187
188 vector<int>* get_mutable_block_size() {
189 return &preconditioner_->block_size_; }
190
191 const vector<int>& get_cluster_membership() {
192 return preconditioner_->cluster_membership_;
193 }
194
195 vector<int>* get_mutable_cluster_membership() {
196 return &preconditioner_->cluster_membership_;
197 }
198
199 const set<pair<int, int> >& get_block_pairs() {
200 return preconditioner_->block_pairs_;
201 }
202
203 set<pair<int, int> >* get_mutable_block_pairs() {
204 return &preconditioner_->block_pairs_;
205 }
206
207 const HashSet<pair<int, int> >& get_cluster_pairs() {
208 return preconditioner_->cluster_pairs_;
209 }
210
211 HashSet<pair<int, int> >* get_mutable_cluster_pairs() {
212 return &preconditioner_->cluster_pairs_;
213 }
214
215 bool IsBlockPairInPreconditioner(const int block1, const int block2) {
216 return preconditioner_->IsBlockPairInPreconditioner(block1, block2);
217 }
218
219 bool IsBlockPairOffDiagonal(const int block1, const int block2) {
220 return preconditioner_->IsBlockPairOffDiagonal(block1, block2);
221 }
222
223 const BlockRandomAccessSparseMatrix* get_m() {
224 return preconditioner_->m_.get();
225 }
226
227 int num_rows_;
228 int num_cols_;
229 int num_eliminate_blocks_;
230 int num_camera_blocks_;
231
232 scoped_ptr<BlockSparseMatrix> A_;
233 scoped_array<double> b_;
234 scoped_array<double> D_;
235
Sameer Agarwal290b9752013-02-17 16:50:37 -0800236 Preconditioner::Options options_;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700237 scoped_ptr<VisibilityBasedPreconditioner> preconditioner_;
238 scoped_ptr<BlockRandomAccessDenseMatrix> schur_complement_;
239};
240
Sameer Agarwaldd2b17d2012-08-16 19:34:57 -0700241#ifndef CERES_NO_PROTOCOL_BUFFERS
Keir Mierle8ebb0732012-04-30 23:09:08 -0700242TEST_F(VisibilityBasedPreconditionerTest, OneClusterClusterJacobi) {
Sameer Agarwal290b9752013-02-17 16:50:37 -0800243 options_.type = CLUSTER_JACOBI;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700244 preconditioner_.reset(
245 new VisibilityBasedPreconditioner(*A_->block_structure(), options_));
246
247 // Override the clustering to be a single clustering containing all
248 // the cameras.
249 vector<int>& cluster_membership = *get_mutable_cluster_membership();
250 for (int i = 0; i < num_camera_blocks_; ++i) {
251 cluster_membership[i] = 0;
252 }
253
254 *get_mutable_num_clusters() = 1;
255
256 HashSet<pair<int, int> >& cluster_pairs = *get_mutable_cluster_pairs();
257 cluster_pairs.clear();
258 cluster_pairs.insert(make_pair(0, 0));
259
260 EXPECT_TRUE(IsSparsityStructureValid());
261 EXPECT_TRUE(PreconditionerValuesMatch());
262
263 // Multiplication by the inverse of the preconditioner.
264 const int num_rows = schur_complement_->num_rows();
265 ConstMatrixRef full_schur_complement(schur_complement_->values(),
266 num_rows,
267 num_rows);
268 Vector x(num_rows);
269 Vector y(num_rows);
270 Vector z(num_rows);
271
272 for (int i = 0; i < num_rows; ++i) {
273 x.setZero();
274 y.setZero();
275 z.setZero();
276 x[i] = 1.0;
277 preconditioner_->RightMultiply(x.data(), y.data());
278 z = full_schur_complement
279 .selfadjointView<Eigen::Upper>()
280 .ldlt().solve(x);
281 double max_relative_difference =
282 ((y - z).array() / z.array()).matrix().lpNorm<Eigen::Infinity>();
283 EXPECT_NEAR(max_relative_difference, 0.0, kTolerance);
284 }
285}
286
287
288
289TEST_F(VisibilityBasedPreconditionerTest, ClusterJacobi) {
Sameer Agarwal290b9752013-02-17 16:50:37 -0800290 options_.type = CLUSTER_JACOBI;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700291 preconditioner_.reset(
292 new VisibilityBasedPreconditioner(*A_->block_structure(), options_));
293
294 // Override the clustering to be equal number of cameras.
295 vector<int>& cluster_membership = *get_mutable_cluster_membership();
296 cluster_membership.resize(num_camera_blocks_);
297 static const int kNumClusters = 3;
298
299 for (int i = 0; i < num_camera_blocks_; ++i) {
300 cluster_membership[i] = (i * kNumClusters) / num_camera_blocks_;
301 }
302 *get_mutable_num_clusters() = kNumClusters;
303
304 HashSet<pair<int, int> >& cluster_pairs = *get_mutable_cluster_pairs();
305 cluster_pairs.clear();
306 for (int i = 0; i < kNumClusters; ++i) {
307 cluster_pairs.insert(make_pair(i, i));
308 }
309
310 EXPECT_TRUE(IsSparsityStructureValid());
311 EXPECT_TRUE(PreconditionerValuesMatch());
312}
313
314
315TEST_F(VisibilityBasedPreconditionerTest, ClusterTridiagonal) {
Sameer Agarwal290b9752013-02-17 16:50:37 -0800316 options_.type = CLUSTER_TRIDIAGONAL;
Keir Mierle8ebb0732012-04-30 23:09:08 -0700317 preconditioner_.reset(
318 new VisibilityBasedPreconditioner(*A_->block_structure(), options_));
319 static const int kNumClusters = 3;
320
321 // Override the clustering to be 3 clusters.
322 vector<int>& cluster_membership = *get_mutable_cluster_membership();
323 cluster_membership.resize(num_camera_blocks_);
324 for (int i = 0; i < num_camera_blocks_; ++i) {
325 cluster_membership[i] = (i * kNumClusters) / num_camera_blocks_;
326 }
327 *get_mutable_num_clusters() = kNumClusters;
328
329 // Spanning forest has structure 0-1 2
330 HashSet<pair<int, int> >& cluster_pairs = *get_mutable_cluster_pairs();
331 cluster_pairs.clear();
332 for (int i = 0; i < kNumClusters; ++i) {
333 cluster_pairs.insert(make_pair(i, i));
334 }
335 cluster_pairs.insert(make_pair(0, 1));
336
337 EXPECT_TRUE(IsSparsityStructureValid());
338 EXPECT_TRUE(PreconditionerValuesMatch());
339}
Sameer Agarwaldd2b17d2012-08-16 19:34:57 -0700340#endif // CERES_NO_PROTOCOL_BUFFERS
Keir Mierle8ebb0732012-04-30 23:09:08 -0700341
342} // namespace internal
343} // namespace ceres
344
345#endif // CERES_NO_SUITESPARSE