| // Ceres Solver - A fast non-linear least squares minimizer | |
| // Copyright 2010, 2011, 2012 Google Inc. All rights reserved. | |
| // http://code.google.com/p/ceres-solver/ | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // * Redistributions of source code must retain the above copyright notice, | |
| // this list of conditions and the following disclaimer. | |
| // * Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // * Neither the name of Google Inc. nor the names of its contributors may be | |
| // used to endorse or promote products derived from this software without | |
| // specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
| // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| // POSSIBILITY OF SUCH DAMAGE. | |
| // | |
| // Author: sameeragarwal@google.com (Sameer Agarwal) | |
| #include "ceres/implicit_schur_complement.h" | |
| #include "Eigen/Dense" | |
| #include "ceres/block_sparse_matrix.h" | |
| #include "ceres/block_structure.h" | |
| #include "ceres/internal/eigen.h" | |
| #include "ceres/internal/scoped_ptr.h" | |
| #include "ceres/linear_solver.h" | |
| #include "ceres/types.h" | |
| #include "glog/logging.h" | |
| namespace ceres { | |
| namespace internal { | |
| ImplicitSchurComplement::ImplicitSchurComplement( | |
| const LinearSolver::Options& options) | |
| : options_(options), | |
| D_(NULL), | |
| b_(NULL) { | |
| } | |
| ImplicitSchurComplement::~ImplicitSchurComplement() { | |
| } | |
| void ImplicitSchurComplement::Init(const BlockSparseMatrix& A, | |
| const double* D, | |
| const double* b) { | |
| // Since initialization is reasonably heavy, perhaps we can save on | |
| // constructing a new object everytime. | |
| if (A_ == NULL) { | |
| A_.reset(PartitionedMatrixViewBase::Create(options_, A)); | |
| } | |
| D_ = D; | |
| b_ = b; | |
| // Initialize temporary storage and compute the block diagonals of | |
| // E'E and F'E. | |
| if (block_diagonal_EtE_inverse_ == NULL) { | |
| block_diagonal_EtE_inverse_.reset(A_->CreateBlockDiagonalEtE()); | |
| if (options_.preconditioner_type == JACOBI) { | |
| block_diagonal_FtF_inverse_.reset(A_->CreateBlockDiagonalFtF()); | |
| } | |
| rhs_.resize(A_->num_cols_f()); | |
| rhs_.setZero(); | |
| tmp_rows_.resize(A_->num_rows()); | |
| tmp_e_cols_.resize(A_->num_cols_e()); | |
| tmp_e_cols_2_.resize(A_->num_cols_e()); | |
| tmp_f_cols_.resize(A_->num_cols_f()); | |
| } else { | |
| A_->UpdateBlockDiagonalEtE(block_diagonal_EtE_inverse_.get()); | |
| if (options_.preconditioner_type == JACOBI) { | |
| A_->UpdateBlockDiagonalFtF(block_diagonal_FtF_inverse_.get()); | |
| } | |
| } | |
| // The block diagonals of the augmented linear system contain | |
| // contributions from the diagonal D if it is non-null. Add that to | |
| // the block diagonals and invert them. | |
| AddDiagonalAndInvert(D_, block_diagonal_EtE_inverse_.get()); | |
| if (options_.preconditioner_type == JACOBI) { | |
| AddDiagonalAndInvert((D_ == NULL) ? NULL : D_ + A_->num_cols_e(), | |
| block_diagonal_FtF_inverse_.get()); | |
| } | |
| // Compute the RHS of the Schur complement system. | |
| UpdateRhs(); | |
| } | |
| // Evaluate the product | |
| // | |
| // Sx = [F'F - F'E (E'E)^-1 E'F]x | |
| // | |
| // By breaking it down into individual matrix vector products | |
| // involving the matrices E and F. This is implemented using a | |
| // PartitionedMatrixView of the input matrix A. | |
| void ImplicitSchurComplement::RightMultiply(const double* x, double* y) const { | |
| // y1 = F x | |
| tmp_rows_.setZero(); | |
| A_->RightMultiplyF(x, tmp_rows_.data()); | |
| // y2 = E' y1 | |
| tmp_e_cols_.setZero(); | |
| A_->LeftMultiplyE(tmp_rows_.data(), tmp_e_cols_.data()); | |
| // y3 = -(E'E)^-1 y2 | |
| tmp_e_cols_2_.setZero(); | |
| block_diagonal_EtE_inverse_->RightMultiply(tmp_e_cols_.data(), | |
| tmp_e_cols_2_.data()); | |
| tmp_e_cols_2_ *= -1.0; | |
| // y1 = y1 + E y3 | |
| A_->RightMultiplyE(tmp_e_cols_2_.data(), tmp_rows_.data()); | |
| // y5 = D * x | |
| if (D_ != NULL) { | |
| ConstVectorRef Dref(D_ + A_->num_cols_e(), num_cols()); | |
| VectorRef(y, num_cols()) = | |
| (Dref.array().square() * | |
| ConstVectorRef(x, num_cols()).array()).matrix(); | |
| } else { | |
| VectorRef(y, num_cols()).setZero(); | |
| } | |
| // y = y5 + F' y1 | |
| A_->LeftMultiplyF(tmp_rows_.data(), y); | |
| } | |
| // Given a block diagonal matrix and an optional array of diagonal | |
| // entries D, add them to the diagonal of the matrix and compute the | |
| // inverse of each diagonal block. | |
| void ImplicitSchurComplement::AddDiagonalAndInvert( | |
| const double* D, | |
| BlockSparseMatrix* block_diagonal) { | |
| const CompressedRowBlockStructure* block_diagonal_structure = | |
| block_diagonal->block_structure(); | |
| for (int r = 0; r < block_diagonal_structure->rows.size(); ++r) { | |
| const int row_block_pos = block_diagonal_structure->rows[r].block.position; | |
| const int row_block_size = block_diagonal_structure->rows[r].block.size; | |
| const Cell& cell = block_diagonal_structure->rows[r].cells[0]; | |
| MatrixRef m(block_diagonal->mutable_values() + cell.position, | |
| row_block_size, row_block_size); | |
| if (D != NULL) { | |
| ConstVectorRef d(D + row_block_pos, row_block_size); | |
| m += d.array().square().matrix().asDiagonal(); | |
| } | |
| m = m | |
| .selfadjointView<Eigen::Upper>() | |
| .llt() | |
| .solve(Matrix::Identity(row_block_size, row_block_size)); | |
| } | |
| } | |
| // Similar to RightMultiply, use the block structure of the matrix A | |
| // to compute y = (E'E)^-1 (E'b - E'F x). | |
| void ImplicitSchurComplement::BackSubstitute(const double* x, double* y) { | |
| const int num_cols_e = A_->num_cols_e(); | |
| const int num_cols_f = A_->num_cols_f(); | |
| const int num_cols = A_->num_cols(); | |
| const int num_rows = A_->num_rows(); | |
| // y1 = F x | |
| tmp_rows_.setZero(); | |
| A_->RightMultiplyF(x, tmp_rows_.data()); | |
| // y2 = b - y1 | |
| tmp_rows_ = ConstVectorRef(b_, num_rows) - tmp_rows_; | |
| // y3 = E' y2 | |
| tmp_e_cols_.setZero(); | |
| A_->LeftMultiplyE(tmp_rows_.data(), tmp_e_cols_.data()); | |
| // y = (E'E)^-1 y3 | |
| VectorRef(y, num_cols).setZero(); | |
| block_diagonal_EtE_inverse_->RightMultiply(tmp_e_cols_.data(), y); | |
| // The full solution vector y has two blocks. The first block of | |
| // variables corresponds to the eliminated variables, which we just | |
| // computed via back substitution. The second block of variables | |
| // corresponds to the Schur complement system, so we just copy those | |
| // values from the solution to the Schur complement. | |
| VectorRef(y + num_cols_e, num_cols_f) = ConstVectorRef(x, num_cols_f); | |
| } | |
| // Compute the RHS of the Schur complement system. | |
| // | |
| // rhs = F'b - F'E (E'E)^-1 E'b | |
| // | |
| // Like BackSubstitute, we use the block structure of A to implement | |
| // this using a series of matrix vector products. | |
| void ImplicitSchurComplement::UpdateRhs() { | |
| // y1 = E'b | |
| tmp_e_cols_.setZero(); | |
| A_->LeftMultiplyE(b_, tmp_e_cols_.data()); | |
| // y2 = (E'E)^-1 y1 | |
| Vector y2 = Vector::Zero(A_->num_cols_e()); | |
| block_diagonal_EtE_inverse_->RightMultiply(tmp_e_cols_.data(), y2.data()); | |
| // y3 = E y2 | |
| tmp_rows_.setZero(); | |
| A_->RightMultiplyE(y2.data(), tmp_rows_.data()); | |
| // y3 = b - y3 | |
| tmp_rows_ = ConstVectorRef(b_, A_->num_rows()) - tmp_rows_; | |
| // rhs = F' y3 | |
| rhs_.setZero(); | |
| A_->LeftMultiplyF(tmp_rows_.data(), rhs_.data()); | |
| } | |
| } // namespace internal | |
| } // namespace ceres |