IVSparse  v1.0
A sparse matrix compression library.
IVCSC_Private_Methods.hpp
1 
9 #pragma once
10 
11 namespace IVSparse {
12 
13  // Encodes the value type of the matrix in a uint32_t
14  template <typename T, typename indexT, uint8_t compressionLevel, bool columnMajor>
15  void SparseMatrix<T, indexT, compressionLevel, columnMajor>::encodeValueType() {
16  uint8_t byte0 = sizeof(T);
17  uint8_t byte1 = std::is_floating_point<T>::value ? 1 : 0;
18  uint8_t byte2 = std::is_signed_v<T> ? 1 : 0;
19  uint8_t byte3 = columnMajor ? 1 : 0;
20 
21  val_t = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
22  }
23 
24  // Checks if the value type is correct for the matrix
25  template <typename T, typename indexT, uint8_t compressionLevel, bool columnMajor>
26  void SparseMatrix<T, indexT, compressionLevel, columnMajor>::checkValueType() {
27  uint8_t byte0 = val_t & 0xFF;
28  uint8_t byte1 = (val_t >> 8) & 0xFF;
29  uint8_t byte2 = (val_t >> 16) & 0xFF;
30  uint8_t byte3 = (val_t >> 24) & 0xFF;
31  assert(byte0 == sizeof(T) && "Value type size does not match");
32  assert(byte1 == std::is_floating_point_v<T> && "Value type is not floating point");
33  assert(byte2 == std::is_signed_v<T> && "Value type is not signed");
34  assert(byte3 == columnMajor && "Major direction does not match");
35  }
36 
37  // performs some simple user checks on the matrices metadata
38  template <typename T, typename indexT, uint8_t compressionLevel, bool columnMajor>
39  void SparseMatrix<T, indexT, compressionLevel, columnMajor>::userChecks() {
40  assert((innerDim >= 1 || outerDim >= 1) && "The matrix must have at least one row, column, and nonzero value");
41  assert(std::is_floating_point<indexT>::value == false && "The index type must be a non-floating point type");
42  assert((compressionLevel == 3) && "The compression level must be 3");
43  assert((std::is_arithmetic<T>::value && std::is_arithmetic<indexT>::value) && "The value and index types must be numeric types");
44  assert((std::is_same<indexT, bool>::value == false) && "The index type must not be bool");
45  assert((innerDim < std::numeric_limits<indexT>::max() && outerDim < std::numeric_limits<indexT>::max()) && "The number of rows and columns must be less than the maximum value of the index type");
46  checkValueType();
47  }
48 
49  // Calculates the current byte size of the matrix in memory
50  template <typename T, typename indexT, uint8_t compressionLevel, bool columnMajor>
51  void SparseMatrix<T, indexT, compressionLevel, columnMajor>::calculateCompSize() {
52  // set compSize to zero
53  compSize = 0;
54 
55  // add the size of the metadata
56  compSize += META_DATA_SIZE;
57 
58  // add the size of the data pointers
59  compSize += (sizeof(void *) * outerDim) * 2;
60 
61  // add the size of the data itself
62  for (uint32_t i = 0; i < outerDim; i++) { compSize += *((uint8_t **)endPointers + i) - *((uint8_t **)data + i); }
63  }
64 
65  // Compression Algorithm for going from CSC to IVCSC
66  template <typename T, typename indexT, uint8_t compressionLevel, bool columnMajor>
67  template <typename T2, typename indexT2>
68  void SparseMatrix<T, indexT, compressionLevel, columnMajor>::compressCSC(T2 *vals, indexT2 *innerIndices, indexT2 *outerPointers) {
69  // ---- Stage 1: Setup the Matrix ---- //
70 
71  // set the value and index types of the matrix
72  encodeValueType();
73  index_t = sizeof(indexT);
74 
75  // allocate space for metadata
76  metadata = new uint32_t[NUM_META_DATA];
77  metadata[0] = compressionLevel;
78  metadata[1] = innerDim;
79  metadata[2] = outerDim;
80  metadata[3] = nnz;
81  metadata[4] = val_t;
82  metadata[5] = index_t;
83 
84  // run the user checks on the metadata
85  #ifdef CSF_DEBUG
86  userChecks();
87  #endif
88 
89  // allocate space for the data
90  try {
91  data = (void **)malloc(outerDim * sizeof(void *));
92  endPointers = (void **)malloc(outerDim * sizeof(void *));
93  } catch (std::bad_alloc &e) {
94  std::cout << "Error: " << e.what() << std::endl;
95  exit(1);
96  }
97 
98  // ---- Stage 2: Construct the Dictionary For Each Column ---- //
99 
100  // Loop through each column and construct a middle data structre for the matrix
101  #ifdef CSF_PARALLEL
102  #pragma omp parallel for
103  #endif
104  for (uint32_t i = 0; i < outerDim; i++) {
105  // create the data structure to temporarily hold the data
106  std::map<T2, std::vector<indexT2>> dict; // Key = value, Value = vector of indices
107 
108  // check if the current column is empty
109  if (outerPointers[i] == outerPointers[i + 1]) {
110  data[i] = nullptr;
111  endPointers[i] = nullptr;
112  continue;
113  }
114 
115  // loop through each value in the column and add it to dict
116  for (indexT2 j = outerPointers[i]; j < outerPointers[i + 1]; j++) {
117 
118  // check if the value is already in the dictionary or not
119  if (dict.find(vals[j]) != dict.end()) {
120 
121  // add the index to the vector
122 
123  // positive delta encode (PDE)
124  dict[vals[j]].push_back(innerIndices[j] - dict[vals[j]][1]);
125 
126  // update the last index (stored in the second index of the vector)
127  dict[vals[j]][1] = innerIndices[j];
128 
129  // update the maximum delta (stored in the first index of the vector)
130  if (dict[vals[j]][dict[vals[j]].size() - 1] > dict[vals[j]][0]) {
131  dict[vals[j]][0] = dict[vals[j]][dict[vals[j]].size() - 1];
132  }
133  } else {
134  // if value not already in the dictionary add it
135 
136  // create a new vector for the indices
137  dict[vals[j]] = std::vector<indexT2>{innerIndices[j]};
138 
139  // if compression level 3 add the maximum delta and the last index
140  dict[vals[j]].push_back(innerIndices[j]);
141  dict[vals[j]].push_back(innerIndices[j]);
142  }
143 
144  } // end of value loop
145 
146  // ---- Stage 3: Find and Allocate Size of Column Data ---- //
147 
148  // create a variable to hold the size of the column
149  size_t outerByteSize = 0;
150 
151  // loop through dictionary finding the byte size of the total column data
152  for (auto &pair : dict) {
153  // change first value to be byte width of the maximum delta
154  pair.second[0] = byteWidth(pair.second[0]);
155 
156  // add the size of the run to the size of the column
157  //* value + index width + indices * index width + delimiter (index width)
158  outerByteSize += sizeof(T) + 1 + (pair.second[0] * (pair.second.size() - 2)) + pair.second[0];
159  }
160 
161  // allocate space for the column
162  try {
163  data[i] = malloc(outerByteSize);
164  } catch (std::bad_alloc &e) {
165  std::cout << "Error: " << e.what() << std::endl;
166  exit(1);
167  }
168 
169  // ---- Stage 4: Write the Data To Memory ---- //
170 
171  // get a help pointer for moving through raw memory
172  void *helpPtr = data[i];
173 
174  // loop through the dictionary and write to memory
175  for (auto &pair : dict) {
176  // Write the value to memory
177  *(T *)helpPtr = (T)pair.first;
178  helpPtr = (T *)helpPtr + 1;
179 
180  // also write the index width
181  *(uint8_t *)helpPtr = (uint8_t)pair.second[0];
182  helpPtr = (uint8_t *)helpPtr + 1;
183 
184  // loop through the indices and write them to memory
185  for (size_t k = 0; k < pair.second.size(); k++) {
186 
187  // if compression level 3 skip the first two indices and cast the index
188  if (k == 0 || k == 1) { continue; }
189 
190  // create a type of the correct width
191  switch (pair.second[0]) {
192  case 1:
193  *(uint8_t *)helpPtr = (uint8_t)pair.second[k];
194  helpPtr = (uint8_t *)helpPtr + 1;
195  break;
196  case 2:
197  *(uint16_t *)helpPtr = (uint16_t)pair.second[k];
198  helpPtr = (uint16_t *)helpPtr + 1;
199  break;
200  case 4:
201  *(uint32_t *)helpPtr = (uint32_t)pair.second[k];
202  helpPtr = (uint32_t *)helpPtr + 1;
203  break;
204  case 8:
205  *(uint64_t *)helpPtr = (uint64_t)pair.second[k];
206  helpPtr = (uint64_t *)helpPtr + 1;
207  break;
208  }
209 
210  } // End of index loop
211 
212  // write a delimiter of the correct width
213  switch (pair.second[0]) {
214  case 1:
215  *(uint8_t *)helpPtr = (uint8_t)DELIM;
216  helpPtr = (uint8_t *)helpPtr + 1;
217  break;
218  case 2:
219  *(uint16_t *)helpPtr = (uint16_t)DELIM;
220  helpPtr = (uint16_t *)helpPtr + 1;
221  break;
222  case 4:
223  *(uint32_t *)helpPtr = (uint32_t)DELIM;
224  helpPtr = (uint32_t *)helpPtr + 1;
225  break;
226  case 8:
227  *(uint64_t *)helpPtr = (uint64_t)DELIM;
228  helpPtr = (uint64_t *)helpPtr + 1;
229  break;
230  }
231  // Set a pointer to the end of the data
232  endPointers[i] = helpPtr;
233 
234  } // End of dictionary loop
235 
236  } // end of column loop
237 
238  calculateCompSize();
239 
240  } // end of compressCSC
241 
242 } // end of namespace IVSparse