xerus
a general purpose tensor library
cholmod_wrapper.cpp
Go to the documentation of this file.
1 // Xerus - A General Purpose Tensor Library
2 // Copyright (C) 2014-2017 Benjamin Huber and Sebastian Wolf.
3 //
4 // Xerus is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as published
6 // by the Free Software Foundation, either version 3 of the License,
7 // or (at your option) any later version.
8 //
9 // Xerus is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
13 //
14 // You should have received a copy of the GNU Affero General Public License
15 // along with Xerus. If not, see <http://www.gnu.org/licenses/>.
16 //
17 // For further information on Xerus visit https://libXerus.org
18 // or contact us at contact@libXerus.org.
19 
25 #include <xerus/cholmod_wrapper.h>
26 #include <xerus/index.h>
27 #include <xerus/tensor.h>
30 
31 #include <xerus/misc/check.h>
32 #include <xerus/misc/internal.h>
33 
34 namespace xerus { namespace internal {
35 
36  CholmodCommon::RestrictedAccess::RestrictedAccess(cholmod_common* const _c, std::mutex& _lock)
37  : c(_c), lock(_lock)
38  {
39  lock.lock();
40  }
41 
42  CholmodCommon::RestrictedAccess::operator cholmod_common*() const {
43  return c;
44  }
45 
47  lock.unlock();
48  }
49 
50  static void error_handler(int status, const char *file, int line, const char *message) {
51  if (status < 0) {
52  LOG(fatal, "CHOLMOD had a fatal error in " << file << ":" << line << " (status: " << status << ") msg: " << message);
53  } else {
54  LOG(cholmod_warning, "CHOLMOD warns in " << file << ":" << line << " (status: " << status << ") msg: " << message);
55  }
56  }
57 
58  CholmodCommon::CholmodCommon() : c(new cholmod_common()) {
59  cholmod_l_start(c.get());
60  REQUIRE(c->itype == CHOLMOD_LONG, "atm only cholmod compiled with itype = long is supported...");
61  REQUIRE(c->dtype == CHOLMOD_DOUBLE, "atm only cholmod compiled with dtype = double is supported...");
62  c->error_handler = &error_handler;
63  c->print = 0;
64  REQUIRE(c->status == 0, "unable to initialize CHOLMOD");
65  }
66 
68  cholmod_l_finish(c.get());
69  }
70 
72  return RestrictedAccess(c.get(), lock);
73  }
74 
75  std::function<void(cholmod_sparse*)> CholmodCommon::get_deleter() {
76  return [&](cholmod_sparse* _toDelete) {
77  cholmod_l_free_sparse(&_toDelete, this->get());
78  };
79  }
80 
82 
83 
84 
85  CholmodSparse::CholmodSparse(cholmod_sparse* _matrix) : matrix(_matrix, cholmodObject.get_deleter()) { }
86 
87 
88  CholmodSparse::CholmodSparse(const size_t _m, const size_t _n, const size_t _N)
89  : matrix(cholmod_l_allocate_sparse(_m, _n, _N, 1, 1, 0, CHOLMOD_REAL, cholmodObject.get()), cholmodObject.get_deleter())
90  {
91  REQUIRE(matrix && cholmodObject.c->status == 0, "cholmod_allocate_sparse did not allocate anything... status: " << cholmodObject.c->status << " call: " << _m << " " << _n << " " << _N << " alloc: " << cholmodObject.c->malloc_count);
92  }
93 
94  CholmodSparse::CholmodSparse(const std::map<size_t, double>& _input, const size_t _m, const size_t _n, const bool _transpose)
95  : CholmodSparse(_n, _m, _input.size())
96  {
97  size_t entryPos = 0;
98  long* i = static_cast<long*>(matrix->i);
99  long* p = static_cast<long*>(matrix->p);
100  double* x = static_cast<double*>(matrix->x);
101  i[0] = 0;
102 
103  // create compressed column storage of A^T aka compressed row storage of A
104 
105  long currRow = -1;
106 
107  for(const auto& entry : _input) {
108  x[entryPos] = entry.second;
109  i[entryPos] = static_cast<long>(entry.first%_n);
110  while(currRow < static_cast<long>(entry.first/_n)) {
111  p[++currRow] = long(entryPos);
112  }
113  entryPos++;
114  }
115 
116  REQUIRE(currRow < long(_m) && entryPos == _input.size(), "cholmod error - invalid input? " << currRow << ", " << _m << " | " << entryPos << ", " << _input.size());
117 
118  while(currRow < static_cast<long>(_m)) {
119  p[++currRow] = long(entryPos);
120  }
121 
122  if(!_transpose) {
123  // we didn't want A^T, so transpose the data to get compressed column storage of A
124  transpose();
125  }
126  }
127 
128  std::map<size_t, double> CholmodSparse::to_map(double _alpha) const {
129  std::map<size_t, double> result;
130  long* mi = reinterpret_cast<long*>(matrix->i);
131  long* p = reinterpret_cast<long*>(matrix->p);
132  double* x = reinterpret_cast<double*>(matrix->x);
133 
134  for(size_t i = 0; i < matrix->ncol; ++i) {
135  for(long j = p[i]; j < p[i+1]; ++j) {
136  IF_CHECK( auto ret = ) result.emplace(size_t(mi[size_t(j)])*matrix->ncol+i, _alpha*x[j]);
137  INTERNAL_CHECK(ret.second, "Internal Error");
138  }
139  }
140  return result;
141  }
142 
144  return CholmodSparse(cholmod_l_ssmult(matrix.get(), _rhs.matrix.get(), 0, 1, 1, cholmodObject.get()));
145  }
146 
148  ptr_type newM(cholmod_l_allocate_sparse(matrix->ncol, matrix->nrow, matrix->nzmax, 1, 1, 0, CHOLMOD_REAL, cholmodObject.get()), cholmodObject.get_deleter());
149  cholmod_l_transpose_unsym(matrix.get(), 1, nullptr, nullptr, 0, newM.get(), cholmodObject.get());
150  matrix = std::move(newM);
151  }
152 
153 
154  void CholmodSparse::matrix_matrix_product( std::map<size_t, double>& _C,
155  const size_t _leftDim,
156  const size_t _rightDim,
157  const double _alpha,
158  const std::map<size_t, double>& _A,
159  const bool _transposeA,
160  const size_t _midDim,
161  const std::map<size_t, double>& _B,
162  const bool _transposeB )
163  {
164 // LOG(ssmult, _leftDim << " " << _midDim << " " << _rightDim << " " << _transposeA << " " << _transposeB);
165  const CholmodSparse lhsCs(_A, _transposeA?_midDim:_leftDim, _transposeA?_leftDim:_midDim, _transposeA);
166  const CholmodSparse rhsCs(_B, _transposeB?_rightDim:_midDim, _transposeB?_midDim:_rightDim, _transposeB);
167  const CholmodSparse resultCs = lhsCs * rhsCs;
168  _C = resultCs.to_map(_alpha);
169  }
170 
171  void CholmodSparse::solve_sparse_rhs(std::map<size_t, double>& _x,
172  size_t _xDim,
173  const std::map<size_t, double>& _A,
174  const bool _transposeA,
175  const std::map<size_t, double>& _b,
176  size_t _bDim)
177  {
178  const CholmodSparse A(_A, _transposeA?_xDim:_bDim, _transposeA?_bDim:_xDim, _transposeA);
179  const CholmodSparse b(_b, 1, _bDim, true); // avoids transpose call by giving transposed dimensions
180  CholmodSparse x(SuiteSparseQR<double>(0, xerus::EPSILON, A.matrix.get(), b.matrix.get(), cholmodObject.get()));
181  _x = x.to_map();
182  }
183 
184 
186  size_t _xDim,
187  const std::map<size_t, double>& _A,
188  const bool _transposeA,
189  const double* _b,
190  size_t _bDim)
191  {
192  REQUIRE(_xDim < std::numeric_limits<long>::max() && _bDim < std::numeric_limits<long>::max() && _A.size() < std::numeric_limits<long>::max(),
193  "sparse matrix given to qc decomposition too large for suitesparse"
194  );
195  const CholmodSparse A(_A, _transposeA?_xDim:_bDim, _transposeA?_bDim:_xDim, _transposeA);
196  cholmod_dense b{
197  _bDim, 1, _bDim, _bDim,
198  static_cast<void*>(const_cast<double*>(_b)), nullptr,
199  CHOLMOD_REAL, CHOLMOD_DOUBLE
200  };
201  std::unique_ptr<cholmod_dense, std::function<void(cholmod_dense*)>> x(
202  SuiteSparseQR<double>(A.matrix.get(), &b, cholmodObject.get()),
203  [](cholmod_dense* _toDelete) {
204  cholmod_l_free_dense(&_toDelete, cholmodObject.get());
205  }
206  );
207  INTERNAL_CHECK(x->z == nullptr, "IE");
208  INTERNAL_CHECK(x->nrow == _xDim, "IE");
209  misc::copy(_x, static_cast<double*>(x->x), _xDim);
210  }
211 
212 
213  std::tuple<std::map<size_t, double>, std::map<size_t, double>, size_t> CholmodSparse::qc(
214  const std::map<size_t, double> &_A,
215  const bool _transposeA,
216  size_t _m,
217  size_t _n,
218  bool _fullrank)
219  {
220  REQUIRE(_m < std::numeric_limits<long>::max() && _n < std::numeric_limits<long>::max() && _A.size() < std::numeric_limits<long>::max(),
221  "sparse matrix given to qc decomposition too large for suitesparse"
222  );
223  REQUIRE(_m>0 && _n>0, "invalid matrix of dimensions " << _m << 'x' << _n);
224  CholmodSparse A(_A, _m, _n, _transposeA);
225  cholmod_sparse *Q, *R;
226  SuiteSparse_long *E;
227  long rank = SuiteSparseQR<double>(0, xerus::EPSILON, _fullrank?long(std::min(_m,_n)):1, A.matrix.get(), &Q, &R, &E, cholmodObject.get());
228  rank = std::max(rank, 1l);
229  CholmodSparse Qs(Q);
230  CholmodSparse Rs(R);
231  INTERNAL_CHECK(E == nullptr, "IE: sparse QR returned a permutation despite fixed ordering?!");
232  INTERNAL_CHECK(rank>=0, "SuiteSparseQR returned negative value " << rank);
233  INTERNAL_CHECK((_fullrank?std::min(_m,_n):size_t(rank)) == Qs.matrix->ncol, "IE: strange rank deficiency after sparse qr " << (_fullrank?long(std::min(_m,_n)):rank) << " vs " << Qs.matrix->ncol);
234  return std::make_tuple(Qs.to_map(), Rs.to_map(), _fullrank?std::min(_m,_n):size_t(rank));
235  }
236 
237  std::tuple<std::map<size_t, double>, std::map<size_t, double>, size_t> CholmodSparse::cq(
238  const std::map<size_t, double> &_A,
239  const bool _transposeA,
240  size_t _m,
241  size_t _n,
242  bool _fullrank)
243  {
244  REQUIRE(_m < std::numeric_limits<long>::max() && _n < std::numeric_limits<long>::max() && _A.size() < std::numeric_limits<long>::max(),
245  "sparse matrix given to qc decomposition too large for suitesparse"
246  );
247  CholmodSparse A(_A, _n, _m, !_transposeA);
248  cholmod_sparse *Q, *R;
249  SuiteSparse_long *E;
250  // decompose A^T = q^T*r^T
251  long rank = SuiteSparseQR<double>(0, xerus::EPSILON, _fullrank?long(std::min(_m,_n)):1, A.matrix.get(), &Q, &R, &E, cholmodObject.get());
252  rank = std::max(rank, 1l);
253  CholmodSparse Qs(Q);
254  CholmodSparse Rs(R);
255  INTERNAL_CHECK(E == nullptr, "IE: sparse QR returned a permutation despite fixed ordering?!");
256  INTERNAL_CHECK(rank>=0, "SuiteSparseQR returned negative value " << rank);
257  INTERNAL_CHECK((_fullrank?std::min(_m,_n):size_t(rank)) == Qs.matrix->ncol, "IE: strange rank deficiency after sparse qr " << (_fullrank?long(std::min(_m,_n)):rank) << " vs " << Qs.matrix->ncol);
258  //transpose q and r to get r*q=A
259  Qs.transpose();
260  Rs.transpose();
261  return std::make_tuple(Rs.to_map(), Qs.to_map(), _fullrank?std::min(_m,_n):size_t(rank));
262  }
263 
264 
265 } // namespace internal
266 } // namespace xerus
Header file for CHECK and REQUIRE macros.
thread_local CholmodCommon cholmodObject
Header file for the performance analysis global objects and analysis function.
std::function< void(cholmod_sparse *)> get_deleter()
std::map< size_t, double > to_map(double _alpha=1.0) const
Transforms a cholmod sparse matrix to sparse Tensor format.
Header file for the Index class.
#define REQUIRE
Definition: internal.h:33
#define LOG
Definition: internal.h:38
RestrictedAccess(cholmod_common *const _c, std::mutex &_lock)
#define IF_CHECK
Definition: internal.h:35
The main namespace of xerus.
Definition: basic.h:37
CholmodSparse operator*(const CholmodSparse &_rhs) const
Calculates the Matrix Matrix product with another sparse matrix.
Header file for the Tensor class.
wrapper class for the cholmod sparse matrix objects
void transpose()
transposes the represented sparse matrix.
static std::tuple< std::map< size_t, double >, std::map< size_t, double >, size_t > cq(const std::map< size_t, double > &_A, const bool _transposeA, size_t _m, size_t _n, bool _fullrank)
calculates the sparse CQ decomposition
wrapper object for the cholmod_common struct to automatically call the constructor and destructor ...
std::unique_ptr< cholmod_common > c
Header file for the low level array support function.
Header file for comfort functions and macros that should not be exported in the library.
constexpr const value_t EPSILON
The default epsilon value in xerus.
Definition: basic.h:50
static void solve_dense_rhs(double *_x, size_t _xDim, const std::map< size_t, double > &_A, const bool _transposeA, const double *_b, size_t _bDim)
solve operator / for dense right hand sites
CholmodSparse(cholmod_sparse *_matrix)
stores the given cholmod_sparse object in this wrapper. NOTE: the object must have been created by th...
Header file for suitesparse wrapper functions.
static void matrix_matrix_product(std::map< size_t, double > &_C, const size_t _leftDim, const size_t _rightDim, const double _alpha, const std::map< size_t, double > &_A, const bool _transposeA, const size_t _midDim, const std::map< size_t, double > &_B, const bool _transposeB)
Calculates the Matrix Matrix product between two sparse matrices.
std::unique_ptr< cholmod_sparse, std::function< void(cholmod_sparse *)> > ptr_type
void copy(T *const __restrict _dest, const T *const __restrict _src, const size_t _n) noexcept
Copys _n entries from _y to _x, where _y and _x must be disjunkt memory regions.
static std::tuple< std::map< size_t, double >, std::map< size_t, double >, size_t > qc(const std::map< size_t, double > &_A, const bool _transposeA, size_t _m, size_t _n, bool _fullrank)
calculates the sparse QR decomposition (or qc if fullrank = false)
#define INTERNAL_CHECK
Definition: internal.h:37
static void error_handler(int status, const char *file, int line, const char *message)
static void solve_sparse_rhs(std::map< size_t, double > &_x, size_t _xDim, const std::map< size_t, double > &_A, const bool _transposeA, const std::map< size_t, double > &_b, size_t _bDim)
solve operator / for sparse right hand sites