1 // 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 (at) google.com (Sameer Agarwal) 30 31 // This include must come before any #ifndef check on Ceres compile options. 32 #include "ceres/internal/port.h" 33 34 #include "ceres/sparse_normal_cholesky_solver.h" 35 36 #include <algorithm> 37 #include <cstring> 38 #include <ctime> 39 40 #include "ceres/compressed_row_sparse_matrix.h" 41 #include "ceres/cxsparse.h" 42 #include "ceres/internal/eigen.h" 43 #include "ceres/internal/scoped_ptr.h" 44 #include "ceres/linear_solver.h" 45 #include "ceres/suitesparse.h" 46 #include "ceres/triplet_sparse_matrix.h" 47 #include "ceres/types.h" 48 #include "ceres/wall_time.h" 49 #include "Eigen/SparseCore" 50 51 52 namespace ceres { 53 namespace internal { 54 55 SparseNormalCholeskySolver::SparseNormalCholeskySolver( 56 const LinearSolver::Options& options) 57 : factor_(NULL), 58 cxsparse_factor_(NULL), 59 options_(options){ 60 } 61 62 void SparseNormalCholeskySolver::FreeFactorization() { 63 if (factor_ != NULL) { 64 ss_.Free(factor_); 65 factor_ = NULL; 66 } 67 68 if (cxsparse_factor_ != NULL) { 69 cxsparse_.Free(cxsparse_factor_); 70 cxsparse_factor_ = NULL; 71 } 72 } 73 74 SparseNormalCholeskySolver::~SparseNormalCholeskySolver() { 75 FreeFactorization(); 76 } 77 78 LinearSolver::Summary SparseNormalCholeskySolver::SolveImpl( 79 CompressedRowSparseMatrix* A, 80 const double* b, 81 const LinearSolver::PerSolveOptions& per_solve_options, 82 double * x) { 83 84 const int num_cols = A->num_cols(); 85 VectorRef(x, num_cols).setZero(); 86 A->LeftMultiply(b, x); 87 88 if (per_solve_options.D != NULL) { 89 // Temporarily append a diagonal block to the A matrix, but undo 90 // it before returning the matrix to the user. 91 scoped_ptr<CompressedRowSparseMatrix> regularizer; 92 if (A->col_blocks().size() > 0) { 93 regularizer.reset(CompressedRowSparseMatrix::CreateBlockDiagonalMatrix( 94 per_solve_options.D, A->col_blocks())); 95 } else { 96 regularizer.reset(new CompressedRowSparseMatrix( 97 per_solve_options.D, num_cols)); 98 } 99 A->AppendRows(*regularizer); 100 } 101 102 LinearSolver::Summary summary; 103 switch (options_.sparse_linear_algebra_library_type) { 104 case SUITE_SPARSE: 105 summary = SolveImplUsingSuiteSparse(A, per_solve_options, x); 106 break; 107 case CX_SPARSE: 108 summary = SolveImplUsingCXSparse(A, per_solve_options, x); 109 break; 110 case EIGEN_SPARSE: 111 summary = SolveImplUsingEigen(A, per_solve_options, x); 112 break; 113 default: 114 LOG(FATAL) << "Unknown sparse linear algebra library : " 115 << options_.sparse_linear_algebra_library_type; 116 } 117 118 if (per_solve_options.D != NULL) { 119 A->DeleteRows(num_cols); 120 } 121 122 return summary; 123 } 124 125 LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingEigen( 126 CompressedRowSparseMatrix* A, 127 const LinearSolver::PerSolveOptions& per_solve_options, 128 double * rhs_and_solution) { 129 #ifndef CERES_USE_EIGEN_SPARSE 130 131 LinearSolver::Summary summary; 132 summary.num_iterations = 0; 133 summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; 134 summary.message = 135 "SPARSE_NORMAL_CHOLESKY cannot be used with EIGEN_SPARSE " 136 "because Ceres was not built with support for " 137 "Eigen's SimplicialLDLT decomposition. " 138 "This requires enabling building with -DEIGENSPARSE=ON."; 139 return summary; 140 141 #else 142 143 EventLogger event_logger("SparseNormalCholeskySolver::Eigen::Solve"); 144 145 LinearSolver::Summary summary; 146 summary.num_iterations = 1; 147 summary.termination_type = LINEAR_SOLVER_SUCCESS; 148 summary.message = "Success."; 149 150 // Compute the normal equations. J'J delta = J'f and solve them 151 // using a sparse Cholesky factorization. Notice that when compared 152 // to SuiteSparse we have to explicitly compute the normal equations 153 // before they can be factorized. CHOLMOD/SuiteSparse on the other 154 // hand can just work off of Jt to compute the Cholesky 155 // factorization of the normal equations. 156 // 157 // TODO(sameeragarwal): See note about how this maybe a bad idea for 158 // dynamic sparsity. 159 if (outer_product_.get() == NULL) { 160 outer_product_.reset( 161 CompressedRowSparseMatrix::CreateOuterProductMatrixAndProgram( 162 *A, &pattern_)); 163 } 164 165 CompressedRowSparseMatrix::ComputeOuterProduct( 166 *A, pattern_, outer_product_.get()); 167 168 // Map to an upper triangular column major matrix. 169 // 170 // outer_product_ is a compressed row sparse matrix and in lower 171 // triangular form, when mapped to a compressed column sparse 172 // matrix, it becomes an upper triangular matrix. 173 Eigen::MappedSparseMatrix<double, Eigen::ColMajor> AtA( 174 outer_product_->num_rows(), 175 outer_product_->num_rows(), 176 outer_product_->num_nonzeros(), 177 outer_product_->mutable_rows(), 178 outer_product_->mutable_cols(), 179 outer_product_->mutable_values()); 180 181 const Vector b = VectorRef(rhs_and_solution, outer_product_->num_rows()); 182 if (simplicial_ldlt_.get() == NULL || options_.dynamic_sparsity) { 183 simplicial_ldlt_.reset(new SimplicialLDLT); 184 // This is a crappy way to be doing this. But right now Eigen does 185 // not expose a way to do symbolic analysis with a given 186 // permutation pattern, so we cannot use a block analysis of the 187 // Jacobian. 188 simplicial_ldlt_->analyzePattern(AtA); 189 if (simplicial_ldlt_->info() != Eigen::Success) { 190 summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; 191 summary.message = 192 "Eigen failure. Unable to find symbolic factorization."; 193 return summary; 194 } 195 } 196 event_logger.AddEvent("Analysis"); 197 198 simplicial_ldlt_->factorize(AtA); 199 if(simplicial_ldlt_->info() != Eigen::Success) { 200 summary.termination_type = LINEAR_SOLVER_FAILURE; 201 summary.message = 202 "Eigen failure. Unable to find numeric factorization."; 203 return summary; 204 } 205 206 VectorRef(rhs_and_solution, outer_product_->num_rows()) = 207 simplicial_ldlt_->solve(b); 208 if(simplicial_ldlt_->info() != Eigen::Success) { 209 summary.termination_type = LINEAR_SOLVER_FAILURE; 210 summary.message = 211 "Eigen failure. Unable to do triangular solve."; 212 return summary; 213 } 214 215 event_logger.AddEvent("Solve"); 216 return summary; 217 #endif // EIGEN_USE_EIGEN_SPARSE 218 } 219 220 221 222 LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingCXSparse( 223 CompressedRowSparseMatrix* A, 224 const LinearSolver::PerSolveOptions& per_solve_options, 225 double * rhs_and_solution) { 226 #ifdef CERES_NO_CXSPARSE 227 228 LinearSolver::Summary summary; 229 summary.num_iterations = 0; 230 summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; 231 summary.message = 232 "SPARSE_NORMAL_CHOLESKY cannot be used with CX_SPARSE " 233 "because Ceres was not built with support for CXSparse. " 234 "This requires enabling building with -DCXSPARSE=ON."; 235 236 return summary; 237 238 #else 239 240 EventLogger event_logger("SparseNormalCholeskySolver::CXSparse::Solve"); 241 242 LinearSolver::Summary summary; 243 summary.num_iterations = 1; 244 summary.termination_type = LINEAR_SOLVER_SUCCESS; 245 summary.message = "Success."; 246 247 // Compute the normal equations. J'J delta = J'f and solve them 248 // using a sparse Cholesky factorization. Notice that when compared 249 // to SuiteSparse we have to explicitly compute the normal equations 250 // before they can be factorized. CHOLMOD/SuiteSparse on the other 251 // hand can just work off of Jt to compute the Cholesky 252 // factorization of the normal equations. 253 // 254 // TODO(sameeragarwal): If dynamic sparsity is enabled, then this is 255 // not a good idea performance wise, since the jacobian has far too 256 // many entries and the program will go crazy with memory. 257 if (outer_product_.get() == NULL) { 258 outer_product_.reset( 259 CompressedRowSparseMatrix::CreateOuterProductMatrixAndProgram( 260 *A, &pattern_)); 261 } 262 263 CompressedRowSparseMatrix::ComputeOuterProduct( 264 *A, pattern_, outer_product_.get()); 265 cs_di AtA_view = 266 cxsparse_.CreateSparseMatrixTransposeView(outer_product_.get()); 267 cs_di* AtA = &AtA_view; 268 269 event_logger.AddEvent("Setup"); 270 271 // Compute symbolic factorization if not available. 272 if (options_.dynamic_sparsity) { 273 FreeFactorization(); 274 } 275 if (cxsparse_factor_ == NULL) { 276 if (options_.use_postordering) { 277 cxsparse_factor_ = cxsparse_.BlockAnalyzeCholesky(AtA, 278 A->col_blocks(), 279 A->col_blocks()); 280 } else { 281 if (options_.dynamic_sparsity) { 282 cxsparse_factor_ = cxsparse_.AnalyzeCholesky(AtA); 283 } else { 284 cxsparse_factor_ = cxsparse_.AnalyzeCholeskyWithNaturalOrdering(AtA); 285 } 286 } 287 } 288 event_logger.AddEvent("Analysis"); 289 290 if (cxsparse_factor_ == NULL) { 291 summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; 292 summary.message = 293 "CXSparse failure. Unable to find symbolic factorization."; 294 } else if (!cxsparse_.SolveCholesky(AtA, cxsparse_factor_, rhs_and_solution)) { 295 summary.termination_type = LINEAR_SOLVER_FAILURE; 296 summary.message = "CXSparse::SolveCholesky failed."; 297 } 298 event_logger.AddEvent("Solve"); 299 300 return summary; 301 #endif 302 } 303 304 LinearSolver::Summary SparseNormalCholeskySolver::SolveImplUsingSuiteSparse( 305 CompressedRowSparseMatrix* A, 306 const LinearSolver::PerSolveOptions& per_solve_options, 307 double * rhs_and_solution) { 308 #ifdef CERES_NO_SUITESPARSE 309 310 LinearSolver::Summary summary; 311 summary.num_iterations = 0; 312 summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; 313 summary.message = 314 "SPARSE_NORMAL_CHOLESKY cannot be used with SUITE_SPARSE " 315 "because Ceres was not built with support for SuiteSparse. " 316 "This requires enabling building with -DSUITESPARSE=ON."; 317 return summary; 318 319 #else 320 321 EventLogger event_logger("SparseNormalCholeskySolver::SuiteSparse::Solve"); 322 LinearSolver::Summary summary; 323 summary.termination_type = LINEAR_SOLVER_SUCCESS; 324 summary.num_iterations = 1; 325 summary.message = "Success."; 326 327 const int num_cols = A->num_cols(); 328 cholmod_sparse lhs = ss_.CreateSparseMatrixTransposeView(A); 329 event_logger.AddEvent("Setup"); 330 331 if (options_.dynamic_sparsity) { 332 FreeFactorization(); 333 } 334 if (factor_ == NULL) { 335 if (options_.use_postordering) { 336 factor_ = ss_.BlockAnalyzeCholesky(&lhs, 337 A->col_blocks(), 338 A->row_blocks(), 339 &summary.message); 340 } else { 341 if (options_.dynamic_sparsity) { 342 factor_ = ss_.AnalyzeCholesky(&lhs, &summary.message); 343 } else { 344 factor_ = ss_.AnalyzeCholeskyWithNaturalOrdering(&lhs, &summary.message); 345 } 346 } 347 } 348 event_logger.AddEvent("Analysis"); 349 350 if (factor_ == NULL) { 351 summary.termination_type = LINEAR_SOLVER_FATAL_ERROR; 352 // No need to set message as it has already been set by the 353 // symbolic analysis routines above. 354 return summary; 355 } 356 357 summary.termination_type = ss_.Cholesky(&lhs, factor_, &summary.message); 358 if (summary.termination_type != LINEAR_SOLVER_SUCCESS) { 359 return summary; 360 } 361 362 cholmod_dense* rhs = ss_.CreateDenseVector(rhs_and_solution, num_cols, num_cols); 363 cholmod_dense* solution = ss_.Solve(factor_, rhs, &summary.message); 364 event_logger.AddEvent("Solve"); 365 366 ss_.Free(rhs); 367 if (solution != NULL) { 368 memcpy(rhs_and_solution, solution->x, num_cols * sizeof(*rhs_and_solution)); 369 ss_.Free(solution); 370 } else { 371 // No need to set message as it has already been set by the 372 // numeric factorization routine above. 373 summary.termination_type = LINEAR_SOLVER_FAILURE; 374 } 375 376 event_logger.AddEvent("Teardown"); 377 return summary; 378 #endif 379 } 380 381 } // namespace internal 382 } // namespace ceres 383