Home | History | Annotate | Download | only in ceres
      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 #ifndef CERES_NO_SUITESPARSE
     32 #include "ceres/suitesparse.h"
     33 
     34 #include <vector>
     35 #include "cholmod.h"
     36 #include "ceres/compressed_col_sparse_matrix_utils.h"
     37 #include "ceres/compressed_row_sparse_matrix.h"
     38 #include "ceres/triplet_sparse_matrix.h"
     39 
     40 namespace ceres {
     41 namespace internal {
     42 
     43 SuiteSparse::SuiteSparse() {
     44   cholmod_start(&cc_);
     45 }
     46 
     47 SuiteSparse::~SuiteSparse() {
     48   cholmod_finish(&cc_);
     49 }
     50 
     51 cholmod_sparse* SuiteSparse::CreateSparseMatrix(TripletSparseMatrix* A) {
     52   cholmod_triplet triplet;
     53 
     54   triplet.nrow = A->num_rows();
     55   triplet.ncol = A->num_cols();
     56   triplet.nzmax = A->max_num_nonzeros();
     57   triplet.nnz = A->num_nonzeros();
     58   triplet.i = reinterpret_cast<void*>(A->mutable_rows());
     59   triplet.j = reinterpret_cast<void*>(A->mutable_cols());
     60   triplet.x = reinterpret_cast<void*>(A->mutable_values());
     61   triplet.stype = 0;  // Matrix is not symmetric.
     62   triplet.itype = CHOLMOD_INT;
     63   triplet.xtype = CHOLMOD_REAL;
     64   triplet.dtype = CHOLMOD_DOUBLE;
     65 
     66   return cholmod_triplet_to_sparse(&triplet, triplet.nnz, &cc_);
     67 }
     68 
     69 
     70 cholmod_sparse* SuiteSparse::CreateSparseMatrixTranspose(
     71     TripletSparseMatrix* A) {
     72   cholmod_triplet triplet;
     73 
     74   triplet.ncol = A->num_rows();  // swap row and columns
     75   triplet.nrow = A->num_cols();
     76   triplet.nzmax = A->max_num_nonzeros();
     77   triplet.nnz = A->num_nonzeros();
     78 
     79   // swap rows and columns
     80   triplet.j = reinterpret_cast<void*>(A->mutable_rows());
     81   triplet.i = reinterpret_cast<void*>(A->mutable_cols());
     82   triplet.x = reinterpret_cast<void*>(A->mutable_values());
     83   triplet.stype = 0;  // Matrix is not symmetric.
     84   triplet.itype = CHOLMOD_INT;
     85   triplet.xtype = CHOLMOD_REAL;
     86   triplet.dtype = CHOLMOD_DOUBLE;
     87 
     88   return cholmod_triplet_to_sparse(&triplet, triplet.nnz, &cc_);
     89 }
     90 
     91 cholmod_sparse SuiteSparse::CreateSparseMatrixTransposeView(
     92     CompressedRowSparseMatrix* A) {
     93   cholmod_sparse m;
     94   m.nrow = A->num_cols();
     95   m.ncol = A->num_rows();
     96   m.nzmax = A->num_nonzeros();
     97   m.nz = NULL;
     98   m.p = reinterpret_cast<void*>(A->mutable_rows());
     99   m.i = reinterpret_cast<void*>(A->mutable_cols());
    100   m.x = reinterpret_cast<void*>(A->mutable_values());
    101   m.z = NULL;
    102   m.stype = 0;  // Matrix is not symmetric.
    103   m.itype = CHOLMOD_INT;
    104   m.xtype = CHOLMOD_REAL;
    105   m.dtype = CHOLMOD_DOUBLE;
    106   m.sorted = 1;
    107   m.packed = 1;
    108 
    109   return m;
    110 }
    111 
    112 cholmod_dense* SuiteSparse::CreateDenseVector(const double* x,
    113                                               int in_size,
    114                                               int out_size) {
    115     CHECK_LE(in_size, out_size);
    116     cholmod_dense* v = cholmod_zeros(out_size, 1, CHOLMOD_REAL, &cc_);
    117     if (x != NULL) {
    118       memcpy(v->x, x, in_size*sizeof(*x));
    119     }
    120     return v;
    121 }
    122 
    123 cholmod_factor* SuiteSparse::AnalyzeCholesky(cholmod_sparse* A) {
    124   // Cholmod can try multiple re-ordering strategies to find a fill
    125   // reducing ordering. Here we just tell it use AMD with automatic
    126   // matrix dependence choice of supernodal versus simplicial
    127   // factorization.
    128   cc_.nmethods = 1;
    129   cc_.method[0].ordering = CHOLMOD_AMD;
    130   cc_.supernodal = CHOLMOD_AUTO;
    131 
    132   cholmod_factor* factor = cholmod_analyze(A, &cc_);
    133   CHECK_EQ(cc_.status, CHOLMOD_OK)
    134       << "Cholmod symbolic analysis failed " << cc_.status;
    135   CHECK_NOTNULL(factor);
    136 
    137   if (VLOG_IS_ON(2)) {
    138     cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
    139   }
    140 
    141   return factor;
    142 }
    143 
    144 cholmod_factor* SuiteSparse::BlockAnalyzeCholesky(
    145     cholmod_sparse* A,
    146     const vector<int>& row_blocks,
    147     const vector<int>& col_blocks) {
    148   vector<int> ordering;
    149   if (!BlockAMDOrdering(A, row_blocks, col_blocks, &ordering)) {
    150     return NULL;
    151   }
    152   return AnalyzeCholeskyWithUserOrdering(A, ordering);
    153 }
    154 
    155 cholmod_factor* SuiteSparse::AnalyzeCholeskyWithUserOrdering(
    156     cholmod_sparse* A,
    157     const vector<int>& ordering) {
    158   CHECK_EQ(ordering.size(), A->nrow);
    159 
    160   cc_.nmethods = 1;
    161   cc_.method[0].ordering = CHOLMOD_GIVEN;
    162 
    163   cholmod_factor* factor  =
    164       cholmod_analyze_p(A, const_cast<int*>(&ordering[0]), NULL, 0, &cc_);
    165   CHECK_EQ(cc_.status, CHOLMOD_OK)
    166       << "Cholmod symbolic analysis failed " << cc_.status;
    167   CHECK_NOTNULL(factor);
    168 
    169   if (VLOG_IS_ON(2)) {
    170     cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
    171   }
    172 
    173   return factor;
    174 }
    175 
    176 cholmod_factor* SuiteSparse::AnalyzeCholeskyWithNaturalOrdering(
    177     cholmod_sparse* A) {
    178   cc_.nmethods = 1;
    179   cc_.method[0].ordering = CHOLMOD_NATURAL;
    180   cc_.postorder = 0;
    181 
    182   cholmod_factor* factor  = cholmod_analyze(A, &cc_);
    183   CHECK_EQ(cc_.status, CHOLMOD_OK)
    184       << "Cholmod symbolic analysis failed " << cc_.status;
    185   CHECK_NOTNULL(factor);
    186 
    187   if (VLOG_IS_ON(2)) {
    188     cholmod_print_common(const_cast<char*>("Symbolic Analysis"), &cc_);
    189   }
    190 
    191   return factor;
    192 }
    193 
    194 bool SuiteSparse::BlockAMDOrdering(const cholmod_sparse* A,
    195                                    const vector<int>& row_blocks,
    196                                    const vector<int>& col_blocks,
    197                                    vector<int>* ordering) {
    198   const int num_row_blocks = row_blocks.size();
    199   const int num_col_blocks = col_blocks.size();
    200 
    201   // Arrays storing the compressed column structure of the matrix
    202   // incoding the block sparsity of A.
    203   vector<int> block_cols;
    204   vector<int> block_rows;
    205 
    206   CompressedColumnScalarMatrixToBlockMatrix(reinterpret_cast<const int*>(A->i),
    207                                             reinterpret_cast<const int*>(A->p),
    208                                             row_blocks,
    209                                             col_blocks,
    210                                             &block_rows,
    211                                             &block_cols);
    212 
    213   cholmod_sparse_struct block_matrix;
    214   block_matrix.nrow = num_row_blocks;
    215   block_matrix.ncol = num_col_blocks;
    216   block_matrix.nzmax = block_rows.size();
    217   block_matrix.p = reinterpret_cast<void*>(&block_cols[0]);
    218   block_matrix.i = reinterpret_cast<void*>(&block_rows[0]);
    219   block_matrix.x = NULL;
    220   block_matrix.stype = A->stype;
    221   block_matrix.itype = CHOLMOD_INT;
    222   block_matrix.xtype = CHOLMOD_PATTERN;
    223   block_matrix.dtype = CHOLMOD_DOUBLE;
    224   block_matrix.sorted = 1;
    225   block_matrix.packed = 1;
    226 
    227   vector<int> block_ordering(num_row_blocks);
    228   if (!cholmod_amd(&block_matrix, NULL, 0, &block_ordering[0], &cc_)) {
    229     return false;
    230   }
    231 
    232   BlockOrderingToScalarOrdering(row_blocks, block_ordering, ordering);
    233   return true;
    234 }
    235 
    236 bool SuiteSparse::Cholesky(cholmod_sparse* A, cholmod_factor* L) {
    237   CHECK_NOTNULL(A);
    238   CHECK_NOTNULL(L);
    239 
    240   // Save the current print level and silence CHOLMOD, otherwise
    241   // CHOLMOD is prone to dumping stuff to stderr, which can be
    242   // distracting when the error (matrix is indefinite) is not a fatal
    243   // failure.
    244   const int old_print_level = cc_.print;
    245   cc_.print = 0;
    246 
    247   cc_.quick_return_if_not_posdef = 1;
    248   int status = cholmod_factorize(A, L, &cc_);
    249   cc_.print = old_print_level;
    250 
    251   // TODO(sameeragarwal): This switch statement is not consistent. It
    252   // treats all kinds of CHOLMOD failures as warnings. Some of these
    253   // like out of memory are definitely not warnings. The problem is
    254   // that the return value Cholesky is two valued, but the state of
    255   // the linear solver is really three valued. SUCCESS,
    256   // NON_FATAL_FAILURE (e.g., indefinite matrix) and FATAL_FAILURE
    257   // (e.g. out of memory).
    258   switch (cc_.status) {
    259     case CHOLMOD_NOT_INSTALLED:
    260       LOG(WARNING) << "CHOLMOD failure: Method not installed.";
    261       return false;
    262     case CHOLMOD_OUT_OF_MEMORY:
    263       LOG(WARNING) << "CHOLMOD failure: Out of memory.";
    264       return false;
    265     case CHOLMOD_TOO_LARGE:
    266       LOG(WARNING) << "CHOLMOD failure: Integer overflow occured.";
    267       return false;
    268     case CHOLMOD_INVALID:
    269       LOG(WARNING) << "CHOLMOD failure: Invalid input.";
    270       return false;
    271     case CHOLMOD_NOT_POSDEF:
    272       // TODO(sameeragarwal): These two warnings require more
    273       // sophisticated handling going forward. For now we will be
    274       // strict and treat them as failures.
    275       LOG(WARNING) << "CHOLMOD warning: Matrix not positive definite.";
    276       return false;
    277     case CHOLMOD_DSMALL:
    278       LOG(WARNING) << "CHOLMOD warning: D for LDL' or diag(L) or "
    279                    << "LL' has tiny absolute value.";
    280       return false;
    281     case CHOLMOD_OK:
    282       if (status != 0) {
    283         return true;
    284       }
    285       LOG(WARNING) << "CHOLMOD failure: cholmod_factorize returned zero "
    286                    << "but cholmod_common::status is CHOLMOD_OK."
    287                    << "Please report this to ceres-solver (at) googlegroups.com.";
    288       return false;
    289     default:
    290       LOG(WARNING) << "Unknown cholmod return code. "
    291                    << "Please report this to ceres-solver (at) googlegroups.com.";
    292       return false;
    293   }
    294   return false;
    295 }
    296 
    297 cholmod_dense* SuiteSparse::Solve(cholmod_factor* L,
    298                                   cholmod_dense* b) {
    299   if (cc_.status != CHOLMOD_OK) {
    300     LOG(WARNING) << "CHOLMOD status NOT OK";
    301     return NULL;
    302   }
    303 
    304   return cholmod_solve(CHOLMOD_A, L, b, &cc_);
    305 }
    306 
    307 cholmod_dense* SuiteSparse::SolveCholesky(cholmod_sparse* A,
    308                                           cholmod_factor* L,
    309                                           cholmod_dense* b) {
    310   CHECK_NOTNULL(A);
    311   CHECK_NOTNULL(L);
    312   CHECK_NOTNULL(b);
    313 
    314   if (Cholesky(A, L)) {
    315     return Solve(L, b);
    316   }
    317 
    318   return NULL;
    319 }
    320 
    321 void SuiteSparse::ApproximateMinimumDegreeOrdering(cholmod_sparse* matrix,
    322                                                    int* ordering) {
    323   cholmod_amd(matrix, NULL, 0, ordering, &cc_);
    324 }
    325 
    326 void SuiteSparse::ConstrainedApproximateMinimumDegreeOrdering(
    327     cholmod_sparse* matrix,
    328     int* constraints,
    329     int* ordering) {
    330 #ifndef CERES_NO_CAMD
    331   cholmod_camd(matrix, NULL, 0, constraints, ordering, &cc_);
    332 #else
    333   LOG(FATAL) << "Congratulations you have found a bug in Ceres."
    334              << "Ceres Solver was compiled with SuiteSparse "
    335              << "version 4.1.0 or less. Calling this function "
    336              << "in that case is a bug. Please contact the"
    337              << "the Ceres Solver developers.";
    338 #endif
    339 }
    340 
    341 }  // namespace internal
    342 }  // namespace ceres
    343 
    344 #endif  // CERES_NO_SUITESPARSE
    345