blob: 3addba6216b4f77d90e17d5117731772f81d787b [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/partitioned_matrix_view.h"
32
33#include <memory>
Austin Schuh3de38b02024-06-25 18:25:10 -070034#include <random>
35#include <sstream>
36#include <string>
Austin Schuh70cc9552019-01-21 19:46:48 -080037#include <vector>
Austin Schuh1d1e6ea2020-12-23 21:56:30 -080038
Austin Schuh70cc9552019-01-21 19:46:48 -080039#include "ceres/block_structure.h"
40#include "ceres/casts.h"
41#include "ceres/internal/eigen.h"
42#include "ceres/linear_least_squares_problems.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080043#include "ceres/sparse_matrix.h"
44#include "glog/logging.h"
45#include "gtest/gtest.h"
46
47namespace ceres {
48namespace internal {
49
50const double kEpsilon = 1e-14;
51
Austin Schuh3de38b02024-06-25 18:25:10 -070052// Param = <problem_id, num_threads>
53using Param = ::testing::tuple<int, int>;
Austin Schuh70cc9552019-01-21 19:46:48 -080054
Austin Schuh3de38b02024-06-25 18:25:10 -070055static std::string ParamInfoToString(testing::TestParamInfo<Param> info) {
56 Param param = info.param;
57 std::stringstream ss;
58 ss << ::testing::get<0>(param) << "_" << ::testing::get<1>(param);
59 return ss.str();
Austin Schuh70cc9552019-01-21 19:46:48 -080060}
61
Austin Schuh3de38b02024-06-25 18:25:10 -070062class PartitionedMatrixViewTest : public ::testing::TestWithParam<Param> {
63 protected:
64 void SetUp() final {
65 const int problem_id = ::testing::get<0>(GetParam());
66 const int num_threads = ::testing::get<1>(GetParam());
67 auto problem = CreateLinearLeastSquaresProblemFromId(problem_id);
68 CHECK(problem != nullptr);
69 A_ = std::move(problem->A);
70 auto block_sparse = down_cast<BlockSparseMatrix*>(A_.get());
71
72 options_.num_threads = num_threads;
73 options_.context = &context_;
74 options_.elimination_groups.push_back(problem->num_eliminate_blocks);
75 pmv_ = PartitionedMatrixViewBase::Create(options_, *block_sparse);
76
77 LinearSolver::Options options_single_threaded = options_;
78 options_single_threaded.num_threads = 1;
79 pmv_single_threaded_ =
80 PartitionedMatrixViewBase::Create(options_, *block_sparse);
81
82 EXPECT_EQ(pmv_->num_col_blocks_e(), problem->num_eliminate_blocks);
83 EXPECT_EQ(pmv_->num_col_blocks_f(),
84 block_sparse->block_structure()->cols.size() -
85 problem->num_eliminate_blocks);
86 EXPECT_EQ(pmv_->num_cols(), A_->num_cols());
87 EXPECT_EQ(pmv_->num_rows(), A_->num_rows());
88 }
89
90 double RandDouble() { return distribution_(prng_); }
91
92 LinearSolver::Options options_;
93 ContextImpl context_;
94 std::unique_ptr<LinearLeastSquaresProblem> problem_;
95 std::unique_ptr<SparseMatrix> A_;
96 std::unique_ptr<PartitionedMatrixViewBase> pmv_;
97 std::unique_ptr<PartitionedMatrixViewBase> pmv_single_threaded_;
98 std::mt19937 prng_;
99 std::uniform_real_distribution<double> distribution_ =
100 std::uniform_real_distribution<double>(0.0, 1.0);
101};
102
103TEST_P(PartitionedMatrixViewTest, RightMultiplyAndAccumulateE) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800104 Vector x1(pmv_->num_cols_e());
105 Vector x2(pmv_->num_cols());
106 x2.setZero();
107
108 for (int i = 0; i < pmv_->num_cols_e(); ++i) {
109 x1(i) = x2(i) = RandDouble();
110 }
111
Austin Schuh3de38b02024-06-25 18:25:10 -0700112 Vector expected = Vector::Zero(pmv_->num_rows());
113 A_->RightMultiplyAndAccumulate(x2.data(), expected.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800114
Austin Schuh3de38b02024-06-25 18:25:10 -0700115 Vector actual = Vector::Zero(pmv_->num_rows());
116 pmv_->RightMultiplyAndAccumulateE(x1.data(), actual.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800117
118 for (int i = 0; i < pmv_->num_rows(); ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700119 EXPECT_NEAR(actual(i), expected(i), kEpsilon);
Austin Schuh70cc9552019-01-21 19:46:48 -0800120 }
121}
122
Austin Schuh3de38b02024-06-25 18:25:10 -0700123TEST_P(PartitionedMatrixViewTest, RightMultiplyAndAccumulateF) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800124 Vector x1(pmv_->num_cols_f());
Austin Schuh3de38b02024-06-25 18:25:10 -0700125 Vector x2(pmv_->num_cols());
126 x2.setZero();
Austin Schuh70cc9552019-01-21 19:46:48 -0800127
128 for (int i = 0; i < pmv_->num_cols_f(); ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700129 x1(i) = x2(i + pmv_->num_cols_e()) = RandDouble();
Austin Schuh70cc9552019-01-21 19:46:48 -0800130 }
131
Austin Schuh3de38b02024-06-25 18:25:10 -0700132 Vector actual = Vector::Zero(pmv_->num_rows());
133 pmv_->RightMultiplyAndAccumulateF(x1.data(), actual.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800134
Austin Schuh3de38b02024-06-25 18:25:10 -0700135 Vector expected = Vector::Zero(pmv_->num_rows());
136 A_->RightMultiplyAndAccumulate(x2.data(), expected.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800137
138 for (int i = 0; i < pmv_->num_rows(); ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700139 EXPECT_NEAR(actual(i), expected(i), kEpsilon);
Austin Schuh70cc9552019-01-21 19:46:48 -0800140 }
141}
142
Austin Schuh3de38b02024-06-25 18:25:10 -0700143TEST_P(PartitionedMatrixViewTest, LeftMultiplyAndAccumulate) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800144 Vector x = Vector::Zero(pmv_->num_rows());
145 for (int i = 0; i < pmv_->num_rows(); ++i) {
146 x(i) = RandDouble();
147 }
Austin Schuh3de38b02024-06-25 18:25:10 -0700148 Vector x_pre = x;
Austin Schuh70cc9552019-01-21 19:46:48 -0800149
Austin Schuh3de38b02024-06-25 18:25:10 -0700150 Vector expected = Vector::Zero(pmv_->num_cols());
151 Vector e_actual = Vector::Zero(pmv_->num_cols_e());
152 Vector f_actual = Vector::Zero(pmv_->num_cols_f());
Austin Schuh70cc9552019-01-21 19:46:48 -0800153
Austin Schuh3de38b02024-06-25 18:25:10 -0700154 A_->LeftMultiplyAndAccumulate(x.data(), expected.data());
155 pmv_->LeftMultiplyAndAccumulateE(x.data(), e_actual.data());
156 pmv_->LeftMultiplyAndAccumulateF(x.data(), f_actual.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800157
158 for (int i = 0; i < pmv_->num_cols(); ++i) {
Austin Schuh3de38b02024-06-25 18:25:10 -0700159 EXPECT_NEAR(expected(i),
160 (i < pmv_->num_cols_e()) ? e_actual(i)
161 : f_actual(i - pmv_->num_cols_e()),
Austin Schuh70cc9552019-01-21 19:46:48 -0800162 kEpsilon);
163 }
164}
165
Austin Schuh3de38b02024-06-25 18:25:10 -0700166TEST_P(PartitionedMatrixViewTest, BlockDiagonalFtF) {
167 std::unique_ptr<BlockSparseMatrix> block_diagonal_ff(
168 pmv_->CreateBlockDiagonalFtF());
169 const auto bs_diagonal = block_diagonal_ff->block_structure();
170 const int num_rows = pmv_->num_rows();
171 const int num_cols_f = pmv_->num_cols_f();
172 const int num_cols_e = pmv_->num_cols_e();
173 const int num_col_blocks_f = pmv_->num_col_blocks_f();
174 const int num_col_blocks_e = pmv_->num_col_blocks_e();
175
176 CHECK_EQ(block_diagonal_ff->num_rows(), num_cols_f);
177 CHECK_EQ(block_diagonal_ff->num_cols(), num_cols_f);
178
179 EXPECT_EQ(bs_diagonal->cols.size(), num_col_blocks_f);
180 EXPECT_EQ(bs_diagonal->rows.size(), num_col_blocks_f);
181
182 Matrix EF;
183 A_->ToDenseMatrix(&EF);
184 const auto F = EF.topRightCorner(num_rows, num_cols_f);
185
186 Matrix expected_FtF = F.transpose() * F;
187 Matrix actual_FtF;
188 block_diagonal_ff->ToDenseMatrix(&actual_FtF);
189
190 // FtF might be not block-diagonal
191 auto bs = down_cast<BlockSparseMatrix*>(A_.get())->block_structure();
192 for (int i = 0; i < num_col_blocks_f; ++i) {
193 const auto col_block_f = bs->cols[num_col_blocks_e + i];
194 const int block_size = col_block_f.size;
195 const int block_pos = col_block_f.position - num_cols_e;
196 const auto cell_expected =
197 expected_FtF.block(block_pos, block_pos, block_size, block_size);
198 auto cell_actual =
199 actual_FtF.block(block_pos, block_pos, block_size, block_size);
200 cell_actual -= cell_expected;
201 EXPECT_NEAR(cell_actual.norm(), 0., kEpsilon);
202 }
203 // There should be nothing remaining outside block-diagonal
204 EXPECT_NEAR(actual_FtF.norm(), 0., kEpsilon);
205}
206
207TEST_P(PartitionedMatrixViewTest, BlockDiagonalEtE) {
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800208 std::unique_ptr<BlockSparseMatrix> block_diagonal_ee(
209 pmv_->CreateBlockDiagonalEtE());
210 const CompressedRowBlockStructure* bs = block_diagonal_ee->block_structure();
Austin Schuh3de38b02024-06-25 18:25:10 -0700211 const int num_rows = pmv_->num_rows();
212 const int num_cols_e = pmv_->num_cols_e();
213 const int num_col_blocks_e = pmv_->num_col_blocks_e();
Austin Schuh70cc9552019-01-21 19:46:48 -0800214
Austin Schuh3de38b02024-06-25 18:25:10 -0700215 CHECK_EQ(block_diagonal_ee->num_rows(), num_cols_e);
216 CHECK_EQ(block_diagonal_ee->num_cols(), num_cols_e);
Austin Schuh70cc9552019-01-21 19:46:48 -0800217
Austin Schuh3de38b02024-06-25 18:25:10 -0700218 EXPECT_EQ(bs->cols.size(), num_col_blocks_e);
219 EXPECT_EQ(bs->rows.size(), num_col_blocks_e);
220
221 Matrix EF;
222 A_->ToDenseMatrix(&EF);
223 const auto E = EF.topLeftCorner(num_rows, num_cols_e);
224
225 Matrix expected_EtE = E.transpose() * E;
226 Matrix actual_EtE;
227 block_diagonal_ee->ToDenseMatrix(&actual_EtE);
228
229 EXPECT_NEAR((expected_EtE - actual_EtE).norm(), 0., kEpsilon);
Austin Schuh70cc9552019-01-21 19:46:48 -0800230}
231
Austin Schuh3de38b02024-06-25 18:25:10 -0700232TEST_P(PartitionedMatrixViewTest, UpdateBlockDiagonalEtE) {
233 std::unique_ptr<BlockSparseMatrix> block_diagonal_ete(
234 pmv_->CreateBlockDiagonalEtE());
235 const int num_cols = pmv_->num_cols_e();
236
237 Matrix multi_threaded(num_cols, num_cols);
238 pmv_->UpdateBlockDiagonalEtE(block_diagonal_ete.get());
239 block_diagonal_ete->ToDenseMatrix(&multi_threaded);
240
241 Matrix single_threaded(num_cols, num_cols);
242 pmv_single_threaded_->UpdateBlockDiagonalEtE(block_diagonal_ete.get());
243 block_diagonal_ete->ToDenseMatrix(&single_threaded);
244
245 EXPECT_NEAR((multi_threaded - single_threaded).norm(), 0., kEpsilon);
246}
247
248TEST_P(PartitionedMatrixViewTest, UpdateBlockDiagonalFtF) {
249 std::unique_ptr<BlockSparseMatrix> block_diagonal_ftf(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800250 pmv_->CreateBlockDiagonalFtF());
Austin Schuh3de38b02024-06-25 18:25:10 -0700251 const int num_cols = pmv_->num_cols_f();
Austin Schuh70cc9552019-01-21 19:46:48 -0800252
Austin Schuh3de38b02024-06-25 18:25:10 -0700253 Matrix multi_threaded(num_cols, num_cols);
254 pmv_->UpdateBlockDiagonalFtF(block_diagonal_ftf.get());
255 block_diagonal_ftf->ToDenseMatrix(&multi_threaded);
256
257 Matrix single_threaded(num_cols, num_cols);
258 pmv_single_threaded_->UpdateBlockDiagonalFtF(block_diagonal_ftf.get());
259 block_diagonal_ftf->ToDenseMatrix(&single_threaded);
260
261 EXPECT_NEAR((multi_threaded - single_threaded).norm(), 0., kEpsilon);
Austin Schuh70cc9552019-01-21 19:46:48 -0800262}
263
Austin Schuh3de38b02024-06-25 18:25:10 -0700264INSTANTIATE_TEST_SUITE_P(
265 ParallelProducts,
266 PartitionedMatrixViewTest,
267 ::testing::Combine(::testing::Values(2, 4, 6),
268 ::testing::Values(1, 2, 3, 4, 5, 6, 7, 8)),
269 ParamInfoToString);
270
Austin Schuh70cc9552019-01-21 19:46:48 -0800271} // namespace internal
272} // namespace ceres