use super::tables::{POLAR_INTERLEAVER, POLAR_SEQUENCE, SUBBLOCK_PATTERN};
use crate::block::{BlockCode, container_traits, expect_len};
use crate::crc::Crc;
use crate::polar::PolarCode;
use crate::puncture::BitSelection;
use comnum_core::signal::KNOWN_BIT_LLR;
use comnum_core::{Error, Result, ensure};
#[derive(Clone, Debug)]
pub struct NrPolar {
a: usize,
e: usize,
crc: Crc,
il: Vec<usize>,
map: BitSelection,
shortened: Vec<usize>,
code: PolarCode,
}
struct Direction {
n_pc: usize,
n_pc_wm: usize,
i_il: bool,
i_bil: bool,
n_max: u32,
}
fn ceil_log2(x: usize) -> u32 {
x.next_power_of_two().trailing_zeros()
}
fn mother_length(k: usize, e: usize, n_max: u32) -> usize {
let ce = ceil_log2(e);
let n1 = if ce >= 1 && 8 * e <= 9 * (1 << (ce - 1)) && 16 * k < 9 * e { ce - 1 } else { ce };
let n2 = ceil_log2(8 * k);
1usize << n1.min(n2).min(n_max).max(5)
}
fn rate_matching_frozen(j: &[usize], k: usize, e: usize) -> (bool, Vec<bool>, Vec<usize>) {
let n = j.len();
let mut frozen = vec![false; n];
let puncturing = e < n && 16 * k <= 7 * e;
let mut shortened = Vec::new();
if e < n {
if puncturing {
for &p in &j[..n - e] {
frozen[p] = true;
}
let t = if 4 * e >= 3 * n {
(6 * n - 4 * e).div_ceil(8)
} else {
(9 * n - 4 * e).div_ceil(16)
};
for f in frozen.iter_mut().take(t) {
*f = true;
}
} else {
for &p in &j[e..] {
frozen[p] = true;
shortened.push(p);
}
}
}
(puncturing, frozen, shortened)
}
fn info_and_pc(frozen: &[bool], k: usize, d: &Direction) -> Result<(Vec<usize>, Vec<usize>)> {
let n = frozen.len();
let reliable: Vec<usize> = POLAR_SEQUENCE.iter().map(|&q| q as usize).filter(|&q| q < n && !frozen[q]).collect();
let used: Vec<usize> = reliable[reliable.len() - (k + d.n_pc)..].to_vec();
let mut pc = Vec::new();
if d.n_pc > 0 {
pc.extend_from_slice(&used[..d.n_pc - d.n_pc_wm]);
if d.n_pc_wm > 0 {
let best = used[d.n_pc..]
.iter()
.enumerate()
.min_by_key(|&(r, &p)| (p.count_ones(), std::cmp::Reverse(r)))
.map(|(_, &p)| p)
.ok_or_else(|| comnum_core::Error::Config("no position for the parity check".into()))?;
pc.push(best);
}
}
let info: Vec<usize> = used.iter().copied().filter(|p| !pc.contains(p)).collect();
Ok((info, pc))
}
fn triangular<T: Copy>(bits: &[T]) -> Vec<T> {
let e = bits.len();
let mut t = 0;
while t * (t + 1) / 2 < e {
t += 1;
}
let mut cell = vec![vec![None; t]; t];
let mut idx = 0;
for (i, row) in cell.iter_mut().enumerate() {
for c in row.iter_mut().take(t - i) {
if idx < e {
*c = Some(bits[idx]);
}
idx += 1;
}
}
let mut out = Vec::with_capacity(e);
for col in 0..t {
for row in cell.iter().take(t - col) {
if let Some(v) = row[col] {
out.push(v);
}
}
}
out
}
impl NrPolar {
pub fn from_name(link: &str, a: usize, e: usize) -> Result<NrPolar> {
match link.to_ascii_lowercase().as_str() {
"uplink" => NrPolar::uplink(a, e),
"downlink" => NrPolar::downlink(a, e),
_ => Err(Error::Config(format!("the link is uplink or downlink, got {link:?}"))),
}
}
pub fn uplink(a: usize, e: usize) -> Result<NrPolar> {
ensure(a >= 12, || format!("the uplink polar code takes A ≥ 12 bits, got {a}"))?;
ensure(a < 1013 && !(a >= 360 && e >= 1088), || {
format!("A = {a}, E = {e} needs two code blocks (Sec. 6.3.1.2.1), not supported")
})?;
let (crc, n_pc) = if a <= 19 { (Crc::crc6(), 3) } else { (Crc::crc11(), 0) };
let k = a + crc.width() as usize;
let n_pc_wm = usize::from(n_pc > 0 && e + n_pc > k + 192);
NrPolar::build(a, e, crc, &Direction { n_pc, n_pc_wm, i_il: false, i_bil: true, n_max: 10 })
}
pub fn downlink(a: usize, e: usize) -> Result<NrPolar> {
ensure(a >= 1 && a + 24 <= 164, || format!("the downlink polar code takes 1 ≤ A ≤ 140 bits, got {a}"))?;
NrPolar::build(a, e, Crc::crc24c(), &Direction { n_pc: 0, n_pc_wm: 0, i_il: true, i_bil: false, n_max: 9 })
}
fn build(a: usize, e: usize, crc: Crc, d: &Direction) -> Result<NrPolar> {
let k = a + crc.width() as usize;
ensure(e >= 1, || "E must be positive".to_string())?;
let n = mother_length(k, e, d.n_max);
ensure(k + d.n_pc <= e && k + d.n_pc <= n, || format!("K + n_PC = {} exceeds E = {e} or N = {n}", k + d.n_pc))?;
let b = n / 32;
let j: Vec<usize> = (0..n).map(|m| SUBBLOCK_PATTERN[32 * m / n] as usize * b + m % b).collect();
let (puncturing, frozen, shortened) = rate_matching_frozen(&j, k, e);
let (info, pc) = info_and_pc(&frozen, k, d)?;
let code = PolarCode::with_positions(n, &info, &pc)?;
let il: Vec<usize> = if d.i_il {
POLAR_INTERLEAVER.iter().map(|&m| m as usize).filter(|&m| m >= 164 - k).map(|m| m - (164 - k)).collect()
} else {
(0..k).collect()
};
let sel: Vec<usize> = (0..e)
.map(|t| {
if e >= n {
t % n
} else if puncturing {
t + n - e
} else {
t
}
})
.map(|y| j[y])
.collect();
let map = BitSelection::new(if d.i_bil { triangular(&sel) } else { sel }, n)?;
Ok(NrPolar { a, e, crc, il, map, shortened, code })
}
pub fn with_list(mut self, list: usize) -> Result<NrPolar> {
self.code = self.code.with_list(list)?;
Ok(self)
}
pub fn list(&self) -> usize {
self.code.list()
}
pub fn mother_length(&self) -> usize {
self.code.n()
}
pub fn code(&self) -> &PolarCode {
&self.code
}
pub fn bit_map(&self) -> &[usize] {
self.map.positions()
}
}
impl BlockCode for NrPolar {
fn k(&self) -> usize {
self.a
}
fn n(&self) -> usize {
self.e
}
fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
expect_len(info.len(), self.a, "bits")?;
let c = self.crc.attach(info);
let interleaved: Vec<u8> = self.il.iter().map(|&p| c[p]).collect();
self.map.select(&self.code.encode_block(&interleaved)?)
}
fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
expect_len(llr.len(), self.e, "LLRs")?;
let mut d = self.map.word_llr(llr)?;
for &p in &self.shortened {
d[p] = KNOWN_BIT_LLR;
}
let k = self.il.len();
let mut first = None;
for cand in self.code.decode_list(&d)? {
let mut c = vec![0u8; k];
for (&p, &b) in self.il.iter().zip(&cand) {
c[p] = b;
}
if self.crc.check(&c) {
return Ok(c[..self.a].to_vec());
}
first.get_or_insert(c);
}
Ok(first.map_or_else(|| vec![0; self.a], |c| c[..self.a].to_vec()))
}
}
container_traits!(NrPolar);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_links_are_read_by_name() {
let same = |a: NrPolar, b: NrPolar| a.k() == b.k() && a.n() == b.n() && a.bit_map() == b.bit_map();
assert!(same(NrPolar::from_name("Uplink", 30, 100).unwrap(), NrPolar::uplink(30, 100).unwrap()));
assert!(same(NrPolar::from_name("downlink", 30, 100).unwrap(), NrPolar::downlink(30, 100).unwrap()));
assert!(!same(NrPolar::uplink(30, 100).unwrap(), NrPolar::downlink(30, 100).unwrap()));
assert!(NrPolar::from_name("sidelink", 30, 100).is_err());
}
#[test]
fn the_tables_are_permutations() {
let mut q: Vec<u16> = POLAR_SEQUENCE.to_vec();
q.sort_unstable();
assert!(q.iter().enumerate().all(|(i, &v)| v as usize == i));
let mut il: Vec<u8> = POLAR_INTERLEAVER.to_vec();
il.sort_unstable();
assert!(il.iter().enumerate().all(|(i, &v)| v as usize == i));
let mut p: Vec<u8> = SUBBLOCK_PATTERN.to_vec();
p.sort_unstable();
assert!(p.iter().enumerate().all(|(i, &v)| v as usize == i));
}
#[test]
fn puncturing_shortening_repetition_and_parity_checks_round_trip() {
for (a, e, down) in
[(30, 45, false), (30, 200, false), (40, 600, false), (14, 100, false), (14, 300, false), (39, 108, true)]
{
let code =
if down { NrPolar::downlink(a, e) } else { NrPolar::uplink(a, e) }.unwrap().with_list(8).unwrap();
let info: Vec<u8> = (0..a).map(|i| ((i * 5 + a) % 3 == 0) as u8).collect();
let c = code.encode_block(&info).unwrap();
assert_eq!(c.len(), e);
let llr: Vec<f64> = c.iter().map(|b| if *b == 0 { 2.0 } else { -2.0 }).collect();
assert_eq!(code.decode_block(&llr).unwrap(), info, "A {a}, E {e}");
}
assert!(NrPolar::uplink(11, 100).is_err() && NrPolar::uplink(400, 2000).is_err());
assert!(NrPolar::uplink(30, 100).unwrap().with_list(0).is_err());
}
#[test]
fn the_triangular_interleaver_reads_the_columns() {
let e: Vec<usize> = (0..10).collect();
assert_eq!(triangular(&e), [0, 4, 7, 9, 1, 5, 8, 2, 6, 3]);
assert_eq!(triangular(&e[..8]), [0, 4, 7, 1, 5, 2, 6, 3]);
}
}