raptorq/
constraint_matrix.rs

1#[cfg(feature = "std")]
2use std::vec::Vec;
3
4#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6
7use crate::base::intermediate_tuple;
8use crate::matrix::BinaryMatrix;
9use crate::octet::Octet;
10use crate::octet_matrix::DenseOctetMatrix;
11use crate::rng::rand;
12use crate::systematic_constants::extended_source_block_symbols;
13use crate::systematic_constants::num_hdpc_symbols;
14use crate::systematic_constants::num_intermediate_symbols;
15use crate::systematic_constants::num_ldpc_symbols;
16use crate::systematic_constants::num_lt_symbols;
17use crate::systematic_constants::num_pi_symbols;
18use crate::systematic_constants::{calculate_p1, systematic_index};
19
20// Simulates Enc[] function to get indices of accessed intermediate symbols, as defined in section 5.3.5.3
21#[allow(clippy::many_single_char_names)]
22pub fn enc_indices(
23    source_tuple: (u32, u32, u32, u32, u32, u32),
24    lt_symbols: u32,
25    pi_symbols: u32,
26    p1: u32,
27) -> Vec<usize> {
28    let w = lt_symbols;
29    let p = pi_symbols;
30    let (d, a, mut b, d1, a1, mut b1) = source_tuple;
31
32    assert!(d > 0);
33    assert!(1 <= a && a < w);
34    assert!(b < w);
35    assert!(d1 == 2 || d1 == 3);
36    assert!(1 <= a1 && a1 < p1);
37    assert!(b1 < p1);
38
39    let mut indices = Vec::with_capacity((d + d1) as usize);
40    indices.push(b as usize);
41
42    for _ in 1..d {
43        b = (b + a) % w;
44        indices.push(b as usize);
45    }
46
47    while b1 >= p {
48        b1 = (b1 + a1) % p1;
49    }
50
51    indices.push((w + b1) as usize);
52
53    for _ in 1..d1 {
54        b1 = (b1 + a1) % p1;
55        while b1 >= p {
56            b1 = (b1 + a1) % p1;
57        }
58        indices.push((w + b1) as usize);
59    }
60
61    indices
62}
63
64#[allow(non_snake_case)]
65fn generate_hdpc_rows(Kprime: usize, S: usize, H: usize) -> DenseOctetMatrix {
66    let mut matrix = DenseOctetMatrix::new(H, Kprime + S + H, 0);
67    // Compute G_HDPC using recursive formulation, since this is much faster than a
68    // naive matrix multiplication approach
69
70    let mut result: Vec<Vec<u8>> = vec![vec![0; Kprime + S]; H];
71    // Initialize the last column to alpha^i, which comes from multiplying the last column of MT
72    // with the lower right 1 in GAMMA
73    #[allow(clippy::needless_range_loop)]
74    for i in 0..H {
75        result[i][Kprime + S - 1] = Octet::alpha(i).byte();
76    }
77
78    // Now compute the rest of G_HDPC.
79    // Note that for each row in GAMMA, i'th col = alpha * (i + 1)'th col
80    // Therefore we can compute this right to left, by multiplying by alpha each time, and adding
81    // the Rand() entries which will be associatively handled
82    for j in (0..=(Kprime + S - 2)).rev() {
83        #[allow(clippy::needless_range_loop)]
84        for i in 0..H {
85            result[i][j] = (Octet::alpha(1) * Octet::new(result[i][j + 1])).byte();
86        }
87        let rand6 = rand((j + 1) as u32, 6u32, H as u32) as usize;
88        let rand7 = rand((j + 1) as u32, 7u32, (H - 1) as u32) as usize;
89        let i1 = rand6;
90        let i2 = (rand6 + rand7 + 1) % H;
91        result[i1][j] ^= Octet::one().byte();
92        result[i2][j] ^= Octet::one().byte();
93    }
94
95    // Copy G_HDPC into matrix
96    #[allow(clippy::needless_range_loop)]
97    for i in 0..H {
98        #[allow(clippy::needless_range_loop)]
99        for j in 0..(Kprime + S) {
100            if result[i][j] != 0 {
101                matrix.set(i, j, Octet::new(result[i][j]));
102            }
103        }
104    }
105
106    // I_H
107    for i in 0..H {
108        matrix.set(i, i + (Kprime + S), Octet::one());
109    }
110
111    matrix
112}
113
114// See section 5.3.3.4.2
115// Returns the HDPC rows separately. These logically replace the rows S..(S + H) of the constraint
116// matrix. They are returned separately to allow easier optimizations.
117#[allow(non_snake_case)]
118pub fn generate_constraint_matrix<T: BinaryMatrix>(
119    source_block_symbols: u32,
120    encoded_symbol_indices: &[u32],
121) -> (T, DenseOctetMatrix) {
122    let Kprime = extended_source_block_symbols(source_block_symbols) as usize;
123    let S = num_ldpc_symbols(source_block_symbols) as usize;
124    let H = num_hdpc_symbols(source_block_symbols) as usize;
125    let W = num_lt_symbols(source_block_symbols) as usize;
126    let B = W - S;
127    let P = num_pi_symbols(source_block_symbols) as usize;
128    let L = num_intermediate_symbols(source_block_symbols) as usize;
129
130    assert!(S + H + encoded_symbol_indices.len() >= L);
131    let mut matrix = T::new(S + H + encoded_symbol_indices.len(), L, P);
132
133    // G_LDPC,1
134    // See section 5.3.3.3
135    for i in 0..B {
136        let a = 1 + i / S;
137
138        let b = i % S;
139        matrix.set(b, i, Octet::one());
140
141        let b = (b + a) % S;
142        matrix.set(b, i, Octet::one());
143
144        let b = (b + a) % S;
145        matrix.set(b, i, Octet::one());
146    }
147
148    // I_S
149    for i in 0..S {
150        matrix.set(i, i + B, Octet::one());
151    }
152
153    // G_LDPC,2
154    // See section 5.3.3.3
155    for i in 0..S {
156        matrix.set(i, (i % P) + W, Octet::one());
157        matrix.set(i, ((i + 1) % P) + W, Octet::one());
158    }
159
160    // G_ENC
161    let lt_symbols = num_lt_symbols(Kprime as u32);
162    let pi_symbols = num_pi_symbols(Kprime as u32);
163    let sys_index = systematic_index(Kprime as u32);
164    let p1 = calculate_p1(Kprime as u32);
165    for (row, &i) in encoded_symbol_indices.iter().enumerate() {
166        // row != i, because i is the ESI
167        let tuple = intermediate_tuple(i, lt_symbols, sys_index, p1);
168
169        for j in enc_indices(tuple, lt_symbols, pi_symbols, p1) {
170            matrix.set(row + S + H, j, Octet::one());
171        }
172    }
173
174    (matrix, generate_hdpc_rows(Kprime, S, H))
175}
176
177#[cfg(feature = "std")]
178#[cfg(test)]
179mod tests {
180    use crate::constraint_matrix::generate_hdpc_rows;
181    use crate::octet::Octet;
182    use crate::octet_matrix::DenseOctetMatrix;
183    use crate::octets::{add_assign, fused_addassign_mul_scalar};
184    use crate::rng::rand;
185    use crate::systematic_constants::{
186        extended_source_block_symbols, num_hdpc_symbols, num_ldpc_symbols,
187    };
188    use rand::Rng;
189    use std::vec::Vec;
190
191    #[allow(non_snake_case)]
192    fn reference_generate_hdpc_rows(Kprime: usize, S: usize, H: usize) -> DenseOctetMatrix {
193        let mut matrix = DenseOctetMatrix::new(H, Kprime + S + H, 0);
194        // G_HDPC
195
196        // Generates the MT matrix
197        // See section 5.3.3.3
198        let mut mt: Vec<Vec<u8>> = vec![vec![0; Kprime + S]; H];
199        #[allow(clippy::needless_range_loop)]
200        for i in 0..H {
201            #[allow(clippy::needless_range_loop)]
202            for j in 0..=(Kprime + S - 2) {
203                let rand6 = rand((j + 1) as u32, 6u32, H as u32) as usize;
204                let rand7 = rand((j + 1) as u32, 7u32, (H - 1) as u32) as usize;
205                if i == rand6 || i == (rand6 + rand7 + 1) % H {
206                    mt[i][j] = 1;
207                }
208            }
209            mt[i][Kprime + S - 1] = Octet::alpha(i).byte();
210        }
211        // Multiply by the GAMMA matrix
212        // See section 5.3.3.3
213        let mut gamma_row = vec![0; Kprime + S];
214        // We only create the last row of the GAMMA matrix, as all preceding rows are just a shift left
215        #[allow(clippy::needless_range_loop)]
216        for j in 0..(Kprime + S) {
217            // The spec says "alpha ^^ (i-j)". However, this clearly can overflow since alpha() is
218            // only defined up to input < 256. Since alpha() only has 255 unique values, we must
219            // take the input mod 255. Without this the constraint matrix ends up being singular
220            // for 1698 and 8837 source symbols.
221            gamma_row[j] = Octet::alpha((Kprime + S - 1 - j) % 255).byte();
222        }
223        #[allow(clippy::needless_range_loop)]
224        for i in 0..H {
225            let mut result_row = vec![0; Kprime + S];
226            for j in 0..(Kprime + S) {
227                let scalar = Octet::new(mt[i][j]);
228                if scalar == Octet::zero() {
229                    continue;
230                }
231                if scalar == Octet::one() {
232                    add_assign(
233                        &mut result_row[0..=j],
234                        &gamma_row[(Kprime + S - j - 1)..(Kprime + S)],
235                    );
236                } else {
237                    fused_addassign_mul_scalar(
238                        &mut result_row[0..=j],
239                        &gamma_row[(Kprime + S - j - 1)..(Kprime + S)],
240                        &scalar,
241                    );
242                }
243            }
244            #[allow(clippy::needless_range_loop)]
245            for j in 0..(Kprime + S) {
246                if result_row[j] != 0 {
247                    matrix.set(i, j, Octet::new(result_row[j]));
248                }
249            }
250        }
251
252        // I_H
253        for i in 0..H {
254            matrix.set(i, i + (Kprime + S), Octet::one());
255        }
256
257        matrix
258    }
259
260    fn assert_matrices_eq(matrix1: &DenseOctetMatrix, matrix2: &DenseOctetMatrix) {
261        assert_eq!(matrix1.height(), matrix2.height());
262        assert_eq!(matrix1.width(), matrix2.width());
263        for i in 0..matrix1.height() {
264            for j in 0..matrix1.width() {
265                assert_eq!(
266                    matrix1.get(i, j),
267                    matrix2.get(i, j),
268                    "Matrices are not equal at row={i} col={j}"
269                );
270            }
271        }
272    }
273
274    #[test]
275    #[allow(non_snake_case)]
276    fn fast_hdpc() {
277        let source_block_symbols = rand::thread_rng().gen_range(1..50000);
278        let Kprime = extended_source_block_symbols(source_block_symbols) as usize;
279        let S = num_ldpc_symbols(source_block_symbols) as usize;
280        let H = num_hdpc_symbols(source_block_symbols) as usize;
281        let expected = reference_generate_hdpc_rows(Kprime, S, H);
282        let generated = generate_hdpc_rows(Kprime, S, H);
283        assert_matrices_eq(&expected, &generated);
284    }
285}