1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
use crate::base::intermediate_tuple;
use crate::matrix::BinaryMatrix;
use crate::octet::Octet;
use crate::octet_matrix::DenseOctetMatrix;
use crate::rng::rand;
use crate::systematic_constants::extended_source_block_symbols;
use crate::systematic_constants::num_hdpc_symbols;
use crate::systematic_constants::num_intermediate_symbols;
use crate::systematic_constants::num_ldpc_symbols;
use crate::systematic_constants::num_lt_symbols;
use crate::systematic_constants::num_pi_symbols;
use crate::systematic_constants::{calculate_p1, systematic_index};

// Simulates Enc[] function to get indices of accessed intermediate symbols, as defined in section 5.3.5.3
#[allow(clippy::many_single_char_names)]
pub fn enc_indices(
    source_tuple: (u32, u32, u32, u32, u32, u32),
    lt_symbols: u32,
    pi_symbols: u32,
    p1: u32,
) -> Vec<usize> {
    let w = lt_symbols;
    let p = pi_symbols;
    let (d, a, mut b, d1, a1, mut b1) = source_tuple;

    assert!(d > 0);
    assert!(1 <= a && a < w);
    assert!(b < w);
    assert!(d1 == 2 || d1 == 3);
    assert!(1 <= a1 && a1 < p1);
    assert!(b1 < p1);

    let mut indices = Vec::with_capacity((d + d1) as usize);
    indices.push(b as usize);

    for _ in 1..d {
        b = (b + a) % w;
        indices.push(b as usize);
    }

    while b1 >= p {
        b1 = (b1 + a1) % p1;
    }

    indices.push((w + b1) as usize);

    for _ in 1..d1 {
        b1 = (b1 + a1) % p1;
        while b1 >= p {
            b1 = (b1 + a1) % p1;
        }
        indices.push((w + b1) as usize);
    }

    indices
}

#[allow(non_snake_case)]
fn generate_hdpc_rows(Kprime: usize, S: usize, H: usize) -> DenseOctetMatrix {
    let mut matrix = DenseOctetMatrix::new(H, Kprime + S + H, 0);
    // Compute G_HDPC using recursive formulation, since this is much faster than a
    // naive matrix multiplication approach

    let mut result: Vec<Vec<u8>> = vec![vec![0; Kprime + S]; H];
    // Initialize the last column to alpha^i, which comes from multiplying the last column of MT
    // with the lower right 1 in GAMMA
    #[allow(clippy::needless_range_loop)]
    for i in 0..H {
        result[i][Kprime + S - 1] = Octet::alpha(i).byte();
    }

    // Now compute the rest of G_HDPC.
    // Note that for each row in GAMMA, i'th col = alpha * (i + 1)'th col
    // Therefore we can compute this right to left, by multiplying by alpha each time, and adding
    // the Rand() entries which will be associatively handled
    for j in (0..=(Kprime + S - 2)).rev() {
        #[allow(clippy::needless_range_loop)]
        for i in 0..H {
            result[i][j] = (Octet::alpha(1) * Octet::new(result[i][j + 1])).byte();
        }
        let rand6 = rand((j + 1) as u32, 6u32, H as u32) as usize;
        let rand7 = rand((j + 1) as u32, 7u32, (H - 1) as u32) as usize;
        let i1 = rand6;
        let i2 = (rand6 + rand7 + 1) % H;
        result[i1][j] ^= Octet::one().byte();
        result[i2][j] ^= Octet::one().byte();
    }

    // Copy G_HDPC into matrix
    #[allow(clippy::needless_range_loop)]
    for i in 0..H {
        #[allow(clippy::needless_range_loop)]
        for j in 0..(Kprime + S) {
            if result[i][j] != 0 {
                matrix.set(i, j, Octet::new(result[i][j]));
            }
        }
    }

    // I_H
    for i in 0..H {
        matrix.set(i, i + (Kprime + S) as usize, Octet::one());
    }

    matrix
}

// See section 5.3.3.4.2
// Returns the HDPC rows separately. These logically replace the rows S..(S + H) of the constraint
// matrix. They are returned separately to allow easier optimizations.
#[allow(non_snake_case)]
pub fn generate_constraint_matrix<T: BinaryMatrix>(
    source_block_symbols: u32,
    encoded_symbol_indices: &[u32],
) -> (T, DenseOctetMatrix) {
    let Kprime = extended_source_block_symbols(source_block_symbols) as usize;
    let S = num_ldpc_symbols(source_block_symbols) as usize;
    let H = num_hdpc_symbols(source_block_symbols) as usize;
    let W = num_lt_symbols(source_block_symbols) as usize;
    let B = W - S;
    let P = num_pi_symbols(source_block_symbols) as usize;
    let L = num_intermediate_symbols(source_block_symbols) as usize;

    assert!(S + H + encoded_symbol_indices.len() >= L);
    let mut matrix = T::new(S + H + encoded_symbol_indices.len(), L, P);

    // G_LDPC,1
    // See section 5.3.3.3
    for i in 0..B {
        let a = 1 + i / S;

        let b = i % S;
        matrix.set(b, i, Octet::one());

        let b = (b + a) % S;
        matrix.set(b, i, Octet::one());

        let b = (b + a) % S;
        matrix.set(b, i, Octet::one());
    }

    // I_S
    for i in 0..S {
        matrix.set(i as usize, i + B as usize, Octet::one());
    }

    // G_LDPC,2
    // See section 5.3.3.3
    for i in 0..S {
        matrix.set(i, (i % P) + W, Octet::one());
        matrix.set(i, ((i + 1) % P) + W, Octet::one());
    }

    // G_ENC
    let lt_symbols = num_lt_symbols(Kprime as u32);
    let pi_symbols = num_pi_symbols(Kprime as u32);
    let sys_index = systematic_index(Kprime as u32);
    let p1 = calculate_p1(Kprime as u32);
    for (row, &i) in encoded_symbol_indices.iter().enumerate() {
        // row != i, because i is the ESI
        let tuple = intermediate_tuple(i, lt_symbols, sys_index, p1);

        for j in enc_indices(tuple, lt_symbols, pi_symbols, p1) {
            matrix.set(row as usize + S + H, j, Octet::one());
        }
    }

    (matrix, generate_hdpc_rows(Kprime, S, H))
}

#[cfg(test)]
mod tests {
    use crate::constraint_matrix::generate_hdpc_rows;
    use crate::octet_matrix::DenseOctetMatrix;
    use crate::octets::{add_assign, fused_addassign_mul_scalar};
    use crate::rng::rand;
    use crate::systematic_constants::{num_hdpc_symbols, num_ldpc_symbols};
    use crate::{extended_source_block_symbols, Octet};
    use rand::Rng;

    #[allow(non_snake_case)]
    fn reference_generate_hdpc_rows(Kprime: usize, S: usize, H: usize) -> DenseOctetMatrix {
        let mut matrix = DenseOctetMatrix::new(H, Kprime + S + H, 0);
        // G_HDPC

        // Generates the MT matrix
        // See section 5.3.3.3
        let mut mt: Vec<Vec<u8>> = vec![vec![0; Kprime + S]; H];
        #[allow(clippy::needless_range_loop)]
        for i in 0..H {
            #[allow(clippy::needless_range_loop)]
            for j in 0..=(Kprime + S - 2) {
                let rand6 = rand((j + 1) as u32, 6u32, H as u32) as usize;
                let rand7 = rand((j + 1) as u32, 7u32, (H - 1) as u32) as usize;
                if i == rand6 || i == (rand6 + rand7 + 1) % H {
                    mt[i][j] = 1;
                }
            }
            mt[i][Kprime + S - 1] = Octet::alpha(i).byte();
        }
        // Multiply by the GAMMA matrix
        // See section 5.3.3.3
        let mut gamma_row = vec![0; Kprime + S];
        // We only create the last row of the GAMMA matrix, as all preceding rows are just a shift left
        #[allow(clippy::needless_range_loop)]
        for j in 0..(Kprime + S) {
            // The spec says "alpha ^^ (i-j)". However, this clearly can overflow since alpha() is
            // only defined up to input < 256. Since alpha() only has 255 unique values, we must
            // take the input mod 255. Without this the constraint matrix ends up being singular
            // for 1698 and 8837 source symbols.
            gamma_row[j] = Octet::alpha((Kprime + S - 1 - j) % 255).byte();
        }
        #[allow(clippy::needless_range_loop)]
        for i in 0..H {
            let mut result_row = vec![0; Kprime + S];
            for j in 0..(Kprime + S) {
                let scalar = Octet::new(mt[i][j]);
                if scalar == Octet::zero() {
                    continue;
                }
                if scalar == Octet::one() {
                    add_assign(
                        &mut result_row[0..=j],
                        &gamma_row[(Kprime + S - j - 1)..(Kprime + S)],
                    );
                } else {
                    fused_addassign_mul_scalar(
                        &mut result_row[0..=j],
                        &gamma_row[(Kprime + S - j - 1)..(Kprime + S)],
                        &scalar,
                    );
                }
            }
            #[allow(clippy::needless_range_loop)]
            for j in 0..(Kprime + S) {
                if result_row[j] != 0 {
                    matrix.set(i, j, Octet::new(result_row[j]));
                }
            }
        }

        // I_H
        for i in 0..H {
            matrix.set(i, i + (Kprime + S) as usize, Octet::one());
        }

        matrix
    }

    fn assert_matrices_eq(matrix1: &DenseOctetMatrix, matrix2: &DenseOctetMatrix) {
        assert_eq!(matrix1.height(), matrix2.height());
        assert_eq!(matrix1.width(), matrix2.width());
        for i in 0..matrix1.height() {
            for j in 0..matrix1.width() {
                assert_eq!(
                    matrix1.get(i, j),
                    matrix2.get(i, j),
                    "Matrices are not equal at row={} col={}",
                    i,
                    j
                );
            }
        }
    }

    #[test]
    #[allow(non_snake_case)]
    fn fast_hdpc() {
        let source_block_symbols = rand::thread_rng().gen_range(1..50000);
        let Kprime = extended_source_block_symbols(source_block_symbols) as usize;
        let S = num_ldpc_symbols(source_block_symbols) as usize;
        let H = num_hdpc_symbols(source_block_symbols) as usize;
        let expected = reference_generate_hdpc_rows(Kprime, S, H);
        let generated = generate_hdpc_rows(Kprime, S, H);
        assert_matrices_eq(&expected, &generated);
    }
}