use crate::algebraic::{berlekamp_massey, chien_search};
use crate::block::{BlockCode, container_traits, expect_len};
use comnum_core::signal::hard_decisions;
use comnum_core::{Result, ensure};
use comnum_math::gf2::{poly_mul, poly_rem};
use comnum_math::gf2m::Field;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Bch {
field: Field,
t: usize,
n: usize,
generator: Vec<u8>,
}
impl Bch {
pub fn new(m: u32, poly: u32, t: usize, n: usize) -> Result<Bch> {
let field = Field::new(m, poly)?;
ensure(t >= 1, || "BCH: t must be at least 1".into())?;
let mut generator = vec![1u8];
let mut used: Vec<Vec<u8>> = Vec::new();
for i in (1..2 * t).step_by(2) {
let mi = field.minimal_polynomial(i);
if !used.contains(&mi) {
generator = poly_mul(&generator, &mi);
used.push(mi);
}
}
let r = generator.len() - 1;
ensure(n <= field.order() && n > r, || {
format!("BCH: the length {n} must exceed the {r} parity bits and not exceed {}", field.order())
})?;
Ok(Bch { field, t, n, generator })
}
pub fn n(&self) -> usize {
self.n
}
pub fn k(&self) -> usize {
self.n + 1 - self.generator.len()
}
pub fn t(&self) -> usize {
self.t
}
pub fn generator(&self) -> &[u8] {
&self.generator
}
pub fn encode(&self, msg: &[u8]) -> Result<Vec<u8>> {
expect_len(msg.len(), self.k(), "bits")?;
let mut c: Vec<u8> = msg.iter().map(|b| b & 1).collect();
c.extend(poly_rem(msg, &self.generator));
Ok(c)
}
pub fn decode(&self, r: &[u8]) -> Result<(Vec<u8>, Option<usize>)> {
expect_len(r.len(), self.n, "bits")?;
let f = &self.field;
let n = self.n;
let two_t = 2 * self.t;
let mut s = vec![0u32; two_t + 1];
for (i, &b) in r.iter().enumerate() {
if b & 1 == 1 {
let e = n - 1 - i;
for (j, sj) in s.iter_mut().enumerate().skip(1) {
*sj ^= f.pow(j * e);
}
}
}
let k = self.k();
let msg = r[..k].iter().map(|b| b & 1).collect::<Vec<u8>>();
if s.iter().all(|&v| v == 0) {
return Ok((msg, Some(0)));
}
let (lambda, _) = berlekamp_massey(f, &s[1..]);
let degree = lambda.len() - 1;
if degree > self.t {
return Ok((msg, None));
}
let order = f.order();
let roots = chien_search(f, &lambda, n, |i| (n - 1 - i) % order);
if roots.len() != degree {
return Ok((msg, None));
}
let mut out: Vec<u8> = r.iter().map(|b| b & 1).collect();
for &i in &roots {
out[i] ^= 1;
}
let found = roots.len();
out.truncate(k);
Ok((out, Some(found)))
}
}
impl BlockCode for Bch {
fn k(&self) -> usize {
Bch::k(self)
}
fn n(&self) -> usize {
Bch::n(self)
}
fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
self.encode(info)
}
fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
expect_len(llr.len(), Bch::n(self), "LLRs")?;
Ok(self.decode(&hard_decisions(llr))?.0)
}
}
container_traits!(Bch);
#[cfg(test)]
mod tests {
use super::*;
use comnum_core::rng::Rng;
fn former_encode(code: &Bch, msg: &[u8]) -> Vec<u8> {
let r = code.generator.len() - 1;
let words = r.div_ceil(64);
let mut g = vec![0u64; words];
for (d, &b) in code.generator[..r].iter().enumerate() {
if b == 1 {
g[d / 64] |= 1 << (d % 64);
}
}
let mut reg = vec![0u64; words];
let top = r - 1;
for &b in msg {
let fb = ((reg[top / 64] >> (top % 64)) & 1) as u8 ^ (b & 1);
for w in (0..words).rev() {
let carry = if w > 0 { reg[w - 1] >> 63 } else { 0 };
reg[w] = (reg[w] << 1) | carry;
}
if r % 64 != 0 {
reg[words - 1] &= (1u64 << (r % 64)) - 1;
}
if fb == 1 {
for (x, y) in reg.iter_mut().zip(&g) {
*x ^= y;
}
}
}
let mut c: Vec<u8> = msg.iter().map(|b| b & 1).collect();
c.extend((0..r).rev().map(|d| ((reg[d / 64] >> (d % 64)) & 1) as u8));
c
}
#[test]
fn the_codewords_are_those_of_the_former_encoder() {
let mut rng = Rng::new(17);
for (m, poly, t, n) in
[(5, 0b10_0101, 2, 31), (16, 0x1_002D, 12, 16_200), (16, 0x1_002D, 4, 1000), (8, 0x11D, 8, 255)]
{
let code = Bch::new(m, poly, t, n).unwrap();
for _ in 0..3 {
let msg: Vec<u8> = (0..code.k()).map(|_| rng.bit()).collect();
assert_eq!(code.encode(&msg).unwrap(), former_encode(&code, &msg), "m {m}, t {t}, n {n}");
}
}
}
#[test]
fn the_generators_of_dvb_s2_are_the_products_of_the_published_polynomials() {
let table6a: [&[usize]; 12] = [
&[0, 2, 3, 5, 16],
&[0, 1, 4, 5, 6, 8, 16],
&[0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 16],
&[0, 2, 4, 6, 9, 11, 12, 14, 16],
&[0, 1, 2, 3, 5, 8, 9, 10, 11, 12, 16],
&[0, 2, 4, 5, 7, 8, 9, 10, 12, 13, 14, 15, 16],
&[0, 2, 5, 6, 8, 9, 10, 11, 13, 15, 16],
&[0, 1, 2, 5, 6, 8, 9, 12, 13, 14, 16],
&[0, 5, 7, 9, 10, 11, 16],
&[0, 1, 2, 5, 7, 8, 10, 12, 13, 14, 16],
&[0, 2, 3, 5, 9, 11, 12, 13, 16],
&[0, 1, 5, 6, 7, 9, 11, 12, 16],
];
let f = Field::new(16, 0b1_0000_0000_0010_1101).unwrap();
for (i, g) in table6a.iter().enumerate() {
let mut p = vec![0u8; 17];
for &d in *g {
p[d] = 1;
}
assert_eq!(f.minimal_polynomial(2 * i + 1), p, "g{}", i + 1);
}
}
#[test]
fn up_to_t_errors_are_corrected_and_more_are_mostly_detected() {
let code = Bch::new(14, 0b100_0000_0010_1011, 12, 7200).unwrap();
assert_eq!(code.k(), 7032);
let mut rng = Rng::new(4);
let mut positions: Vec<usize> = (0..code.n()).collect();
let (mut detected, mut wrong) = (0, 0);
for errors in (0..=12).chain((13..=24).flat_map(|e| [e; 4])) {
let msg: Vec<u8> = (0..code.k()).map(|_| rng.bit()).collect();
let mut c = code.encode(&msg).unwrap();
rng.shuffle(&mut positions);
for &p in &positions[..errors] {
c[p] ^= 1;
}
let (m, n) = code.decode(&c).unwrap();
if errors <= 12 {
assert_eq!((&m, n), (&msg, Some(errors)), "{errors} errors");
} else if n.is_none() {
assert_eq!(m, c[..code.k()], "{errors} errors");
detected += 1;
} else {
assert_ne!(m, msg, "{errors} errors");
wrong += 1;
}
}
assert_eq!((detected, wrong), (48, 0), "{detected} detected, {wrong} miscorrected");
let msg: Vec<u8> = (0..code.k()).map(|_| rng.bit()).collect();
let mut c = code.encode(&msg).unwrap();
for p in [3, 100, 2000, 5000, 7031, 7032, 7100, 7199, 1, 2, 4000, 6000] {
c[p] ^= 1;
}
assert_eq!(code.decode(&c).unwrap(), (msg, Some(12)));
}
}