blob: a63352916f43b1ff5b1fc47f72f3e1ca8ac091e0 [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/implicit_schur_complement.h"
32
33#include "Eigen/Dense"
34#include "ceres/block_sparse_matrix.h"
35#include "ceres/block_structure.h"
36#include "ceres/internal/eigen.h"
37#include "ceres/linear_solver.h"
Austin Schuh3de38b02024-06-25 18:25:10 -070038#include "ceres/parallel_for.h"
39#include "ceres/parallel_vector_ops.h"
Austin Schuh70cc9552019-01-21 19:46:48 -080040#include "ceres/types.h"
41#include "glog/logging.h"
42
Austin Schuh3de38b02024-06-25 18:25:10 -070043namespace ceres::internal {
Austin Schuh70cc9552019-01-21 19:46:48 -080044
45ImplicitSchurComplement::ImplicitSchurComplement(
46 const LinearSolver::Options& options)
Austin Schuh3de38b02024-06-25 18:25:10 -070047 : options_(options) {}
Austin Schuh70cc9552019-01-21 19:46:48 -080048
49void ImplicitSchurComplement::Init(const BlockSparseMatrix& A,
50 const double* D,
51 const double* b) {
52 // Since initialization is reasonably heavy, perhaps we can save on
53 // constructing a new object everytime.
Austin Schuh3de38b02024-06-25 18:25:10 -070054 if (A_ == nullptr) {
55 A_ = PartitionedMatrixViewBase::Create(options_, A);
Austin Schuh70cc9552019-01-21 19:46:48 -080056 }
57
58 D_ = D;
59 b_ = b;
60
Austin Schuh3de38b02024-06-25 18:25:10 -070061 compute_ftf_inverse_ =
62 options_.use_spse_initialization ||
63 options_.preconditioner_type == JACOBI ||
64 options_.preconditioner_type == SCHUR_POWER_SERIES_EXPANSION;
65
Austin Schuh70cc9552019-01-21 19:46:48 -080066 // Initialize temporary storage and compute the block diagonals of
67 // E'E and F'E.
Austin Schuh3de38b02024-06-25 18:25:10 -070068 if (block_diagonal_EtE_inverse_ == nullptr) {
69 block_diagonal_EtE_inverse_ = A_->CreateBlockDiagonalEtE();
70 if (compute_ftf_inverse_) {
71 block_diagonal_FtF_inverse_ = A_->CreateBlockDiagonalFtF();
Austin Schuh70cc9552019-01-21 19:46:48 -080072 }
73 rhs_.resize(A_->num_cols_f());
74 rhs_.setZero();
75 tmp_rows_.resize(A_->num_rows());
76 tmp_e_cols_.resize(A_->num_cols_e());
77 tmp_e_cols_2_.resize(A_->num_cols_e());
78 tmp_f_cols_.resize(A_->num_cols_f());
79 } else {
80 A_->UpdateBlockDiagonalEtE(block_diagonal_EtE_inverse_.get());
Austin Schuh3de38b02024-06-25 18:25:10 -070081 if (compute_ftf_inverse_) {
Austin Schuh70cc9552019-01-21 19:46:48 -080082 A_->UpdateBlockDiagonalFtF(block_diagonal_FtF_inverse_.get());
83 }
84 }
85
86 // The block diagonals of the augmented linear system contain
87 // contributions from the diagonal D if it is non-null. Add that to
88 // the block diagonals and invert them.
89 AddDiagonalAndInvert(D_, block_diagonal_EtE_inverse_.get());
Austin Schuh3de38b02024-06-25 18:25:10 -070090 if (compute_ftf_inverse_) {
91 AddDiagonalAndInvert((D_ == nullptr) ? nullptr : D_ + A_->num_cols_e(),
Austin Schuh70cc9552019-01-21 19:46:48 -080092 block_diagonal_FtF_inverse_.get());
93 }
94
95 // Compute the RHS of the Schur complement system.
96 UpdateRhs();
97}
98
99// Evaluate the product
100//
101// Sx = [F'F - F'E (E'E)^-1 E'F]x
102//
103// By breaking it down into individual matrix vector products
104// involving the matrices E and F. This is implemented using a
105// PartitionedMatrixView of the input matrix A.
Austin Schuh3de38b02024-06-25 18:25:10 -0700106void ImplicitSchurComplement::RightMultiplyAndAccumulate(const double* x,
107 double* y) const {
Austin Schuh70cc9552019-01-21 19:46:48 -0800108 // y1 = F x
Austin Schuh3de38b02024-06-25 18:25:10 -0700109 ParallelSetZero(options_.context, options_.num_threads, tmp_rows_);
110 A_->RightMultiplyAndAccumulateF(x, tmp_rows_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800111
112 // y2 = E' y1
Austin Schuh3de38b02024-06-25 18:25:10 -0700113 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_);
114 A_->LeftMultiplyAndAccumulateE(tmp_rows_.data(), tmp_e_cols_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800115
116 // y3 = -(E'E)^-1 y2
Austin Schuh3de38b02024-06-25 18:25:10 -0700117 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_2_);
118 block_diagonal_EtE_inverse_->RightMultiplyAndAccumulate(tmp_e_cols_.data(),
119 tmp_e_cols_2_.data(),
120 options_.context,
121 options_.num_threads);
122
123 ParallelAssign(
124 options_.context, options_.num_threads, tmp_e_cols_2_, -tmp_e_cols_2_);
Austin Schuh70cc9552019-01-21 19:46:48 -0800125
126 // y1 = y1 + E y3
Austin Schuh3de38b02024-06-25 18:25:10 -0700127 A_->RightMultiplyAndAccumulateE(tmp_e_cols_2_.data(), tmp_rows_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800128
129 // y5 = D * x
Austin Schuh3de38b02024-06-25 18:25:10 -0700130 if (D_ != nullptr) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800131 ConstVectorRef Dref(D_ + A_->num_cols_e(), num_cols());
Austin Schuh3de38b02024-06-25 18:25:10 -0700132 VectorRef y_cols(y, num_cols());
133 ParallelAssign(
134 options_.context,
135 options_.num_threads,
136 y_cols,
137 (Dref.array().square() * ConstVectorRef(x, num_cols()).array()));
Austin Schuh70cc9552019-01-21 19:46:48 -0800138 } else {
Austin Schuh3de38b02024-06-25 18:25:10 -0700139 ParallelSetZero(options_.context, options_.num_threads, y, num_cols());
Austin Schuh70cc9552019-01-21 19:46:48 -0800140 }
141
142 // y = y5 + F' y1
Austin Schuh3de38b02024-06-25 18:25:10 -0700143 A_->LeftMultiplyAndAccumulateF(tmp_rows_.data(), y);
144}
145
146void ImplicitSchurComplement::InversePowerSeriesOperatorRightMultiplyAccumulate(
147 const double* x, double* y) const {
148 CHECK(compute_ftf_inverse_);
149 // y1 = F x
150 ParallelSetZero(options_.context, options_.num_threads, tmp_rows_);
151 A_->RightMultiplyAndAccumulateF(x, tmp_rows_.data());
152
153 // y2 = E' y1
154 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_);
155 A_->LeftMultiplyAndAccumulateE(tmp_rows_.data(), tmp_e_cols_.data());
156
157 // y3 = (E'E)^-1 y2
158 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_2_);
159 block_diagonal_EtE_inverse_->RightMultiplyAndAccumulate(tmp_e_cols_.data(),
160 tmp_e_cols_2_.data(),
161 options_.context,
162 options_.num_threads);
163 // y1 = E y3
164 ParallelSetZero(options_.context, options_.num_threads, tmp_rows_);
165 A_->RightMultiplyAndAccumulateE(tmp_e_cols_2_.data(), tmp_rows_.data());
166
167 // y4 = F' y1
168 ParallelSetZero(options_.context, options_.num_threads, tmp_f_cols_);
169 A_->LeftMultiplyAndAccumulateF(tmp_rows_.data(), tmp_f_cols_.data());
170
171 // y += (F'F)^-1 y4
172 block_diagonal_FtF_inverse_->RightMultiplyAndAccumulate(
173 tmp_f_cols_.data(), y, options_.context, options_.num_threads);
Austin Schuh70cc9552019-01-21 19:46:48 -0800174}
175
176// Given a block diagonal matrix and an optional array of diagonal
177// entries D, add them to the diagonal of the matrix and compute the
178// inverse of each diagonal block.
179void ImplicitSchurComplement::AddDiagonalAndInvert(
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800180 const double* D, BlockSparseMatrix* block_diagonal) {
Austin Schuh70cc9552019-01-21 19:46:48 -0800181 const CompressedRowBlockStructure* block_diagonal_structure =
182 block_diagonal->block_structure();
Austin Schuh3de38b02024-06-25 18:25:10 -0700183 ParallelFor(options_.context,
184 0,
185 block_diagonal_structure->rows.size(),
186 options_.num_threads,
187 [block_diagonal_structure, D, block_diagonal](int row_block_id) {
188 auto& row = block_diagonal_structure->rows[row_block_id];
189 const int row_block_pos = row.block.position;
190 const int row_block_size = row.block.size;
191 const Cell& cell = row.cells[0];
192 MatrixRef m(block_diagonal->mutable_values() + cell.position,
193 row_block_size,
194 row_block_size);
Austin Schuh70cc9552019-01-21 19:46:48 -0800195
Austin Schuh3de38b02024-06-25 18:25:10 -0700196 if (D != nullptr) {
197 ConstVectorRef d(D + row_block_pos, row_block_size);
198 m += d.array().square().matrix().asDiagonal();
199 }
Austin Schuh70cc9552019-01-21 19:46:48 -0800200
Austin Schuh3de38b02024-06-25 18:25:10 -0700201 m = m.selfadjointView<Eigen::Upper>().llt().solve(
202 Matrix::Identity(row_block_size, row_block_size));
203 });
Austin Schuh70cc9552019-01-21 19:46:48 -0800204}
205
Austin Schuh3de38b02024-06-25 18:25:10 -0700206// Similar to RightMultiplyAndAccumulate, use the block structure of the matrix
207// A to compute y = (E'E)^-1 (E'b - E'F x).
Austin Schuh70cc9552019-01-21 19:46:48 -0800208void ImplicitSchurComplement::BackSubstitute(const double* x, double* y) {
209 const int num_cols_e = A_->num_cols_e();
210 const int num_cols_f = A_->num_cols_f();
Austin Schuh1d1e6ea2020-12-23 21:56:30 -0800211 const int num_cols = A_->num_cols();
Austin Schuh70cc9552019-01-21 19:46:48 -0800212 const int num_rows = A_->num_rows();
213
214 // y1 = F x
Austin Schuh3de38b02024-06-25 18:25:10 -0700215 ParallelSetZero(options_.context, options_.num_threads, tmp_rows_);
216 A_->RightMultiplyAndAccumulateF(x, tmp_rows_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800217
218 // y2 = b - y1
Austin Schuh3de38b02024-06-25 18:25:10 -0700219 ParallelAssign(options_.context,
220 options_.num_threads,
221 tmp_rows_,
222 ConstVectorRef(b_, num_rows) - tmp_rows_);
Austin Schuh70cc9552019-01-21 19:46:48 -0800223
224 // y3 = E' y2
Austin Schuh3de38b02024-06-25 18:25:10 -0700225 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_);
226 A_->LeftMultiplyAndAccumulateE(tmp_rows_.data(), tmp_e_cols_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800227
228 // y = (E'E)^-1 y3
Austin Schuh3de38b02024-06-25 18:25:10 -0700229 ParallelSetZero(options_.context, options_.num_threads, y, num_cols);
230 block_diagonal_EtE_inverse_->RightMultiplyAndAccumulate(
231 tmp_e_cols_.data(), y, options_.context, options_.num_threads);
Austin Schuh70cc9552019-01-21 19:46:48 -0800232
233 // The full solution vector y has two blocks. The first block of
234 // variables corresponds to the eliminated variables, which we just
235 // computed via back substitution. The second block of variables
236 // corresponds to the Schur complement system, so we just copy those
237 // values from the solution to the Schur complement.
Austin Schuh3de38b02024-06-25 18:25:10 -0700238 VectorRef y_cols_f(y + num_cols_e, num_cols_f);
239 ParallelAssign(options_.context,
240 options_.num_threads,
241 y_cols_f,
242 ConstVectorRef(x, num_cols_f));
Austin Schuh70cc9552019-01-21 19:46:48 -0800243}
244
245// Compute the RHS of the Schur complement system.
246//
247// rhs = F'b - F'E (E'E)^-1 E'b
248//
249// Like BackSubstitute, we use the block structure of A to implement
250// this using a series of matrix vector products.
251void ImplicitSchurComplement::UpdateRhs() {
252 // y1 = E'b
Austin Schuh3de38b02024-06-25 18:25:10 -0700253 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_);
254 A_->LeftMultiplyAndAccumulateE(b_, tmp_e_cols_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800255
256 // y2 = (E'E)^-1 y1
Austin Schuh3de38b02024-06-25 18:25:10 -0700257 ParallelSetZero(options_.context, options_.num_threads, tmp_e_cols_2_);
258 block_diagonal_EtE_inverse_->RightMultiplyAndAccumulate(tmp_e_cols_.data(),
259 tmp_e_cols_2_.data(),
260 options_.context,
261 options_.num_threads);
Austin Schuh70cc9552019-01-21 19:46:48 -0800262
263 // y3 = E y2
Austin Schuh3de38b02024-06-25 18:25:10 -0700264 ParallelSetZero(options_.context, options_.num_threads, tmp_rows_);
265 A_->RightMultiplyAndAccumulateE(tmp_e_cols_2_.data(), tmp_rows_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800266
267 // y3 = b - y3
Austin Schuh3de38b02024-06-25 18:25:10 -0700268 ParallelAssign(options_.context,
269 options_.num_threads,
270 tmp_rows_,
271 ConstVectorRef(b_, A_->num_rows()) - tmp_rows_);
Austin Schuh70cc9552019-01-21 19:46:48 -0800272
273 // rhs = F' y3
Austin Schuh3de38b02024-06-25 18:25:10 -0700274 ParallelSetZero(options_.context, options_.num_threads, rhs_);
275 A_->LeftMultiplyAndAccumulateF(tmp_rows_.data(), rhs_.data());
Austin Schuh70cc9552019-01-21 19:46:48 -0800276}
277
Austin Schuh3de38b02024-06-25 18:25:10 -0700278} // namespace ceres::internal