use super::tables::{BG1, BG2, LIFTING_SETS};
use crate::block::{BlockCode, SoftDecode, container_traits, expect_len};
use crate::interleaving::Interleaver;
use crate::ldpc::{LdpcCode, LdpcDecoder};
use crate::puncture::BitSelection;
use comnum_core::signal::KNOWN_BIT_LLR;
use comnum_core::{Result, ensure};
type BaseGraph = [(u8, u8, [u16; 8])];
#[derive(Clone, Debug)]
pub struct NrLdpc {
bg: u8,
z: usize,
k_info: usize,
e: usize,
map: BitSelection,
code: LdpcCode,
}
fn base_graph(k_info: usize, e: usize) -> u8 {
let r = k_info as f64 / e as f64;
if k_info <= 292 || (k_info <= 3824 && r <= 0.67) || r <= 0.25 { 2 } else { 1 }
}
impl NrLdpc {
pub fn new(k_info: usize, e: usize) -> Result<NrLdpc> {
NrLdpc::with_options(k_info, e, None, 0, 1)
}
pub fn with_options(k_info: usize, e: usize, base_graph_override: Option<u8>, rv: u8, qm: usize) -> Result<NrLdpc> {
ensure(k_info >= 1 && e >= 1, || format!("need K' ≥ 1 and E ≥ 1, got {k_info} and {e}"))?;
ensure(rv <= 3 && qm >= 1 && e % qm == 0, || format!("rv ≤ 3 and Qm | E, got rv {rv}, Qm {qm}, E {e}"))?;
let bg = base_graph_override.unwrap_or_else(|| base_graph(k_info, e));
ensure(bg == 1 || bg == 2, || format!("the base graph is 1 or 2, got {bg}"))?;
let k_cb = if bg == 1 { 8448 } else { 3840 };
ensure(k_info <= k_cb, || {
format!("{k_info} bits exceed one code block of base graph {bg} ({k_cb}); segment the block first")
})?;
let kb = match (bg, k_info) {
(1, _) => 22,
(_, b) if b > 640 => 10,
(_, b) if b > 560 => 9,
(_, b) if b > 192 => 8,
_ => 6,
};
let (z, ils) = LIFTING_SETS
.iter()
.enumerate()
.flat_map(|(i, set)| set.iter().map(move |&z| (z, i)))
.filter(|&(z, _)| kb * z >= k_info)
.min()
.ok_or_else(|| comnum_core::Error::Config(format!("no lifting size for K' = {k_info}")))?;
let (table, cols_k, rows_n): (&BaseGraph, usize, usize) = if bg == 1 { (&BG1, 22, 46) } else { (&BG2, 10, 42) };
let k = cols_k * z;
let n_full = (cols_k + rows_n) * z;
let mut rows: Vec<Vec<usize>> = vec![Vec::new(); rows_n * z];
for &(i, j, v) in table {
let shift = v[ils] as usize % z;
for r in 0..z {
rows[i as usize * z + r].push(j as usize * z + (r + shift) % z);
}
}
let code = LdpcCode::from_core_rows(n_full, rows, k, 4 * z)?;
let ncb = n_full - 2 * z;
let num = match (bg, rv) {
(_, 0) => 0,
(1, 1) => 17,
(1, 2) => 33,
(1, _) => 56,
(_, 1) => 13,
(_, 2) => 25,
_ => 43,
};
let den = if bg == 1 { 66 } else { 50 };
let k0 = num * ncb / (den * z) * z;
let filler = |col: usize| col >= k_info && col < k;
let mut sel = Vec::with_capacity(e);
let mut j = 0;
while sel.len() < e {
let col = 2 * z + (k0 + j) % ncb;
if !filler(col) {
sel.push(col);
}
j += 1;
}
let map = BitSelection::new(Interleaver::block(qm, e / qm).interleave(&sel), code.n())?;
Ok(NrLdpc { bg, z, k_info, e, map, code })
}
pub fn base_graph(&self) -> u8 {
self.bg
}
pub fn lifting_size(&self) -> usize {
self.z
}
pub fn bit_map(&self) -> &[usize] {
self.map.positions()
}
pub fn code(&self) -> &LdpcCode {
&self.code
}
pub fn decoder_mut(&mut self) -> &mut LdpcDecoder {
&mut self.code.decoder
}
pub fn combine(&self, llr: &[f64], buffer: &mut [f64]) -> Result<()> {
expect_len(buffer.len(), self.code.n(), "buffer LLRs")?;
for (b, v) in buffer.iter_mut().zip(self.map.word_llr(llr)?) {
*b += v;
}
Ok(())
}
pub fn decode_buffer(&self, buffer: &[f64]) -> Result<Vec<u8>> {
expect_len(buffer.len(), self.code.n(), "buffer LLRs")?;
let mut full = buffer.to_vec();
for v in full.iter_mut().take(self.code.k()).skip(self.k_info) {
*v = KNOWN_BIT_LLR;
}
let (post, _) = self.code.decode_llr(&full)?;
Ok(comnum_core::signal::hard_decisions(&post[..self.k_info]))
}
fn full_llr(&self, llr: &[f64]) -> Result<Vec<f64>> {
let mut full = self.map.word_llr(llr)?;
for v in full.iter_mut().take(self.code.k()).skip(self.k_info) {
*v = KNOWN_BIT_LLR;
}
Ok(full)
}
}
impl BlockCode for NrLdpc {
fn k(&self) -> usize {
self.k_info
}
fn n(&self) -> usize {
self.e
}
fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
expect_len(info.len(), self.k_info, "bits")?;
let mut u = info.to_vec();
u.resize(self.code.k(), 0);
self.map.select(&self.code.encode_block(&u)?)
}
fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
expect_len(llr.len(), self.e, "LLRs")?;
let (post, _) = self.code.decode_llr(&self.full_llr(llr)?)?;
Ok(comnum_core::signal::hard_decisions(&post[..self.k_info]))
}
}
impl SoftDecode for NrLdpc {
fn app_into(&self, llr: &[f64], apriori: Option<&[f64]>, out: &mut [f64]) -> Result<usize> {
expect_len(llr.len(), self.e, "LLRs")?;
expect_len(out.len(), self.k_info, "a posteriori LLRs")?;
let mut full = self.full_llr(llr)?;
if let Some(a) = apriori {
expect_len(a.len(), self.k_info, "a priori LLRs")?;
for (v, a) in full.iter_mut().zip(a) {
*v += a;
}
}
let (post, it) = self.code.decode_llr(&full)?;
out.copy_from_slice(&post[..self.k_info]);
Ok(it)
}
}
container_traits!(NrLdpc);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn soft_combining_of_redundancy_versions_recovers_what_one_cannot() {
use comnum_core::rng::Rng;
let (k, e) = (480, 528);
let codes: Vec<NrLdpc> =
[0u8, 2, 3, 1].iter().map(|&rv| NrLdpc::with_options(k, e, Some(2), rv, 2).unwrap()).collect();
let mut rng = Rng::new(5);
let info: Vec<u8> = (0..k).map(|_| u8::from(rng.uniform() < 0.5)).collect();
let sigma2: f64 = 1.6;
let mut buffer = vec![0.0; codes[0].code().n()];
let mut first = None;
for code in &codes {
let c = code.encode_block(&info).unwrap();
let llr: Vec<f64> = c
.iter()
.map(|b| {
let y = if *b == 0 { 1.0 } else { -1.0 } + rng.gaussian() * sigma2.sqrt();
2.0 * y / sigma2
})
.collect();
code.combine(&llr, &mut buffer).unwrap();
first.get_or_insert_with(|| code.decode_block(&llr).unwrap() == info);
}
assert_eq!(first, Some(false));
assert_eq!(codes[0].decode_buffer(&buffer).unwrap(), info);
let c = codes[0].encode_block(&info).unwrap();
let llr: Vec<f64> = c.iter().map(|b| if *b == 0 { 3.0 } else { -3.0 }).collect();
let mut one = vec![0.0; codes[0].code().n()];
codes[0].combine(&llr, &mut one).unwrap();
assert_eq!(codes[0].decode_buffer(&one).unwrap(), codes[0].decode_block(&llr).unwrap());
assert!(codes[0].combine(&llr[1..], &mut one).is_err());
}
#[test]
fn the_base_graphs_have_their_dimensions() {
assert_eq!((BG1.len(), BG2.len()), (316, 197));
assert_eq!(BG1.iter().map(|e| e.0).max(), Some(45));
assert_eq!(BG1.iter().map(|e| e.1).max(), Some(67));
assert_eq!(BG2.iter().map(|e| e.0).max(), Some(41));
assert_eq!(BG2.iter().map(|e| e.1).max(), Some(51));
for (t, kb) in [(&BG1[..], 22u8), (&BG2[..], 10)] {
for i in 4..=t.iter().map(|e| e.0).max().unwrap() {
let last = t.iter().filter(|e| e.0 == i).map(|e| e.1).max().unwrap();
assert_eq!(last, kb + i);
}
}
}
#[test]
fn every_lifting_size_encodes_codewords() {
for set in LIFTING_SETS {
for &z in set.iter().filter(|&&z| z <= 64) {
for bg in [1u8, 2] {
let kb = if bg == 1 { 22 } else { 10 };
let k_info = kb * z - 3;
let code = NrLdpc::with_options(k_info, 3 * k_info, Some(bg), 0, 1).unwrap();
assert_eq!(code.lifting_size(), z.max(code.lifting_size()));
let info: Vec<u8> = (0..k_info).map(|i| ((i * 7 + z) % 5 < 2) as u8).collect();
let mut u = info.clone();
u.resize(code.code().k(), 0);
assert!(code.code().is_codeword(&code.code().encode_block(&u).unwrap()));
assert_eq!(code.encode_block(&info).unwrap().len(), 3 * k_info);
}
}
}
assert!(NrLdpc::new(9000, 10000).is_err());
}
#[test]
fn the_selection_gives_the_former_rate_matching() {
let mut rng = comnum_core::rng::Rng::new(21);
for (k_info, e, rv, qm) in [(100, 300, 0, 1), (500, 1200, 2, 4), (1000, 1500, 3, 6), (60, 1000, 1, 2)] {
let code = NrLdpc::with_options(k_info, e, None, rv, qm).unwrap();
let k = code.code.k();
let z = code.z;
let ncb = code.code.n() - 2 * z;
let (num, den) = match (code.bg, rv) {
(_, 0) => (0, 1),
(1, 1) => (17, 66),
(1, 2) => (33, 66),
(1, _) => (56, 66),
(_, 1) => (13, 50),
(_, 2) => (25, 50),
_ => (43, 50),
};
let k0 = num * ncb / (den * z) * z;
let mut sel = Vec::new();
let mut j = 0;
while sel.len() < e {
let col = 2 * z + (k0 + j) % ncb;
if !(col >= k_info && col < k) {
sel.push(col);
}
j += 1;
}
let map: Vec<usize> = (0..e).map(|t| sel[(t % qm) * (e / qm) + t / qm]).collect();
assert_eq!(code.bit_map(), map);
let llr: Vec<f64> = (0..e).map(|_| rng.gaussian()).collect();
let mut full = vec![0.0; code.code.n()];
for (&i, &l) in map.iter().zip(&llr) {
full[i] += l;
}
for v in full.iter_mut().take(k).skip(k_info) {
*v = 30.0;
}
let new = code.full_llr(&llr).unwrap();
assert!(new.iter().zip(&full).all(|(a, b)| a.to_bits() == b.to_bits()));
let (post, _) = code.code.decode_llr(&full).unwrap();
let app = code.app(&llr, None).unwrap();
assert!(app.iter().zip(&post[..k_info]).all(|(a, b)| a.to_bits() == b.to_bits()));
assert_eq!(code.decode_block(&llr).unwrap(), comnum_core::signal::hard_decisions(&post[..k_info]));
}
}
}