g2o
linear_solver_cholmod.h
Go to the documentation of this file.
1 // g2o - General Graph Optimization
2 // Copyright (C) 2011 R. Kuemmerle, G. Grisetti, W. Burgard
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright notice,
10 // this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above copyright
12 // notice, this list of conditions and the following disclaimer in the
13 // documentation and/or other materials provided with the distribution.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16 // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
18 // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19 // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 
27 #ifndef G2O_LINEAR_SOLVER_CHOLMOD
28 #define G2O_LINEAR_SOLVER_CHOLMOD
29 
30 #include "g2o/core/linear_solver.h"
32 #include "g2o/core/batch_stats.h"
33 #include "g2o/stuff/timeutil.h"
35 
36 #include <cholmod.h>
37 
38 namespace g2o {
39 
43 struct CholmodExt : public cholmod_sparse
44 {
46  {
47  nzmax = 0;
48  nrow = 0;
49  ncol = 0;
50  p = 0;
51  i = 0;
52  nz = 0;
53  x = 0;
54  z = 0;
55  stype = 1; // upper triangular block only
56  itype = CHOLMOD_INT;
57  xtype = CHOLMOD_REAL;
58  dtype = CHOLMOD_DOUBLE;
59  sorted = 1;
60  packed = 1;
61  columnsAllocated = 0;
62  }
64  {
65  delete[] (int*)p; p = 0;
66  delete[] (double*)x; x = 0;
67  delete[] (int*)i; i = 0;
68  }
70 };
71 
75 template <typename MatrixType>
76 class LinearSolverCholmod : public LinearSolverCCS<MatrixType>
77 {
78  public:
80  LinearSolverCCS<MatrixType>()
81  {
82  _writeDebug = true;
83  _blockOrdering = false;
84  _cholmodSparse = new CholmodExt();
85  _cholmodFactor = 0;
86  cholmod_start(&_cholmodCommon);
87 
88  // setup ordering strategy
89  _cholmodCommon.nmethods = 1 ;
90  _cholmodCommon.method[0].ordering = CHOLMOD_AMD; //CHOLMOD_COLAMD
91  //_cholmodCommon.postorder = 0;
92 
93  _cholmodCommon.supernodal = CHOLMOD_AUTO; //CHOLMOD_SUPERNODAL; //CHOLMOD_SIMPLICIAL;
94  }
95 
97  {
98  delete _cholmodSparse;
99  if (_cholmodFactor != 0) {
100  cholmod_free_factor(&_cholmodFactor, &_cholmodCommon);
101  _cholmodFactor = 0;
102  }
103  cholmod_finish(&_cholmodCommon);
104  }
105 
106  virtual bool init()
107  {
108  if (_cholmodFactor != 0) {
109  cholmod_free_factor(&_cholmodFactor, &_cholmodCommon);
110  _cholmodFactor = 0;
111  }
112  return true;
113  }
114 
115  bool solve(const SparseBlockMatrix<MatrixType>& A, double* x, double* b)
116  {
117  //cerr << __PRETTY_FUNCTION__ << " using cholmod" << endl;
118  fillCholmodExt(A, _cholmodFactor != 0); // _cholmodFactor used as bool, if not existing will copy the whole structure, otherwise only the values
119 
120  if (_cholmodFactor == 0) {
121  computeSymbolicDecomposition(A);
122  assert(_cholmodFactor != 0 && "Symbolic cholesky failed");
123  }
124  double t=get_monotonic_time();
125 
126  // setting up b for calling cholmod
127  cholmod_dense bcholmod;
128  bcholmod.nrow = bcholmod.d = _cholmodSparse->nrow;
129  bcholmod.ncol = 1;
130  bcholmod.x = b;
131  bcholmod.xtype = CHOLMOD_REAL;
132  bcholmod.dtype = CHOLMOD_DOUBLE;
133 
134  cholmod_factorize(_cholmodSparse, _cholmodFactor, &_cholmodCommon);
135  if (_cholmodCommon.status == CHOLMOD_NOT_POSDEF) {
136  if (_writeDebug) {
137  std::cerr << "Cholesky failure, writing debug.txt (Hessian loadable by Octave)" << std::endl;
138  saveMatrix("debug.txt");
139  }
140  return false;
141  }
142 
143  cholmod_dense* xcholmod = cholmod_solve(CHOLMOD_A, _cholmodFactor, &bcholmod, &_cholmodCommon);
144  memcpy(x, xcholmod->x, sizeof(double) * bcholmod.nrow); // copy back to our array
145  cholmod_free_dense(&xcholmod, &_cholmodCommon);
146 
148  if (globalStats){
149  globalStats->timeNumericDecomposition = get_monotonic_time() - t;
150  globalStats->choleskyNNZ = static_cast<size_t>(_cholmodCommon.method[0].lnz);
151  }
152 
153  return true;
154  }
155 
156  bool solveBlocks(double**& blocks, const SparseBlockMatrix<MatrixType>& A)
157  {
158  //cerr << __PRETTY_FUNCTION__ << " using cholmod" << endl;
159  fillCholmodExt(A, _cholmodFactor != 0); // _cholmodFactor used as bool, if not existing will copy the whole structure, otherwise only the values
160 
161  if (_cholmodFactor == 0) {
162  computeSymbolicDecomposition(A);
163  assert(_cholmodFactor && "Symbolic cholesky failed");
164  }
165 
166  if (! blocks){
167  blocks=new double*[A.rows()];
168  double **block=blocks;
169  for (size_t i = 0; i < A.rowBlockIndices().size(); ++i){
170  int dim=A.rowsOfBlock(i)*A.colsOfBlock(i);
171  *block = new double [dim];
172  block++;
173  }
174  }
175 
176  cholmod_factorize(_cholmodSparse, _cholmodFactor, &_cholmodCommon);
177  if (_cholmodCommon.status == CHOLMOD_NOT_POSDEF)
178  return false;
179 
180  // convert the factorization to LL, simplical, packed, monotonic
181  int change_status = cholmod_change_factor(CHOLMOD_REAL, 1, 0, 1, 1, _cholmodFactor, &_cholmodCommon);
182  if (! change_status) {
183  return false;
184  }
185  assert(_cholmodFactor->is_ll && !_cholmodFactor->is_super && _cholmodFactor->is_monotonic && "Cholesky factor has wrong format");
186 
187  // invert the permutation
188  int* p = (int*)_cholmodFactor->Perm;
189  VectorXI pinv; pinv.resize(_cholmodSparse->ncol);
190  for (size_t i = 0; i < _cholmodSparse->ncol; ++i)
191  pinv(p[i]) = i;
192 
193  // compute the marginal covariance
195  mcc.setCholeskyFactor(_cholmodSparse->ncol, (int*)_cholmodFactor->p, (int*)_cholmodFactor->i,
196  (double*)_cholmodFactor->x, pinv.data());
197  mcc.computeCovariance(blocks, A.rowBlockIndices());
198 
200  if (globalStats) {
201  globalStats->choleskyNNZ = static_cast<size_t>(_cholmodCommon.method[_cholmodCommon.selected].lnz);
202  }
203 
204  return true;
205  }
206 
207  virtual bool solvePattern(SparseBlockMatrix<MatrixXD>& spinv, const std::vector<std::pair<int, int> >& blockIndices, const SparseBlockMatrix<MatrixType>& A)
208  {
209  //cerr << __PRETTY_FUNCTION__ << " using cholmod" << endl;
210  fillCholmodExt(A, _cholmodFactor != 0); // _cholmodFactor used as bool, if not existing will copy the whole structure, otherwise only the values
211 
212  if (_cholmodFactor == 0) {
213  computeSymbolicDecomposition(A);
214  assert(_cholmodFactor && "Symbolic cholesky failed");
215  }
216 
217  cholmod_factorize(_cholmodSparse, _cholmodFactor, &_cholmodCommon);
218  if (_cholmodCommon.status == CHOLMOD_NOT_POSDEF)
219  return false;
220 
221  // convert the factorization to LL, simplical, packed, monotonic
222  int change_status = cholmod_change_factor(CHOLMOD_REAL, 1, 0, 1, 1, _cholmodFactor, &_cholmodCommon);
223  if (! change_status) {
224  return false;
225  }
226  assert(_cholmodFactor->is_ll && !_cholmodFactor->is_super && _cholmodFactor->is_monotonic && "Cholesky factor has wrong format");
227 
228  // invert the permutation
229  int* p = (int*)_cholmodFactor->Perm;
230  VectorXI pinv; pinv.resize(_cholmodSparse->ncol);
231  for (size_t i = 0; i < _cholmodSparse->ncol; ++i)
232  pinv(p[i]) = i;
233 
234  // compute the marginal covariance
236  mcc.setCholeskyFactor(_cholmodSparse->ncol, (int*)_cholmodFactor->p, (int*)_cholmodFactor->i,
237  (double*)_cholmodFactor->x, pinv.data());
238  mcc.computeCovariance(spinv, A.rowBlockIndices(), blockIndices);
239 
241  if (globalStats) {
242  globalStats->choleskyNNZ = static_cast<size_t>(_cholmodCommon.method[_cholmodCommon.selected].lnz);
243  }
244 
245  return true;
246  }
247 
249  bool blockOrdering() const { return _blockOrdering;}
250  void setBlockOrdering(bool blockOrdering) { _blockOrdering = blockOrdering;}
251 
253  virtual bool writeDebug() const { return _writeDebug;}
254  virtual void setWriteDebug(bool b) { _writeDebug = b;}
255 
256  virtual bool saveMatrix(const std::string& fileName) {
257  writeCCSMatrix(fileName, _cholmodSparse->nrow, _cholmodSparse->ncol, (int*)_cholmodSparse->p, (int*)_cholmodSparse->i, (double*)_cholmodSparse->x, true);
258  return true;
259  }
260 
261  protected:
262  // temp used for cholesky with cholmod
263  cholmod_common _cholmodCommon;
265  cholmod_factor* _cholmodFactor;
268  VectorXI _scalarPermutation, _blockPermutation;
270 
272  {
273  double t = get_monotonic_time();
274  if (! _blockOrdering) {
275  // setup ordering strategy
276  _cholmodCommon.nmethods = 1;
277  _cholmodCommon.method[0].ordering = CHOLMOD_AMD; //CHOLMOD_COLAMD
278  _cholmodFactor = cholmod_analyze(_cholmodSparse, &_cholmodCommon); // symbolic factorization
279  } else {
280 
281  A.fillBlockStructure(_matrixStructure);
282 
283  // get the ordering for the block matrix
284  if (_blockPermutation.size() == 0)
285  _blockPermutation.resize(_matrixStructure.n);
286  if (_blockPermutation.size() < _matrixStructure.n) // double space if resizing
287  _blockPermutation.resize(2*_matrixStructure.n);
288 
289  // prepare AMD call via CHOLMOD
290  cholmod_sparse auxCholmodSparse;
291  auxCholmodSparse.nzmax = _matrixStructure.nzMax();
292  auxCholmodSparse.nrow = auxCholmodSparse.ncol = _matrixStructure.n;
293  auxCholmodSparse.p = _matrixStructure.Ap;
294  auxCholmodSparse.i = _matrixStructure.Aii;
295  auxCholmodSparse.nz = 0;
296  auxCholmodSparse.x = 0;
297  auxCholmodSparse.z = 0;
298  auxCholmodSparse.stype = 1;
299  auxCholmodSparse.xtype = CHOLMOD_PATTERN;
300  auxCholmodSparse.itype = CHOLMOD_INT;
301  auxCholmodSparse.dtype = CHOLMOD_DOUBLE;
302  auxCholmodSparse.sorted = 1;
303  auxCholmodSparse.packed = 1;
304  int amdStatus = cholmod_amd(&auxCholmodSparse, NULL, 0, _blockPermutation.data(), &_cholmodCommon);
305  if (! amdStatus) {
306  return;
307  }
308 
309  // blow up the permutation to the scalar matrix
310  if (_scalarPermutation.size() == 0)
311  _scalarPermutation.resize(_cholmodSparse->ncol);
312  if (_scalarPermutation.size() < (int)_cholmodSparse->ncol)
313  _scalarPermutation.resize(2*_cholmodSparse->ncol);
314  size_t scalarIdx = 0;
315  for (int i = 0; i < _matrixStructure.n; ++i) {
316  const int& p = _blockPermutation(i);
317  int base = A.colBaseOfBlock(p);
318  int nCols = A.colsOfBlock(p);
319  for (int j = 0; j < nCols; ++j)
320  _scalarPermutation(scalarIdx++) = base++;
321  }
322  assert(scalarIdx == _cholmodSparse->ncol);
323 
324  // apply the ordering
325  _cholmodCommon.nmethods = 1 ;
326  _cholmodCommon.method[0].ordering = CHOLMOD_GIVEN;
327  _cholmodFactor = cholmod_analyze_p(_cholmodSparse, _scalarPermutation.data(), NULL, 0, &_cholmodCommon);
328 
329  }
331  if (globalStats)
332  globalStats->timeSymbolicDecomposition = get_monotonic_time() - t;
333 
334  //const int& bestIdx = _cholmodCommon.selected;
335  //cerr << "# Number of nonzeros in L: " << (int)_cholmodCommon.method[bestIdx].lnz << " by "
336  //<< (_cholmodCommon.method[bestIdx].ordering == CHOLMOD_GIVEN ? "block" : "scalar") << " AMD ordering " << endl;
337 
338  }
339 
340  void fillCholmodExt(const SparseBlockMatrix<MatrixType>& A, bool onlyValues)
341  {
342  if (! onlyValues)
343  this->initMatrixStructure(A);
344  size_t m = A.rows();
345  size_t n = A.cols();
346  assert(m > 0 && n > 0 && "Hessian has 0 rows/cols");
347 
348  if (_cholmodSparse->columnsAllocated < n) {
349  //std::cerr << __PRETTY_FUNCTION__ << ": reallocating columns" << std::endl;
350  _cholmodSparse->columnsAllocated = _cholmodSparse->columnsAllocated == 0 ? n : 2 * n; // pre-allocate more space if re-allocating
351  delete[] (int*)_cholmodSparse->p;
352  _cholmodSparse->p = new int[_cholmodSparse->columnsAllocated+1];
353  }
354  if (! onlyValues) {
355  size_t nzmax = A.nonZeros();
356  if (_cholmodSparse->nzmax < nzmax) {
357  //std::cerr << __PRETTY_FUNCTION__ << ": reallocating row + values" << std::endl;
358  _cholmodSparse->nzmax = _cholmodSparse->nzmax == 0 ? nzmax : 2 * nzmax; // pre-allocate more space if re-allocating
359  delete[] (double*)_cholmodSparse->x;
360  delete[] (int*)_cholmodSparse->i;
361  _cholmodSparse->i = new int[_cholmodSparse->nzmax];
362  _cholmodSparse->x = new double[_cholmodSparse->nzmax];
363  }
364  }
365  _cholmodSparse->ncol = n;
366  _cholmodSparse->nrow = m;
367 
368  if (onlyValues)
369  this->_ccsMatrix->fillCCS((double*)_cholmodSparse->x, true);
370  else
371  this->_ccsMatrix->fillCCS((int*)_cholmodSparse->p, (int*)_cholmodSparse->i, (double*)_cholmodSparse->x, true);
372  }
373 
374 };
375 
376 } // end namespace
377 
378 #endif
const std::vector< int > & rowBlockIndices() const
indices of the row blocks
double get_monotonic_time()
Definition: timeutil.cpp:113
statistics about the optimization
Definition: batch_stats.h:40
int rowsOfBlock(int r) const
how many rows does the block at block-row r has?
virtual bool solvePattern(SparseBlockMatrix< MatrixXD > &spinv, const std::vector< std::pair< int, int > > &blockIndices, const SparseBlockMatrix< MatrixType > &A)
void setCholeskyFactor(int n, int *Lp, int *Li, double *Lx, int *permInv)
int cols() const
columns of the matrix
int * Ap
column pointers for A, of size n+1
computing the marginal covariance given a cholesky factor (lower triangle of the factor) ...
int n
A is m-by-n. n must be >= 0.
bool blockOrdering() const
do the AMD ordering on the blocks or on the scalar matrix
int rows() const
rows of the matrix
representing the structure of a matrix in column compressed structure (only the upper triangular part...
bool writeCCSMatrix(const string &filename, int rows, int cols, const int *Ap, const int *Ai, const double *Ax, bool upperTriangleSymmetric)
int colsOfBlock(int c) const
how many cols does the block at block-col c has?
size_t nonZeros() const
number of non-zero elements
Our extension of the CHOLMOD matrix struct.
void fillBlockStructure(MatrixStructure &ms) const
exports the non zero blocks in the structure matrix ms
size_t choleskyNNZ
number of non-zeros in the cholesky factor
Definition: batch_stats.h:71
utility functions for handling time related stuff
void computeSymbolicDecomposition(const SparseBlockMatrix< MatrixType > &A)
void computeCovariance(double **covBlocks, const std::vector< int > &blockIndices)
bool solve(const SparseBlockMatrix< MatrixType > &A, double *x, double *b)
static G2OBatchStatistics * globalStats()
Definition: batch_stats.h:73
bool solveBlocks(double **&blocks, const SparseBlockMatrix< MatrixType > &A)
double timeNumericDecomposition
numeric decomposition (0 if not done)
Definition: batch_stats.h:58
virtual void setWriteDebug(bool b)
int colBaseOfBlock(int c) const
where does the col at block-col r starts?
basic solver for Ax = b which has to reimplemented for different linear algebra libraries ...
int * Aii
row indices of A, of size nz = Ap [n]
virtual bool saveMatrix(const std::string &fileName)
void fillCholmodExt(const SparseBlockMatrix< MatrixType > &A, bool onlyValues)
Solver with faster iterating structure for the linear matrix.
Definition: linear_solver.h:87
void setBlockOrdering(bool blockOrdering)
Eigen::Matrix< int, Eigen::Dynamic, 1, Eigen::ColMajor > VectorXI
Definition: eigen_types.h:38
virtual bool writeDebug() const
write a debug dump of the system matrix if it is not SPD in solve
int nzMax() const
max number of non-zeros blocks
Sparse matrix which uses blocks.