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
//! LDPC systematic encoder.
//!
//! This module implements a systematic encoder for LDPC (n, k) codes in which
//! the parity check matrix H has size (n-k) x n (i.e., has maximum rank), and
//! the square matrix formed by the last n-k columns of H is invertible. For
//! these codes, the encoder uses the first k symbols of the codeword as
//! systematic.
//!
//! There are two cases handled by the encoder, depending on the structure of
//! the parity check matrix H = [H0 H1], where H1 is square. In both cases, the
//! matrix H1 is required to be invertible.
//!
//! The first case is the case of a "staircase-type" LDPC code (this is the case
//! for DVB-S2 codes, for example). In this case, H1 has its main diagonal and
//! the diagonal below filled with ones and no other one elsewhere. An O(n)
//! encoding can be obtained by mutiplying the matrix H0 by the k message bits
//! (as a column vector on the right) and by computing the n-k running sums of
//! the components of the resulting vector of size n-k.
//!
//! In the second case (non staircase-type), the encoder computes G0 =
//! H1^{-1}H0, which in general is a dense matrix. To encode a message, the
//! matrix G0 is multiplied by the k message bits (as a column vector on the
//! right) to obtain the n-k parity check bits. In this case, the encoding
//! complexity is O(n^2).
use crate::{gf2::GF2, sparse::SparseMatrix};
use ndarray::{s, Array1, Array2, ArrayBase, Data, Ix1};
use num_traits::One;
use thiserror::Error;
mod gauss;
mod staircase;
/// LDPC encoder error.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Error)]
pub enum Error {
/// The square submatrix formed by the last columns of the parity check
/// matrix is not invertible, so the encoder cannot be constructed.
#[error("the square matrix formed by the last columns of the parity check is not invertible")]
SubmatrixNotInvertible,
}
/// LDPC systematic encoder.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Encoder {
encoder: EncoderType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum EncoderType {
// Encoder with a general dense generator matrix for the parity.
DenseGenerator { gen_matrix: Array2<GF2> },
// Encoder for a staircase type (repeat-accumulate) code. The encoder sparse
// matrix computes the parity data before accumulation.
Staircase { gen: SparseMatrix },
}
impl Encoder {
/// Creates the systematic encoder corresponding to a parity check matrix.
pub fn from_h(h: &SparseMatrix) -> Result<Encoder, Error> {
let n = h.num_rows();
let m = h.num_cols();
let encoder = if staircase::is_staircase(h) {
// Special encoder for a staircase-type LDPC code.
// If H = [H0 H1] with H0 n x (m-n) and H1 n x n, extract H0 to a
// SparseMatrix
let mut gen = SparseMatrix::new(n, m - n);
for (j, k) in h.iter_all() {
if k < m - n {
gen.insert(j, k);
}
}
EncoderType::Staircase { gen }
} else {
// General case, in which the generator matrix is obtained by
// Gaussian reduction (it will be a dense matrix in general).
// If H = [H0 H1] with H0 n x (m-n) and H1 n x n, then
// A = [H1 H0].
let mut a = Array2::zeros((n, m));
for (j, k) in h.iter_all() {
let t = if k < m - n { k + n } else { k - (m - n) };
a[[j, t]] = GF2::one();
}
match gauss::gauss_reduction(&mut a) {
Ok(()) => (),
Err(gauss::Error::NotInvertible) => return Err(Error::SubmatrixNotInvertible),
};
let gen_matrix = a.slice(s![.., n..]).to_owned();
EncoderType::DenseGenerator { gen_matrix }
};
Ok(Encoder { encoder })
}
/// Encodes a message into a codeword.
pub fn encode<S>(&self, message: &ArrayBase<S, Ix1>) -> Array1<GF2>
where
S: Data<Elem = GF2>,
{
let parity = match &self.encoder {
EncoderType::DenseGenerator { gen_matrix } => gen_matrix.dot(message),
EncoderType::Staircase { gen } => {
// initial parity (needs to be accumulated)
let mut parity = Array1::from_iter(
(0..gen.num_rows()).map(|j| gen.iter_row(j).map(|&k| message[k]).sum()),
);
// Accumulate parity
for j in 1..parity.len() {
let previous = parity[j - 1];
parity[j] += previous;
}
parity
}
};
ndarray::concatenate(ndarray::Axis(0), &[message.view(), parity.view()]).unwrap()
}
}
#[cfg(test)]
mod test {
use super::*;
use num_traits::Zero;
#[test]
fn encode() {
let alist = "12 4
3 9
3 3 3 3 3 3 3 3 3 3 3 3
9 9 9 9
1 2 3
1 3 4
2 3 4
2 3 4
1 2 4
1 2 3
1 3 4
1 2 4
1 2 3
2 3 4
1 2 4
1 3 4
1 2 5 6 7 8 9 11 12
1 3 4 5 6 8 9 10 11
1 2 3 4 6 7 9 10 12
2 3 4 5 7 8 10 11 12
";
let h = SparseMatrix::from_alist(alist).unwrap();
let encoder = Encoder::from_h(&h).unwrap();
let i = GF2::one();
let o = GF2::zero();
let message = [i, o, i, i, o, o, i, o];
let codeword = encoder.encode(&ndarray::arr1(&message));
let expected = [i, o, i, i, o, o, i, o, i, o, o, i];
assert_eq!(&codeword.as_slice().unwrap(), &expected);
let message = [o, i, o, o, i, i, i, o];
let codeword = encoder.encode(&ndarray::arr1(&message));
let expected = [o, i, o, o, i, i, i, o, i, o, i, o];
assert_eq!(&codeword.as_slice().unwrap(), &expected);
}
#[test]
fn encode_staircase() {
let alist = "5 3
2 4
2 2 2 2 1
2 4 4
1 3
2 3
1 2
2 3
3
1 3
2 3 4
1 2 4 5
";
let h = SparseMatrix::from_alist(alist).unwrap();
let encoder = Encoder::from_h(&h).unwrap();
assert!(matches!(encoder.encoder, EncoderType::Staircase { .. }));
let i = GF2::one();
let o = GF2::zero();
let message = [i, o];
let codeword = encoder.encode(&ndarray::arr1(&message));
let expected = [i, o, i, i, o];
assert_eq!(&codeword.as_slice().unwrap(), &expected);
let message = [o, i];
let codeword = encoder.encode(&ndarray::arr1(&message));
let expected = [o, i, o, i, o];
assert_eq!(&codeword.as_slice().unwrap(), &expected);
}
}