Skip to main content

raptorq/
sparse_matrix.rs

1#[cfg(feature = "std")]
2use std::{mem::size_of, vec::Vec};
3
4#[cfg(not(feature = "std"))]
5use core::mem::size_of;
6
7#[cfg(not(feature = "std"))]
8use alloc::vec::Vec;
9
10use crate::arraymap::{ImmutableListMap, ImmutableListMapBuilder};
11use crate::iterators::OctetIter;
12use crate::matrix::BinaryMatrix;
13use crate::octet::Octet;
14use crate::octets::BinaryOctetVec;
15use crate::sparse_vec::SparseBinaryVec;
16use crate::util::get_both_indices;
17
18// Stores a matrix in sparse representation, with an optional dense block for the right most columns
19// The logical storage is as follows:
20// |---------------------------------------|
21// |                          | (optional) |
22// |      sparse rows         | dense      |
23// |                          | columns    |
24// |---------------------------------------|
25#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
26pub struct SparseBinaryMatrix {
27    height: usize,
28    width: usize,
29    sparse_elements: Vec<SparseBinaryVec>,
30    // Note these are stored right aligned, so that the right most element is always at
31    // dense_elements[x] & (1 << 63)
32    dense_elements: Vec<u64>,
33    // Columnar storage of values. Only stores rows that have a 1-valued entry in the given column
34    sparse_columnar_values: Option<ImmutableListMap>,
35    // Mapping of logical row numbers to index in sparse_elements, dense_elements, and sparse_column_index
36    logical_row_to_physical: Vec<u32>,
37    physical_row_to_logical: Vec<u32>,
38    logical_col_to_physical: Vec<u16>,
39    physical_col_to_logical: Vec<u16>,
40    column_index_disabled: bool,
41    // Only include for debug to avoid taking up extra memory in the cache
42    #[cfg(debug_assertions)]
43    debug_indexed_column_valid: Vec<bool>,
44    num_dense_columns: usize,
45}
46
47const WORD_WIDTH: usize = 64;
48
49impl SparseBinaryMatrix {
50    #[cfg(debug_assertions)]
51    fn verify(&self) {
52        if self.column_index_disabled {
53            return;
54        }
55        let columns = self.sparse_columnar_values.as_ref().unwrap();
56        for row in 0..self.height {
57            for (col, value) in self.sparse_elements[row].keys_values() {
58                if value != Octet::zero() {
59                    debug_assert!(columns.get(col as u16).contains(&(row as u32)));
60                }
61            }
62        }
63    }
64
65    // Convert a logical col index to the bit index in the dense columns
66    fn logical_col_to_dense_col(&self, col: usize) -> usize {
67        assert!(col >= self.width - self.num_dense_columns);
68        col - (self.width - self.num_dense_columns)
69    }
70
71    // Returns (word in elements vec, and bit in word) for the given col
72    fn bit_position(&self, row: usize, col: usize) -> (usize, usize) {
73        return (
74            row * self.row_word_width() + self.word_offset(col),
75            (self.left_padding_bits() + col) % WORD_WIDTH,
76        );
77    }
78
79    // Number of words required per row
80    fn row_word_width(&self) -> usize {
81        self.num_dense_columns.div_ceil(WORD_WIDTH)
82    }
83
84    // Returns the number of unused bits on the left of each row
85    fn left_padding_bits(&self) -> usize {
86        (WORD_WIDTH - (self.num_dense_columns % WORD_WIDTH)) % WORD_WIDTH
87    }
88
89    // Return the word in which bit lives, offset from the first for a row
90    fn word_offset(&self, bit: usize) -> usize {
91        (self.left_padding_bits() + bit) / WORD_WIDTH
92    }
93
94    // Returns mask to select the given bit in a word
95    fn select_mask(bit: usize) -> u64 {
96        1u64 << (bit as u64)
97    }
98
99    fn clear_bit(word: &mut u64, bit: usize) {
100        *word &= !SparseBinaryMatrix::select_mask(bit);
101    }
102
103    fn set_bit(word: &mut u64, bit: usize) {
104        *word |= SparseBinaryMatrix::select_mask(bit);
105    }
106}
107
108impl BinaryMatrix for SparseBinaryMatrix {
109    fn new(height: usize, width: usize, trailing_dense_column_hint: usize) -> SparseBinaryMatrix {
110        debug_assert!(height < 16777216);
111        // Matrix width can never exceed maximum L
112        debug_assert!(width < 65536);
113        let mut col_mapping = vec![0; width];
114        let elements = vec![SparseBinaryVec::with_capacity(10); height];
115        let mut row_mapping = vec![0; height];
116        #[allow(clippy::needless_range_loop)]
117        for i in 0..height {
118            row_mapping[i] = i as u32;
119        }
120        #[allow(clippy::needless_range_loop)]
121        for i in 0..width {
122            col_mapping[i] = i as u16;
123        }
124        let dense_elements = if trailing_dense_column_hint > 0 {
125            vec![0; height * ((trailing_dense_column_hint - 1) / WORD_WIDTH + 1)]
126        } else {
127            vec![]
128        };
129        SparseBinaryMatrix {
130            height,
131            width,
132            sparse_elements: elements,
133            dense_elements,
134            sparse_columnar_values: None,
135            logical_row_to_physical: row_mapping.clone(),
136            physical_row_to_logical: row_mapping,
137            logical_col_to_physical: col_mapping.clone(),
138            physical_col_to_logical: col_mapping,
139            column_index_disabled: true,
140            num_dense_columns: trailing_dense_column_hint,
141            #[cfg(debug_assertions)]
142            debug_indexed_column_valid: vec![true; width],
143        }
144    }
145
146    fn set(&mut self, i: usize, j: usize, value: Octet) {
147        let physical_i = self.logical_row_to_physical[i] as usize;
148        let physical_j = self.logical_col_to_physical[j] as usize;
149        if self.width - j <= self.num_dense_columns {
150            let (word, bit) = self.bit_position(physical_i, self.logical_col_to_dense_col(j));
151            if value == Octet::zero() {
152                SparseBinaryMatrix::clear_bit(&mut self.dense_elements[word], bit);
153            } else {
154                SparseBinaryMatrix::set_bit(&mut self.dense_elements[word], bit);
155            }
156        } else {
157            self.sparse_elements[physical_i].insert(physical_j, value);
158            assert!(self.column_index_disabled);
159        }
160    }
161
162    fn height(&self) -> usize {
163        self.height
164    }
165
166    fn width(&self) -> usize {
167        self.width
168    }
169
170    fn count_ones(&self, row: usize, start_col: usize, end_col: usize) -> usize {
171        if end_col > self.width - self.num_dense_columns {
172            unimplemented!(
173                "It was assumed that this wouldn't be needed, because the method would only be called on the V section of matrix A"
174            );
175        }
176        let mut ones = 0;
177        let physical_row = self.logical_row_to_physical[row] as usize;
178        for (physical_col, value) in self.sparse_elements[physical_row].keys_values() {
179            let col = self.physical_col_to_logical[physical_col] as usize;
180            if col >= start_col && col < end_col && value == Octet::one() {
181                ones += 1;
182            }
183        }
184        return ones;
185    }
186
187    fn get_sub_row_as_octets(&self, row: usize, start_col: usize) -> BinaryOctetVec {
188        let first_dense_column = self.width - self.num_dense_columns;
189        assert_eq!(start_col, first_dense_column);
190        // The following implementation is equivalent to .map(|x| self.get(row, x))
191        // but this implementation optimizes for sequential access and avoids all the
192        // extra bit index math
193        let physical_row = self.logical_row_to_physical[row] as usize;
194        let (first_word, _) =
195            self.bit_position(physical_row, self.logical_col_to_dense_col(start_col));
196        let last_word = first_word + self.row_word_width();
197
198        BinaryOctetVec::new(
199            self.dense_elements[first_word..last_word].to_vec(),
200            self.num_dense_columns,
201        )
202    }
203
204    fn query_non_zero_columns(&self, row: usize, start_col: usize) -> Vec<usize> {
205        let mut result = Vec::with_capacity(self.num_dense_columns);
206        self.query_non_zero_columns_into(row, start_col, &mut result);
207        result
208    }
209
210    fn query_non_zero_columns_into(&self, row: usize, start_col: usize, out: &mut Vec<usize>) {
211        // The following implementation is equivalent to .filter(|x| self.get(row, x) != Octet::zero())
212        // but this implementation optimizes for sequential access and avoids all the
213        // extra bit index math
214        assert_eq!(start_col, self.width - self.num_dense_columns);
215        out.clear();
216        out.reserve(self.num_dense_columns);
217        let physical_row = self.logical_row_to_physical[row] as usize;
218        let (mut word, bit) =
219            self.bit_position(physical_row, self.logical_col_to_dense_col(start_col));
220        let mut col = start_col;
221        // Process the first word, which may not be entirely filled, due to left zero padding
222        // Because of the assert that start_col is always the first dense column, the first one
223        // must be the column we're looking for, so they're no need to zero out columns left of it.
224        let mut block = self.dense_elements[word];
225        while block.trailing_zeros() < WORD_WIDTH as u32 {
226            out.push(col + block.trailing_zeros() as usize - bit);
227            block &= !(SparseBinaryMatrix::select_mask(block.trailing_zeros() as usize));
228        }
229        col += WORD_WIDTH - bit;
230        word += 1;
231
232        while col < self.width() {
233            let mut block = self.dense_elements[word];
234            // process the whole word in one shot to improve efficiency
235            while block.trailing_zeros() < WORD_WIDTH as u32 {
236                out.push(col + block.trailing_zeros() as usize);
237                block &= !(SparseBinaryMatrix::select_mask(block.trailing_zeros() as usize));
238            }
239            col += WORD_WIDTH;
240            word += 1;
241        }
242    }
243
244    fn get(&self, i: usize, j: usize) -> Octet {
245        let physical_i = self.logical_row_to_physical[i] as usize;
246        let physical_j = self.logical_col_to_physical[j] as usize;
247        if self.width - j <= self.num_dense_columns {
248            let (word, bit) = self.bit_position(physical_i, self.logical_col_to_dense_col(j));
249            if self.dense_elements[word] & SparseBinaryMatrix::select_mask(bit) == 0 {
250                return Octet::zero();
251            } else {
252                return Octet::one();
253            }
254        } else {
255            return self.sparse_elements[physical_i]
256                .get(physical_j)
257                .unwrap_or_else(Octet::zero);
258        }
259    }
260
261    fn get_row_iter(&self, row: usize, start_col: usize, end_col: usize) -> OctetIter<'_> {
262        if end_col > self.width - self.num_dense_columns {
263            unimplemented!(
264                "It was assumed that this wouldn't be needed, because the method would only be called on the V section of matrix A"
265            );
266        }
267        let physical_row = self.logical_row_to_physical[row] as usize;
268        let sparse_elements = &self.sparse_elements[physical_row];
269        OctetIter::new_sparse(
270            start_col,
271            end_col,
272            sparse_elements,
273            &self.physical_col_to_logical,
274        )
275    }
276
277    fn get_ones_in_column(&self, col: usize, start_row: usize, end_row: usize) -> Vec<u32> {
278        let mut rows = Vec::with_capacity(end_row.saturating_sub(start_row));
279        self.get_ones_in_column_into(col, start_row, end_row, &mut rows);
280        rows
281    }
282
283    fn get_ones_in_column_into(
284        &self,
285        col: usize,
286        start_row: usize,
287        end_row: usize,
288        out: &mut Vec<u32>,
289    ) {
290        assert!(!self.column_index_disabled);
291        #[cfg(debug_assertions)]
292        debug_assert!(self.debug_indexed_column_valid[col]);
293        let physical_col = self.logical_col_to_physical[col];
294        out.clear();
295        out.reserve(end_row.saturating_sub(start_row));
296        for physical_row in self
297            .sparse_columnar_values
298            .as_ref()
299            .unwrap()
300            .get(physical_col)
301        {
302            let logical_row = self.physical_row_to_logical[*physical_row as usize];
303            if start_row <= logical_row as usize && logical_row < end_row as u32 {
304                out.push(logical_row);
305            }
306        }
307    }
308
309    fn swap_rows(&mut self, i: usize, j: usize) {
310        let physical_i = self.logical_row_to_physical[i] as usize;
311        let physical_j = self.logical_row_to_physical[j] as usize;
312        self.logical_row_to_physical.swap(i, j);
313        self.physical_row_to_logical.swap(physical_i, physical_j);
314    }
315
316    fn swap_columns(&mut self, i: usize, j: usize, _: usize) {
317        if j >= self.width - self.num_dense_columns {
318            unimplemented!(
319                "It was assumed that this wouldn't be needed, because the method would only be called on the V section of matrix A"
320            );
321        }
322
323        #[cfg(debug_assertions)]
324        self.debug_indexed_column_valid.swap(i, j);
325
326        let physical_i = self.logical_col_to_physical[i] as usize;
327        let physical_j = self.logical_col_to_physical[j] as usize;
328        self.logical_col_to_physical.swap(i, j);
329        self.physical_col_to_logical.swap(physical_i, physical_j);
330    }
331
332    fn enable_column_access_acceleration(&mut self) {
333        self.column_index_disabled = false;
334        let mut builder = ImmutableListMapBuilder::new(self.height);
335        for (physical_row, elements) in self.sparse_elements.iter().enumerate() {
336            for (physical_col, _) in elements.keys_values() {
337                builder.add(physical_col as u16, physical_row as u32);
338            }
339        }
340        self.sparse_columnar_values = Some(builder.build());
341    }
342
343    fn disable_column_access_acceleration(&mut self) {
344        self.column_index_disabled = true;
345        self.sparse_columnar_values = None;
346    }
347
348    fn hint_column_dense_and_frozen(&mut self, i: usize) {
349        assert_eq!(
350            self.width - self.num_dense_columns - 1,
351            i,
352            "Can only freeze the last sparse column"
353        );
354        assert!(!self.column_index_disabled);
355        self.num_dense_columns += 1;
356        let (last_word, _) = self.bit_position(self.height - 1, self.num_dense_columns - 1);
357        // If this is in a new word
358        if last_word >= self.dense_elements.len() {
359            // Append a new set of words
360            let mut src = self.dense_elements.len();
361            self.dense_elements.extend(vec![0; self.height]);
362            let mut dest = self.dense_elements.len();
363            // Re-space the elements, so that each row has an empty word
364            while src > 0 {
365                src -= 1;
366                dest -= 1;
367                self.dense_elements[dest] = self.dense_elements[src];
368                if dest % self.row_word_width() == 1 {
369                    dest -= 1;
370                    self.dense_elements[dest] = 0;
371                }
372            }
373            assert_eq!(src, 0);
374            assert_eq!(dest, 0);
375        }
376        let physical_i = self.logical_col_to_physical[i] as usize;
377        for maybe_present_in_row in self
378            .sparse_columnar_values
379            .as_ref()
380            .unwrap()
381            .get(physical_i as u16)
382        {
383            let physical_row = *maybe_present_in_row as usize;
384            if let Some(value) = self.sparse_elements[physical_row].remove(physical_i) {
385                let (word, bit) = self.bit_position(physical_row, 0);
386                if value == Octet::zero() {
387                    SparseBinaryMatrix::clear_bit(&mut self.dense_elements[word], bit);
388                } else {
389                    SparseBinaryMatrix::set_bit(&mut self.dense_elements[word], bit);
390                }
391            }
392        }
393    }
394
395    fn add_assign_rows(&mut self, dest: usize, src: usize, start_col: usize) {
396        assert_ne!(dest, src);
397        assert!(
398            start_col == 0 || start_col == self.width - self.num_dense_columns,
399            "start_col must be zero or at the beginning of the U matrix"
400        );
401        let physical_dest = self.logical_row_to_physical[dest] as usize;
402        let physical_src = self.logical_row_to_physical[src] as usize;
403        // First handle the dense columns
404        if self.num_dense_columns > 0 {
405            let (dest_word, _) = self.bit_position(physical_dest, 0);
406            let (src_word, _) = self.bit_position(physical_src, 0);
407            for word in 0..self.row_word_width() {
408                self.dense_elements[dest_word + word] ^= self.dense_elements[src_word + word];
409            }
410        }
411
412        if start_col == 0 {
413            // Then the sparse columns
414            let (dest_row, temp_row) =
415                get_both_indices(&mut self.sparse_elements, physical_dest, physical_src);
416            // This shouldn't be needed, because while column indexing is enabled in first phase,
417            // columns are only eliminated one at a time in sparse section of matrix.
418            assert!(self.column_index_disabled || temp_row.len() == 1);
419
420            let column_added = dest_row.add_assign(temp_row);
421            // This shouldn't be needed, because while column indexing is enabled in first phase,
422            // columns are only removed.
423            assert!(self.column_index_disabled || !column_added);
424
425            #[cfg(debug_assertions)]
426            {
427                if !self.column_index_disabled {
428                    let col = self.physical_col_to_logical[temp_row.get_by_raw_index(0).0];
429                    self.debug_indexed_column_valid[col as usize] = false;
430                }
431            }
432        }
433
434        #[cfg(debug_assertions)]
435        self.verify();
436    }
437
438    fn resize(&mut self, new_height: usize, new_width: usize) {
439        assert!(new_height <= self.height);
440        // Only support same width or removing all the dense columns
441        let mut columns_to_remove = self.width - new_width;
442        assert!(columns_to_remove == 0 || columns_to_remove >= self.num_dense_columns);
443        if !self.column_index_disabled {
444            unimplemented!(
445                "Resize should only be used in phase 2, after column indexing is no longer needed"
446            );
447        }
448        let mut new_sparse = vec![None; new_height];
449        for i in (0..self.sparse_elements.len()).rev() {
450            let logical_row = self.physical_row_to_logical[i] as usize;
451            let sparse = self.sparse_elements.pop();
452            if logical_row < new_height {
453                new_sparse[logical_row] = sparse;
454            }
455        }
456
457        if columns_to_remove == 0 && self.num_dense_columns > 0 {
458            // TODO: optimize to not allocate this extra vec
459            let mut new_dense = vec![0; new_height * self.row_word_width()];
460            for logical_row in 0..new_height {
461                let physical_row = self.logical_row_to_physical[logical_row] as usize;
462                for word in 0..self.row_word_width() {
463                    new_dense[logical_row * self.row_word_width() + word] =
464                        self.dense_elements[physical_row * self.row_word_width() + word];
465                }
466            }
467            self.dense_elements = new_dense;
468        } else {
469            columns_to_remove -= self.num_dense_columns;
470            self.dense_elements.clear();
471            self.num_dense_columns = 0;
472        }
473
474        self.logical_row_to_physical.truncate(new_height);
475        self.physical_row_to_logical.truncate(new_height);
476        for i in 0..new_height {
477            self.logical_row_to_physical[i] = i as u32;
478            self.physical_row_to_logical[i] = i as u32;
479        }
480        for row in new_sparse.drain(0..new_height) {
481            self.sparse_elements.push(row.unwrap());
482        }
483
484        // Next remove sparse columns
485        if columns_to_remove > 0 {
486            let physical_to_logical = &self.physical_col_to_logical;
487            for row in 0..self.sparse_elements.len() {
488                self.sparse_elements[row]
489                    .retain(|(col, _)| physical_to_logical[*col] < new_width as u16);
490            }
491        }
492
493        self.height = new_height;
494        self.width = new_width;
495
496        #[cfg(debug_assertions)]
497        self.verify();
498    }
499
500    fn size_in_bytes(&self) -> usize {
501        let mut bytes = size_of::<Self>();
502        for x in self.sparse_elements.iter() {
503            bytes += x.size_in_bytes();
504        }
505        bytes += size_of::<u64>() * self.dense_elements.len();
506        if let Some(ref columns) = self.sparse_columnar_values {
507            bytes += columns.size_in_bytes();
508        }
509        bytes += size_of::<u32>() * self.logical_row_to_physical.len();
510        bytes += size_of::<u32>() * self.physical_row_to_logical.len();
511        bytes += size_of::<u16>() * self.logical_col_to_physical.len();
512        bytes += size_of::<u16>() * self.physical_col_to_logical.len();
513        #[cfg(debug_assertions)]
514        {
515            bytes += size_of::<bool>() * self.debug_indexed_column_valid.len();
516        }
517
518        bytes
519    }
520}
521
522#[cfg(test)]
523mod tests {
524    use crate::systematic_constants::{MAX_SOURCE_SYMBOLS_PER_BLOCK, num_intermediate_symbols};
525
526    #[test]
527    fn check_max_width_optimization() {
528        // Check that the optimization of limiting matrix width to 2^16 is safe.
529        // Matrix width will never exceed L
530        assert!(num_intermediate_symbols(MAX_SOURCE_SYMBOLS_PER_BLOCK) < 65536);
531    }
532}