// Ceres Solver - A fast non-linear least squares minimizer // Copyright 2023 Google Inc. All rights reserved. // http://ceres-solver.org/ // // 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/iterative_refiner.h" #include #include "Eigen/Dense" #include "ceres/dense_cholesky.h" #include "ceres/internal/eigen.h" #include "ceres/sparse_cholesky.h" #include "ceres/sparse_matrix.h" #include "glog/logging.h" #include "gtest/gtest.h" namespace ceres::internal { // Macros to help us define virtual methods which we do not expect to // use/call in this test. #define DO_NOT_CALL \ { LOG(FATAL) << "DO NOT CALL"; } #define DO_NOT_CALL_WITH_RETURN(x) \ { \ LOG(FATAL) << "DO NOT CALL"; \ return x; \ } // A fake SparseMatrix, which uses an Eigen matrix to do the real work. class FakeSparseMatrix : public SparseMatrix { public: explicit FakeSparseMatrix(Matrix m) : m_(std::move(m)) {} // y += Ax void RightMultiplyAndAccumulate(const double* x, double* y) const final { VectorRef(y, m_.cols()) += m_ * ConstVectorRef(x, m_.cols()); } // y += A'x void LeftMultiplyAndAccumulate(const double* x, double* y) const final { // We will assume that this is a symmetric matrix. RightMultiplyAndAccumulate(x, y); } double* mutable_values() final { return m_.data(); } const double* values() const final { return m_.data(); } int num_rows() const final { return m_.cols(); } int num_cols() const final { return m_.cols(); } int num_nonzeros() const final { return m_.cols() * m_.cols(); } // The following methods are not needed for tests in this file. void SquaredColumnNorm(double* x) const final DO_NOT_CALL; void ScaleColumns(const double* scale) final DO_NOT_CALL; void SetZero() final DO_NOT_CALL; void ToDenseMatrix(Matrix* dense_matrix) const final DO_NOT_CALL; void ToTextFile(FILE* file) const final DO_NOT_CALL; private: Matrix m_; }; // A fake SparseCholesky which uses Eigen's Cholesky factorization to // do the real work. The template parameter allows us to work in // doubles or floats, even though the source matrix is double. template class FakeSparseCholesky : public SparseCholesky { public: explicit FakeSparseCholesky(const Matrix& lhs) { lhs_ = lhs.cast(); } LinearSolverTerminationType Solve(const double* rhs_ptr, double* solution_ptr, std::string* message) final { const int num_cols = lhs_.cols(); VectorRef solution(solution_ptr, num_cols); ConstVectorRef rhs(rhs_ptr, num_cols); auto llt = lhs_.llt(); CHECK_EQ(llt.info(), Eigen::Success); solution = llt.solve(rhs.cast()).template cast(); return LinearSolverTerminationType::SUCCESS; } // The following methods are not needed for tests in this file. CompressedRowSparseMatrix::StorageType StorageType() const final DO_NOT_CALL_WITH_RETURN( CompressedRowSparseMatrix::StorageType::UPPER_TRIANGULAR); LinearSolverTerminationType Factorize(CompressedRowSparseMatrix* lhs, std::string* message) final DO_NOT_CALL_WITH_RETURN(LinearSolverTerminationType::FAILURE); private: Eigen::Matrix lhs_; }; // A fake DenseCholesky which uses Eigen's Cholesky factorization to // do the real work. The template parameter allows us to work in // doubles or floats, even though the source matrix is double. template class FakeDenseCholesky : public DenseCholesky { public: explicit FakeDenseCholesky(const Matrix& lhs) { lhs_ = lhs.cast(); } LinearSolverTerminationType Solve(const double* rhs_ptr, double* solution_ptr, std::string* message) final { const int num_cols = lhs_.cols(); VectorRef solution(solution_ptr, num_cols); ConstVectorRef rhs(rhs_ptr, num_cols); solution = lhs_.llt().solve(rhs.cast()).template cast(); return LinearSolverTerminationType::SUCCESS; } LinearSolverTerminationType Factorize(int num_cols, double* lhs, std::string* message) final DO_NOT_CALL_WITH_RETURN(LinearSolverTerminationType::FAILURE); private: Eigen::Matrix lhs_; }; #undef DO_NOT_CALL #undef DO_NOT_CALL_WITH_RETURN class SparseIterativeRefinerTest : public ::testing::Test { public: void SetUp() override { num_cols_ = 5; max_num_iterations_ = 30; Matrix m(num_cols_, num_cols_); m.setRandom(); lhs_ = m * m.transpose(); solution_.resize(num_cols_); solution_.setRandom(); rhs_ = lhs_ * solution_; }; protected: int num_cols_; int max_num_iterations_; Matrix lhs_; Vector rhs_, solution_; }; TEST_F(SparseIterativeRefinerTest, RandomSolutionWithExactFactorizationConverges) { FakeSparseMatrix lhs(lhs_); FakeSparseCholesky sparse_cholesky(lhs_); SparseIterativeRefiner refiner(max_num_iterations_); Vector refined_solution(num_cols_); refined_solution.setRandom(); refiner.Refine(lhs, rhs_.data(), &sparse_cholesky, refined_solution.data()); EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(), 0.0, std::numeric_limits::epsilon() * 10); } TEST_F(SparseIterativeRefinerTest, RandomSolutionWithApproximationFactorizationConverges) { FakeSparseMatrix lhs(lhs_); // Use a single precision Cholesky factorization of the double // precision matrix. This will give us an approximate factorization. FakeSparseCholesky sparse_cholesky(lhs_); SparseIterativeRefiner refiner(max_num_iterations_); Vector refined_solution(num_cols_); refined_solution.setRandom(); refiner.Refine(lhs, rhs_.data(), &sparse_cholesky, refined_solution.data()); EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(), 0.0, std::numeric_limits::epsilon() * 10); } class DenseIterativeRefinerTest : public ::testing::Test { public: void SetUp() override { num_cols_ = 5; max_num_iterations_ = 30; Matrix m(num_cols_, num_cols_); m.setRandom(); lhs_ = m * m.transpose(); solution_.resize(num_cols_); solution_.setRandom(); rhs_ = lhs_ * solution_; }; protected: int num_cols_; int max_num_iterations_; Matrix lhs_; Vector rhs_, solution_; }; TEST_F(DenseIterativeRefinerTest, RandomSolutionWithExactFactorizationConverges) { Matrix lhs = lhs_; FakeDenseCholesky dense_cholesky(lhs); DenseIterativeRefiner refiner(max_num_iterations_); Vector refined_solution(num_cols_); refined_solution.setRandom(); refiner.Refine(lhs.cols(), lhs.data(), rhs_.data(), &dense_cholesky, refined_solution.data()); EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(), 0.0, std::numeric_limits::epsilon() * 10); } TEST_F(DenseIterativeRefinerTest, RandomSolutionWithApproximationFactorizationConverges) { Matrix lhs = lhs_; // Use a single precision Cholesky factorization of the double // precision matrix. This will give us an approximate factorization. FakeDenseCholesky dense_cholesky(lhs_); DenseIterativeRefiner refiner(max_num_iterations_); Vector refined_solution(num_cols_); refined_solution.setRandom(); refiner.Refine(lhs.cols(), lhs.data(), rhs_.data(), &dense_cholesky, refined_solution.data()); EXPECT_NEAR((lhs_ * refined_solution - rhs_).norm(), 0.0, std::numeric_limits::epsilon() * 10); } } // namespace ceres::internal