matrix_sparse.hpp
Go to the documentation of this file.
1 /*!
2  * \brief Sparse matrix class
3  *
4  * \author O. Krause
5  * \date 2013
6  *
7  *
8  * \par Copyright 1995-2015 Shark Development Team
9  *
10  * <BR><HR>
11  * This file is part of Shark.
12  * <http://image.diku.dk/shark/>
13  *
14  * Shark is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU Lesser General Public License as published
16  * by the Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * Shark is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU Lesser General Public License for more details.
23  *
24  * You should have received a copy of the GNU Lesser General Public License
25  * along with Shark. If not, see <http://www.gnu.org/licenses/>.
26  *
27  */
28 #ifndef REMORA_MATRIX_SPARSE_HPP
29 #define REMORA_MATRIX_SPARSE_HPP
30 
31 #include "assignment.hpp"
32 #include "detail/matrix_proxy_classes.hpp"
33 
34 #include <vector>
35 #include <boost/serialization/vector.hpp>
36 
37 namespace remora{
38 
39 template<class T, class I=std::size_t>
40 class compressed_matrix:public matrix_container<compressed_matrix<T, I>, cpu_tag > {
41  typedef compressed_matrix<T, I> self_type;
42 public:
43  typedef I size_type;
44  typedef T value_type;
45 
46 
47  typedef T const& const_reference;
48  class reference {
49  private:
50  const_reference value()const {
51  return const_cast<self_type const&>(m_matrix)(m_i,m_j);
52  }
53  value_type& ref() const {
54  //get array bounds
55  size_type const *start = m_matrix.m_indices.data() + m_matrix.m_rowStart[m_i];
56  size_type const *end = m_matrix.m_indices.data() + m_matrix.m_rowEnd[m_i];
57  //find position of the index in the array
58  size_type const *pos = std::lower_bound(start,end,m_j);
59 
60  if (pos != end && *pos == m_j)
61  return m_matrix.m_values[(pos-start)+m_matrix.m_rowStart[m_i]];
62  else {
63  //create iterator to the insertion position and insert new element
64  row_iterator posIter(
65  m_matrix.m_values.data(),
66  m_matrix.m_indices.data(),
67  pos-start + m_matrix.m_rowStart[m_i]
68  ,m_i
69  );
70  return *m_matrix.set_element(posIter, m_j, m_matrix.m_zero);
71  }
72  }
73 
74  public:
75  // Construction and destruction
76  reference(compressed_matrix &m, size_type i, size_type j):
77  m_matrix(m), m_i(i), m_j(j) {}
78 
79  // Assignment
80  value_type& operator = (value_type d)const {
81  return ref() = d;
82  }
83  value_type& operator=(reference const & other){
84  return ref() = other.value();
85  }
86  value_type& operator += (value_type d)const {
87  return ref()+=d;
88  }
89  value_type& operator -= (value_type d)const {
90  return ref()-=d;
91  }
92  value_type& operator *= (value_type d)const {
93  return ref()*=d;
94  }
95  value_type& operator /= (value_type d)const {
96  return ref()/=d;
97  }
98 
99  operator const_reference() const {
100  return value();
101  }
102  private:
103  compressed_matrix& m_matrix;
104  size_type m_i;
105  size_type m_j;
106  };
107 
108  typedef matrix_reference<self_type const> const_closure_type;
109  typedef matrix_reference<self_type> closure_type;
110  typedef sparse_matrix_storage<T,I> storage_type;
111  typedef sparse_matrix_storage<value_type const,size_type const> const_storage_type;
112  typedef elementwise<sparse_tag> evaluation_category;
113  typedef row_major orientation;
114 
115  // Construction and destruction
116  compressed_matrix()
117  : m_size1(0), m_size2(0), m_nnz(0)
118  , m_rowStart(1,0), m_indices(0), m_values(0), m_zero(0) {}
119 
120  compressed_matrix(size_type size1, size_type size2, size_type non_zeros = 0)
121  : m_size1(size1), m_size2(size2), m_nnz(0)
122  , m_rowStart(size1 + 1,0)
123  , m_rowEnd(size1,0)
124  , m_indices(non_zeros), m_values(non_zeros), m_zero(0) {}
125 
126  template<class E>
127  compressed_matrix(matrix_expression<E, cpu_tag> const& e, size_type non_zeros = 0)
128  : m_size1(e().size1()), m_size2(e().size2()), m_nnz(0)
129  , m_rowStart(e().size1() + 1, 0)
130  , m_rowEnd(e().size1(), 0)
131  , m_indices(non_zeros), m_values(non_zeros), m_zero(0) {
132  assign(*this, e);
133  }
134 
135  // Accessors
136  size_type size1() const {
137  return m_size1;
138  }
139  size_type size2() const {
140  return m_size2;
141  }
142 
143  std::size_t nnz_capacity() const {
144  return m_values.size();
145  }
146  std::size_t row_capacity(size_type row)const {
147  REMORA_RANGE_CHECK(row < size1());
148  return m_rowStart[row+1] - m_rowStart[row];
149  }
150  std::size_t nnz() const {
151  return m_nnz;
152  }
153  std::size_t inner_nnz(size_type row) const {
154  return m_rowEnd[row] - m_rowStart[row];
155  }
156 
157  ///\brief Returns the underlying storage structure for low level access
158  storage_type raw_storage(){
159  return {m_values.data(),m_indices.data(), m_rowStart.data(), m_rowEnd.data()};
160  }
161 
162  ///\brief Returns the underlying storage structure for low level access
163  const_storage_type raw_storage() const{
164  return {m_values.data(),m_indices.data(), m_rowStart.data(), m_rowEnd.data()};
165  }
166 
167  typename device_traits<cpu_tag>::queue_type& queue(){
168  return device_traits<cpu_tag>::default_queue();
169  }
170 
171  void set_filled(std::size_t non_zeros) {
172  m_nnz = non_zeros;
173  }
174 
175  void set_row_filled(size_type i,std::size_t non_zeros) {
176  REMORA_SIZE_CHECK(i < size1());
177  REMORA_SIZE_CHECK(non_zeros <=row_capacity(i));
178 
179  m_rowEnd[i] = m_rowStart[i]+non_zeros;
180  //correct end pointers
181  if(i == size1()-1)
182  m_rowStart[size1()] = m_rowEnd[i];
183  }
184 
185  void resize(size_type size1, size_type size2) {
186  m_size1 = size1;
187  m_size2 = size2;
188  m_nnz = 0;
189  //clear row data
190  m_rowStart.resize(m_size1 + 1);
191  m_rowEnd.resize(m_size1);
192  std::fill(m_rowStart.begin(),m_rowStart.end(),0);
193  std::fill(m_rowEnd.begin(),m_rowEnd.end(),0);
194  }
195  void reserve(std::size_t non_zeros) {
196  if (non_zeros < nnz_capacity()) return;
197  //non_zeros = std::min(m_size2*m_size1,non_zeros);//this can lead to totally strange errors.
198  m_indices.resize(non_zeros);
199  m_values.resize(non_zeros);
200  }
201 
202  void reserve_row(size_type row, std::size_t non_zeros) {
203  REMORA_RANGE_CHECK(row < size1());
204  non_zeros = std::min(m_size2,non_zeros);
205  if (non_zeros <= row_capacity(row)) return;
206  std::size_t spaceDifference = non_zeros - row_capacity(row);
207 
208  //check if there is place in the end of the container to store the elements
209  if (spaceDifference > nnz_capacity()-m_rowStart.back()) {
210  reserve(nnz_capacity()+std::max<std::size_t>(nnz_capacity(),2*spaceDifference));
211  }
212  //move the elements of the next rows to make room for the reserved space
213  for (size_type i = size1()-1; i != row; --i) {
214  value_type* values = m_values.data() + m_rowStart[i];
215  value_type* valueRowEnd = m_values.data() + m_rowEnd[i];
216  size_type* indices = m_indices.data() + m_rowStart[i];
217  size_type* indicesEnd = m_indices.data() + m_rowEnd[i];
218  std::copy_backward(values,valueRowEnd, valueRowEnd+spaceDifference);
219  std::copy_backward(indices,indicesEnd, indicesEnd+spaceDifference);
220  m_rowStart[i]+=spaceDifference;
221  m_rowEnd[i]+=spaceDifference;
222  }
223  m_rowStart.back() +=spaceDifference;
224  REMORA_SIZE_CHECK(row_capacity(row) == non_zeros);
225  }
226 
227  void clear() {
228  m_nnz = 0;
229  m_rowStart [0] = 0;
230  }
231 
232  // Element access
233  const_reference operator()(size_type i, size_type j) const {
234  REMORA_SIZE_CHECK(i < size1());
235  REMORA_SIZE_CHECK(j < size2());
236  //get array bounds
237  size_type const *start = m_indices.data() + m_rowStart[i];
238  size_type const *end = m_indices.data() + m_rowEnd[i];
239  //find position of the index in the array
240  size_type const *pos = std::lower_bound(start,end,j);
241 
242  if (pos != end && *pos == j)
243  return m_values[(pos-start)+m_rowStart[i]];
244  else
245  return m_zero;
246  }
247 
248  reference operator()(size_type i, size_type j) {
249  REMORA_SIZE_CHECK(i < size1());
250  REMORA_SIZE_CHECK(j < size2());
251  return reference(*this,i,j);
252  }
253 
254  // Assignment
255  template<class C> // Container assignment without temporary
256  compressed_matrix &operator = (matrix_container<C, cpu_tag> const& m) {
257  resize(m().size1(), m().size2());
258  assign(*this, m);
259  return *this;
260  }
261  template<class E>
262  compressed_matrix &operator = (matrix_expression<E, cpu_tag> const& e) {
263  self_type temporary(e, nnz_capacity());
264  swap(temporary);
265  return *this;
266  }
267 
268  // Swapping
269  void swap(compressed_matrix &m) {
270  std::swap(m_size1, m.m_size1);
271  std::swap(m_size2, m.m_size2);
272  std::swap(m_nnz, m.m_nnz);
273  m_rowStart.swap(m.m_rowStart);
274  m_rowEnd.swap(m.m_rowEnd);
275  m_indices.swap(m.m_indices);
276  m_values.swap(m.m_values);
277  }
278 
279  friend void swap(compressed_matrix &m1, compressed_matrix &m2) {
280  m1.swap(m2);
281  }
282 
283  friend void swap_rows(compressed_matrix& a, size_type i, compressed_matrix& b, size_type j) {
284  REMORA_SIZE_CHECK(i < a.size1());
285  REMORA_SIZE_CHECK(j < b.size1());
286  REMORA_SIZE_CHECK(a.size2() == b.size2());
287 
288  //rearrange (i,j) such that i has equal or more elements than j
289  if(a.inner_nnz(i) < b.inner_nnz(j)){
290  swap_rows(b,j,a,i);
291  return;
292  }
293 
294  std::size_t nnzi = a.inner_nnz(i);
295  std::size_t nnzj = b.inner_nnz(j);
296 
297  //reserve enough space for swapping
298  b.reserve_row(j,nnzi);
299  REMORA_SIZE_CHECK(b.row_capacity(j) >= nnzi);
300  REMORA_SIZE_CHECK(a.row_capacity(i) >= nnzj);
301 
302  size_type* indicesi = a.m_indices.data() + a.m_rowStart[i];
303  size_type* indicesj = b.m_indices.data() + b.m_rowStart[j];
304  value_type* valuesi = a.m_values.data() + a.m_rowStart[i];
305  value_type* valuesj = b.m_values.data() + b.m_rowStart[j];
306 
307  //swap all elements of j with the elements in i, don't care about unitialized elements in j
308  std::swap_ranges(indicesi,indicesi+nnzi,indicesj);
309  std::swap_ranges(valuesi, valuesi+nnzi,valuesj);
310 
311  //if both rows had the same number of elements, we are done.
312  if(nnzi == nnzj)
313  return;
314 
315  //otherwise correct end pointers
316  a.set_row_filled(i,nnzj);
317  b.set_row_filled(j,nnzi);
318  }
319 
320  friend void swap_rows(compressed_matrix& a, size_type i, size_type j) {
321  if(i == j) return;
322  swap_rows(a,i,a,j);
323  }
324 
325  typedef iterators::compressed_storage_iterator<value_type const, size_type const> const_row_iterator;
326  typedef iterators::compressed_storage_iterator<value_type, size_type const> row_iterator;
327 
328  const_row_iterator row_begin(size_type i) const {
329  REMORA_SIZE_CHECK(i < size1());
330  REMORA_RANGE_CHECK(m_rowStart[i] <= m_rowEnd[i]);//internal check
331  return const_row_iterator(m_values.data(), m_indices.data(), m_rowStart[i],i);
332  }
333 
334  const_row_iterator row_end(size_type i) const {
335  REMORA_SIZE_CHECK(i < size1());
336  REMORA_RANGE_CHECK(m_rowStart[i] <= m_rowEnd[i]);//internal check
337  return const_row_iterator(m_values.data(), m_indices.data(), m_rowEnd[i],i);
338  }
339 
340  row_iterator row_begin(size_type i) {
341  REMORA_SIZE_CHECK(i < size1());
342  REMORA_RANGE_CHECK(m_rowStart[i] <= m_rowEnd[i]);//internal check
343  return row_iterator(m_values.data(), m_indices.data(), m_rowStart[i],i);
344  }
345 
346  row_iterator row_end(size_type i) {
347  REMORA_SIZE_CHECK(i < size1());
348  REMORA_RANGE_CHECK(m_rowStart[i] <= m_rowEnd[i]);//internal check
349  return row_iterator(m_values.data(), m_indices.data(), m_rowEnd[i],i);
350  }
351 
352  typedef iterators::compressed_storage_iterator<value_type const, size_type const> const_column_iterator;
353  typedef iterators::compressed_storage_iterator<value_type, size_type const> column_iterator;
354 
355  row_iterator set_element(row_iterator pos, size_type index, value_type value) {
356  std::size_t row = pos.row();
357  REMORA_RANGE_CHECK(row < size1());
358  REMORA_RANGE_CHECK(size_type(row_end(row) - pos) <= row_capacity(row));
359  //todo: check in debug, that iterator position is valid
360 
361  //shortcut: element already exists.
362  if (pos != row_end(row) && pos.index() == index) {
363  *pos = value;
364  return pos;
365  }
366 
367  //get position of the element in the array.
368  std::ptrdiff_t arrayPos = (pos - row_begin(row)) + m_rowStart[row];
369 
370  //check that there is enough space in the row. this invalidates pos.
371  if (row_capacity(row) == inner_nnz(row))
372  reserve_row(row,std::max<std::size_t>(2*row_capacity(row),1));
373 
374  //copy the remaining elements further to make room for the new element
375  std::copy_backward(
376  m_values.begin() + arrayPos, m_values.begin() + m_rowEnd[row],
377  m_values.begin() + m_rowEnd[row] + 1
378  );
379  std::copy_backward(
380  m_indices.begin()+arrayPos, m_indices.begin() + m_rowEnd[row],
381  m_indices.begin() + m_rowEnd[row] + 1
382  );
383  //insert new element
384  m_values[arrayPos] = value;
385  m_indices[arrayPos] = index;
386  ++m_rowEnd[row];
387  ++m_nnz;
388 
389  //return new iterator to the inserted element.
390  return row_iterator(m_values.data(), m_indices.data(), arrayPos,row);
391 
392  }
393 
394  row_iterator clear_range(row_iterator start, row_iterator end) {
395  std::size_t row = start.row();
396  REMORA_RANGE_CHECK(row == end.row());
397  //get position of the elements in the array.
398  size_type rowEndPos = m_rowEnd[row];
399  size_type rowStartPos = m_rowStart[row];
400  size_type rangeStartPos = start - row_begin(row)+rowStartPos;
401  size_type rangeEndPos = end - row_begin(row)+rowStartPos;
402  std::ptrdiff_t rangeSize = end - start;
403 
404  //remove the elements in the range
405  std::copy(
406  m_values.begin()+rangeEndPos,m_values.begin() + rowEndPos, m_values.begin() + rangeStartPos
407  );
408  std::copy(
409  m_indices.begin()+rangeEndPos,m_indices.begin() + rowEndPos , m_indices.begin() + rangeStartPos
410  );
411  m_rowEnd[row] -= rangeSize;
412  m_nnz -= rangeSize;
413  //return new iterator to the next element
414  return row_iterator(m_values.data(), m_indices.data(), rangeStartPos,row);
415  }
416 
417  row_iterator clear_element(row_iterator elem) {
418  REMORA_RANGE_CHECK(elem != row_end());
419  row_iterator next = elem;
420  ++next;
421  clear_range(elem,next);
422  }
423 
424  // Serialization
425  template<class Archive>
426  void serialize(Archive &ar, const unsigned int /* file_version */) {
427  ar &boost::serialization::make_nvp("outer_indices", m_rowStart);
428  ar &boost::serialization::make_nvp("outer_indices_end", m_rowEnd);
429  ar &boost::serialization::make_nvp("inner_indices", m_indices);
430  ar &boost::serialization::make_nvp("values", m_values);
431  }
432 
433 private:
434  size_type m_size1;
435  size_type m_size2;
436  size_type m_nnz;
437  std::vector<size_type> m_rowStart;
438  std::vector<size_type> m_rowEnd;
439  std::vector<size_type> m_indices;
440  std::vector<value_type> m_values;
441  value_type m_zero;
442 };
443 
444 template<class T, class O>
445 struct matrix_temporary_type<T,O,sparse_tag, cpu_tag> {
446  typedef compressed_matrix<T> type;
447 };
448 
449 }
450 
451 #endif