cyfs_raptorq/
sparse_matrix.rs

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