use alloc::vec;
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use num_traits::Float;
use super::params::{Ldpc174_91Params, LdpcParams};
use super::{LDPC_K, LDPC_N};
pub use crate::core::BpKind;
const NCW: usize = 3;
#[inline]
fn platanh(x: f32) -> f32 {
if x.abs() > 0.999_999_9 {
x.signum() * 4.6
} else {
x.atanh()
}
}
pub fn crc14(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &byte in data {
for i in (0..8).rev() {
let bit = (byte >> i) & 1;
let msb = (crc >> 13) & 1;
crc = ((crc << 1) | bit as u16) & 0x3FFF;
if msb != 0 {
crc ^= 0x2757;
}
}
}
crc
}
pub fn check_crc14(decoded: &[u8]) -> bool {
if decoded.len() != LDPC_K {
return false;
}
let mut bytes = [0u8; 12];
for (i, &bit) in decoded[..77].iter().enumerate() {
let byte_idx = i / 8;
let bit_pos = 7 - (i % 8);
bytes[byte_idx] |= (bit & 1) << bit_pos;
}
let computed = crc14(&bytes);
let mut received: u16 = 0;
for &bit in &decoded[77..91] {
received = (received << 1) | (bit as u16 & 1);
}
computed == received
}
pub struct BpResult {
pub message77: [u8; 77],
pub info: Vec<u8>,
pub codeword: Vec<u8>,
pub hard_errors: u32,
pub iterations: u32,
}
pub fn bp_decode_generic<P: LdpcParams>(
llr: &[f32],
ap_mask: Option<&[bool]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
) -> Option<BpResult> {
bp_decode_generic_kind::<P>(llr, ap_mask, max_iter, verify, BpKind::SumProduct)
}
pub fn bp_decode_generic_kind<P: LdpcParams>(
llr: &[f32],
ap_mask: Option<&[bool]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
kind: BpKind,
) -> Option<BpResult> {
debug_assert_eq!(llr.len(), P::N, "llr length must equal P::N");
if let Some(m) = ap_mask {
debug_assert_eq!(m.len(), P::N, "ap_mask length must equal P::N");
}
let n = P::N;
let m_checks = P::M;
let k = P::K;
let max_row = P::MAX_ROW;
let mut tov = vec![0f32; n * NCW];
let mut toc = vec![0f32; m_checks * max_row];
let mut tanhtoc: Vec<f32> = match kind {
BpKind::SumProduct => vec![0f32; m_checks * max_row],
BpKind::NormalizedMinSum { .. } | BpKind::OffsetMinSum { .. } => Vec::new(),
};
let mut min1 = vec![0f32; m_checks];
let mut min2 = vec![0f32; m_checks];
let mut idx_min1 = vec![0u32; m_checks];
let mut sign_xor = vec![false; m_checks];
let mut zn = vec![0f32; n];
let mut cw = vec![0u8; n];
for j in 0..m_checks {
let nrw_j = P::nrw(j) as usize;
for i in 0..nrw_j {
let bit = P::nm(j, i) as usize;
toc[j * max_row + i] = llr[bit];
}
}
let mut ncnt = 0u32;
let mut nclast = 0u32;
for iter in 0..=max_iter {
for i in 0..n {
let ap = ap_mask.is_some_and(|mm| mm[i]);
if !ap {
let mut sum = 0.0f32;
for k_ in 0..NCW {
sum += tov[i * NCW + k_];
}
zn[i] = llr[i] + sum;
} else {
zn[i] = llr[i];
}
}
for i in 0..n {
cw[i] = if zn[i] > 0.0 { 1 } else { 0 };
}
let mut ncheck = 0u32;
for i in 0..m_checks {
let nrw_i = P::nrw(i) as usize;
let mut parity = 0u8;
for s in 0..nrw_i {
parity ^= cw[P::nm(i, s) as usize];
}
if parity != 0 {
ncheck += 1;
}
}
if ncheck == 0 {
let mut decoded = vec![0u8; k];
decoded.copy_from_slice(&cw[..k]);
let accept = match verify {
Some(f) => f(&decoded),
None => true,
};
if accept {
let mut hard_errors = 0u32;
for i in 0..n {
if (cw[i] == 1) != (llr[i] > 0.0) {
hard_errors += 1;
}
}
let mut message77 = [0u8; 77];
message77.copy_from_slice(&decoded[..77]);
return Some(BpResult {
message77,
info: decoded,
codeword: cw,
hard_errors,
iterations: iter,
});
}
}
if iter > 0 {
if ncheck < nclast {
ncnt = 0;
} else {
ncnt += 1;
}
if ncnt >= 5 && iter >= 10 && ncheck > 15 {
return None;
}
}
nclast = ncheck;
for j in 0..m_checks {
let nrw_j = P::nrw(j) as usize;
for i in 0..nrw_j {
let ibj = P::nm(j, i) as usize;
let mut msg = zn[ibj];
let mn_ibj = P::mn(ibj);
for kk in 0..NCW {
if mn_ibj[kk] as usize == j {
msg -= tov[ibj * NCW + kk];
}
}
toc[j * max_row + i] = msg;
}
}
match kind {
BpKind::SumProduct => {
for i in 0..m_checks {
let nrw_i = P::nrw(i) as usize;
for k_ in 0..nrw_i {
tanhtoc[i * max_row + k_] = (-toc[i * max_row + k_] / 2.0).tanh();
}
}
for j in 0..n {
let mn_j = P::mn(j);
for k_ in 0..NCW {
let ichk = mn_j[k_] as usize;
let nrw_ichk = P::nrw(ichk) as usize;
let mut tmn = 1.0f32;
for s in 0..nrw_ichk {
let bit = P::nm(ichk, s) as usize;
if bit != j {
tmn *= tanhtoc[ichk * max_row + s];
}
}
tov[j * NCW + k_] = 2.0 * platanh(-tmn);
}
}
}
BpKind::NormalizedMinSum { .. } | BpKind::OffsetMinSum { .. } => {
for i in 0..m_checks {
let nrw_i = P::nrw(i) as usize;
let mut m1 = f32::INFINITY;
let mut m2 = f32::INFINITY;
let mut imin = 0_usize;
let mut sx = false;
for s in 0..nrw_i {
let v = toc[i * max_row + s];
if v < 0.0 {
sx = !sx;
}
let av = v.abs();
if av < m1 {
m2 = m1;
m1 = av;
imin = s;
} else if av < m2 {
m2 = av;
}
}
min1[i] = m1;
min2[i] = m2;
idx_min1[i] = imin as u32;
sign_xor[i] = sx;
}
let alpha_eff = match kind {
BpKind::NormalizedMinSum { alpha } => alpha,
_ => 1.0,
};
let beta = match kind {
BpKind::OffsetMinSum { beta } => beta,
_ => 0.0,
};
let is_offset = matches!(kind, BpKind::OffsetMinSum { .. });
for j in 0..n {
let mn_j = P::mn(j);
for k_ in 0..NCW {
let ichk = mn_j[k_] as usize;
let nrw_ichk = P::nrw(ichk) as usize;
let mut my_slot = nrw_ichk; for s in 0..nrw_ichk {
if P::nm(ichk, s) as usize == j {
my_slot = s;
break;
}
}
let my_v = if my_slot < nrw_ichk {
toc[ichk * max_row + my_slot]
} else {
0.0
};
let my_neg = my_v < 0.0;
let nrw_odd = (nrw_ichk & 1) != 0;
let extrinsic_sign_neg = sign_xor[ichk] ^ my_neg ^ nrw_odd;
let mag = if my_slot < nrw_ichk && my_slot as u32 == idx_min1[ichk] {
min2[ichk]
} else {
min1[ichk]
};
let scaled = if is_offset {
(mag - beta).max(0.0)
} else {
alpha_eff * mag
};
tov[j * NCW + k_] = if extrinsic_sign_neg { -scaled } else { scaled };
}
}
}
}
}
None
}
pub fn bp_llr_zsum<P: LdpcParams>(llr: &[f32], n_iter: u32) -> Vec<f32> {
let n = P::N;
let m_checks = P::M;
let max_row = P::MAX_ROW;
let mut tov = vec![0f32; n * NCW];
let mut toc = vec![0f32; m_checks * max_row];
let mut tanhtoc = vec![0f32; m_checks * max_row];
let mut zn = vec![0f32; n];
let mut zsum = vec![0f32; n];
for j in 0..m_checks {
let nrw_j = P::nrw(j) as usize;
for i in 0..nrw_j {
let bit = P::nm(j, i) as usize;
toc[j * max_row + i] = llr[bit];
}
}
for _iter in 0..=n_iter {
for i in 0..n {
let mut sum = 0.0f32;
for k_ in 0..NCW {
sum += tov[i * NCW + k_];
}
zn[i] = llr[i] + sum;
}
for i in 0..n {
zsum[i] += zn[i];
}
for j in 0..m_checks {
let nrw_j = P::nrw(j) as usize;
for i in 0..nrw_j {
let ibj = P::nm(j, i) as usize;
let mut msg = zn[ibj];
let mn_ibj = P::mn(ibj);
for kk in 0..NCW {
if mn_ibj[kk] as usize == j {
msg -= tov[ibj * NCW + kk];
}
}
toc[j * max_row + i] = msg;
}
}
for i in 0..m_checks {
let nrw_i = P::nrw(i) as usize;
for k_ in 0..nrw_i {
tanhtoc[i * max_row + k_] = (-toc[i * max_row + k_] / 2.0).tanh();
}
}
for j in 0..n {
let mn_j = P::mn(j);
for k_ in 0..NCW {
let ichk = mn_j[k_] as usize;
let nrw_ichk = P::nrw(ichk) as usize;
let mut tmn = 1.0f32;
for s in 0..nrw_ichk {
let bit = P::nm(ichk, s) as usize;
if bit != j {
tmn *= tanhtoc[ichk * max_row + s];
}
}
tov[j * NCW + k_] = 2.0 * platanh(-tmn);
}
}
}
zsum
}
pub fn bp_decode(
llr: &[f32; LDPC_N],
ap_mask: Option<&[bool; LDPC_N]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
) -> Option<BpResult> {
let ap_slice: Option<&[bool]> = ap_mask.map(|a| a.as_slice());
bp_decode_generic::<Ldpc174_91Params>(llr.as_slice(), ap_slice, max_iter, verify)
}
pub fn bp_decode_kind(
llr: &[f32; LDPC_N],
ap_mask: Option<&[bool; LDPC_N]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
kind: BpKind,
) -> Option<BpResult> {
let ap_slice: Option<&[bool]> = ap_mask.map(|a| a.as_slice());
bp_decode_generic_kind::<Ldpc174_91Params>(llr.as_slice(), ap_slice, max_iter, verify, kind)
}
use crate::core::scalar::LlrScalar;
#[inline]
pub fn llr_f32_to_q11(x: f32) -> i16 {
use crate::core::scalar::Q11i16;
Q11i16::from_f32(x).0
}
pub struct BpScratch<P: LdpcParams, T: LlrScalar> {
tov: Vec<T>,
toc: Vec<T>,
min1: Vec<T>,
min2: Vec<T>,
idx_min1: Vec<u32>,
sign_xor: Vec<bool>,
zn: Vec<T::Wide>,
cw: Vec<u8>,
_p: core::marker::PhantomData<P>,
}
impl<P: LdpcParams, T: LlrScalar> BpScratch<P, T> {
pub fn new() -> Self {
let n = P::N;
let m_checks = P::M;
let max_row = P::MAX_ROW;
Self {
tov: vec![T::ZERO; n * NCW],
toc: vec![T::ZERO; m_checks * max_row],
min1: vec![T::POS_INF_LIKE; m_checks],
min2: vec![T::POS_INF_LIKE; m_checks],
idx_min1: vec![0u32; m_checks],
sign_xor: vec![false; m_checks],
zn: vec![T::wide_zero(); n],
cw: vec![0u8; n],
_p: core::marker::PhantomData,
}
}
#[inline]
fn reset(&mut self) {
for v in self.tov.iter_mut() {
*v = T::ZERO;
}
for v in self.toc.iter_mut() {
*v = T::ZERO;
}
for v in self.min1.iter_mut() {
*v = T::POS_INF_LIKE;
}
for v in self.min2.iter_mut() {
*v = T::POS_INF_LIKE;
}
for v in self.idx_min1.iter_mut() {
*v = 0;
}
for v in self.sign_xor.iter_mut() {
*v = false;
}
}
}
impl<P: LdpcParams, T: LlrScalar> Default for BpScratch<P, T> {
fn default() -> Self {
Self::new()
}
}
pub fn bp_decode_generic_nms<P: LdpcParams, T: LlrScalar>(
llr: &[T],
ap_mask: Option<&[bool]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
alpha: f32,
) -> Option<BpResult> {
let mut scratch = BpScratch::<P, T>::new();
bp_decode_generic_nms_with_scratch::<P, T>(&mut scratch, llr, ap_mask, max_iter, verify, alpha)
}
pub fn bp_decode_generic_nms_with_scratch<P: LdpcParams, T: LlrScalar>(
scratch: &mut BpScratch<P, T>,
llr: &[T],
ap_mask: Option<&[bool]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
alpha: f32,
) -> Option<BpResult> {
debug_assert_eq!(llr.len(), P::N);
if let Some(m) = ap_mask {
debug_assert_eq!(m.len(), P::N);
}
let n = P::N;
let m_checks = P::M;
let k = P::K;
let max_row = P::MAX_ROW;
scratch.reset();
let BpScratch {
tov,
toc,
min1,
min2,
idx_min1,
sign_xor,
zn,
cw,
..
} = scratch;
for j in 0..m_checks {
let nrw_j = P::nrw(j) as usize;
for i in 0..nrw_j {
let bit = P::nm(j, i) as usize;
toc[j * max_row + i] = llr[bit];
}
}
let mut ncnt = 0u32;
let mut nclast = 0u32;
for iter in 0..=max_iter {
for i in 0..n {
let ap = ap_mask.is_some_and(|mm| mm[i]);
if !ap {
let mut sum = llr[i].to_wide();
for k_ in 0..NCW {
sum = T::wide_add(sum, tov[i * NCW + k_].to_wide());
}
zn[i] = sum;
} else {
zn[i] = llr[i].to_wide();
}
}
for i in 0..n {
cw[i] = if T::wide_is_positive(zn[i]) { 1 } else { 0 };
}
let mut ncheck = 0u32;
for i in 0..m_checks {
let nrw_i = P::nrw(i) as usize;
let mut parity = 0u8;
for s in 0..nrw_i {
parity ^= cw[P::nm(i, s) as usize];
}
if parity != 0 {
ncheck += 1;
}
}
if ncheck == 0 {
let mut decoded = vec![0u8; k];
decoded.copy_from_slice(&cw[..k]);
let accept = match verify {
Some(f) => f(&decoded),
None => true,
};
if accept {
let mut hard_errors = 0u32;
for i in 0..n {
let llr_says_one = !llr[i].is_negative();
if (cw[i] == 1) != llr_says_one {
hard_errors += 1;
}
}
let mut message77 = [0u8; 77];
message77.copy_from_slice(&decoded[..77]);
return Some(BpResult {
message77,
info: decoded,
codeword: cw.clone(),
hard_errors,
iterations: iter,
});
}
}
if iter > 0 {
if ncheck < nclast {
ncnt = 0;
} else {
ncnt += 1;
}
if ncnt >= 5 && iter >= 10 && ncheck > 15 {
return None;
}
}
nclast = ncheck;
for j in 0..m_checks {
let nrw_j = P::nrw(j) as usize;
for i in 0..nrw_j {
let ibj = P::nm(j, i) as usize;
let mut msg = zn[ibj];
let mn_ibj = P::mn(ibj);
for kk in 0..NCW {
if mn_ibj[kk] as usize == j {
msg = T::wide_sub(msg, tov[ibj * NCW + kk].to_wide());
}
}
toc[j * max_row + i] = T::from_wide_sat(msg);
}
}
for i in 0..m_checks {
let nrw_i = P::nrw(i) as usize;
let mut m1 = T::POS_INF_LIKE;
let mut m2 = T::POS_INF_LIKE;
let mut imin = 0_usize;
let mut sx = false;
for s in 0..nrw_i {
let v = toc[i * max_row + s];
if v.is_negative() {
sx = !sx;
}
let av = v.abs_sat();
if av.lt_total(m1) {
m2 = m1;
m1 = av;
imin = s;
} else if av.lt_total(m2) {
m2 = av;
}
}
min1[i] = m1;
min2[i] = m2;
idx_min1[i] = imin as u32;
sign_xor[i] = sx;
}
for j in 0..n {
let mn_j = P::mn(j);
for k_ in 0..NCW {
let ichk = mn_j[k_] as usize;
let nrw_ichk = P::nrw(ichk) as usize;
let mut my_slot = nrw_ichk;
for s in 0..nrw_ichk {
if P::nm(ichk, s) as usize == j {
my_slot = s;
break;
}
}
let my_v = if my_slot < nrw_ichk {
toc[ichk * max_row + my_slot]
} else {
T::ZERO
};
let my_neg = my_v.is_negative();
let nrw_odd = (nrw_ichk & 1) != 0;
let extrinsic_sign_neg = sign_xor[ichk] ^ my_neg ^ nrw_odd;
let mag = if my_slot < nrw_ichk && my_slot as u32 == idx_min1[ichk] {
min2[ichk]
} else {
min1[ichk]
};
let scaled = mag.mul_alpha(alpha);
tov[j * NCW + k_] = if extrinsic_sign_neg {
scaled.neg_sat()
} else {
scaled
};
}
}
}
None
}
pub fn bp_decode_nms<T: LlrScalar>(
llr: &[T; LDPC_N],
ap_mask: Option<&[bool; LDPC_N]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
alpha: f32,
) -> Option<BpResult> {
let ap_slice: Option<&[bool]> = ap_mask.map(|a| a.as_slice());
bp_decode_generic_nms::<Ldpc174_91Params, T>(llr.as_slice(), ap_slice, max_iter, verify, alpha)
}
pub fn bp_decode_nms_with_scratch<T: LlrScalar>(
scratch: &mut BpScratch<Ldpc174_91Params, T>,
llr: &[T; LDPC_N],
ap_mask: Option<&[bool; LDPC_N]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
alpha: f32,
) -> Option<BpResult> {
let ap_slice: Option<&[bool]> = ap_mask.map(|a| a.as_slice());
bp_decode_generic_nms_with_scratch::<Ldpc174_91Params, T>(
scratch,
llr.as_slice(),
ap_slice,
max_iter,
verify,
alpha,
)
}
pub fn bp_decode_nms_q11(
llr: &[i16; LDPC_N],
ap_mask: Option<&[bool; LDPC_N]>,
max_iter: u32,
verify: Option<fn(&[u8]) -> bool>,
alpha: f32,
) -> Option<BpResult> {
use crate::core::scalar::Q11i16;
let ap_slice: Option<&[bool]> = ap_mask.map(|a| a.as_slice());
let llr_q: alloc::vec::Vec<Q11i16> = llr.iter().map(|&x| Q11i16(x)).collect();
bp_decode_generic_nms::<Ldpc174_91Params, Q11i16>(&llr_q, ap_slice, max_iter, verify, alpha)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_perfect_llr_all_zeros() {
let llr = [10.0f32; 174];
let _result = bp_decode(&llr, None, 30, None);
}
#[test]
fn crc14_known_vector() {
assert_eq!(crc14(&[0u8; 12]), 0);
}
}