everscale_raptorq/
sparse_matrix.rs

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