use super::{npfwht, pdmath};
#[repr(i32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum QraCodeType {
Normal = 0,
Crc = 1,
CrcPunctured = 2,
CrcPunctured2 = 3,
}
#[allow(non_snake_case)]
pub struct QraCode {
pub K: usize,
pub N: usize,
pub m: u32,
pub M: usize,
pub a: usize,
pub NC: usize,
pub V: usize,
pub C: usize,
pub NMSG: usize,
pub MAXVDEG: usize,
pub MAXCDEG: usize,
pub code_type: QraCodeType,
pub R: f32,
pub name: &'static str,
pub acc_input_idx: &'static [i32],
pub acc_input_wlog: &'static [i32],
pub gflog: &'static [i32],
pub gfexp: &'static [i32],
pub msgw: &'static [i32],
pub vdeg: &'static [i32],
pub cdeg: &'static [i32],
pub v2cmidx: &'static [i32],
pub c2vmidx: &'static [i32],
pub gfpmat: &'static [i32],
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExtrinsicResult {
Converged { iterations: u32 },
NotConverged,
BadCodeTables,
}
pub struct DecoderScratch {
v2cmsg: Vec<f32>,
c2vmsg: Vec<f32>,
msgout: Vec<f32>,
}
impl DecoderScratch {
pub fn for_code(code: &QraCode) -> Self {
let edges = code.NMSG * code.M;
Self {
v2cmsg: vec![0.0; edges],
c2vmsg: vec![0.0; edges],
msgout: vec![0.0; code.M],
}
}
}
impl QraCode {
pub fn encode(&self, x: &[i32], y: &mut [i32]) {
assert_eq!(x.len(), self.K, "encode: x.len() != K");
assert_eq!(y.len(), self.N, "encode: y.len() != N");
y[..self.K].copy_from_slice(x);
let modulus = (self.M - 1) as i32;
let mut chk: i32 = 0;
if self.a == 1 {
for k in 0..self.NC {
let idx = self.acc_input_idx[k] as usize;
let t = x[idx];
if t != 0 {
let lg = (self.gflog[t as usize] + self.acc_input_wlog[k]).rem_euclid(modulus);
chk ^= self.gfexp[lg as usize];
}
y[self.K + k] = chk;
}
} else {
for k in 0..self.NC {
let kk = self.a * k;
for j in 0..self.a {
let jj = kk + j;
let idx = self.acc_input_idx[jj];
if idx < 0 {
continue;
}
let t = x[idx as usize];
if t != 0 {
let lg =
(self.gflog[t as usize] + self.acc_input_wlog[jj]).rem_euclid(modulus);
chk ^= self.gfexp[lg as usize];
}
}
y[self.K + k] = chk;
}
}
}
pub fn mfsk_bessel_metric(
&self,
pix: &mut [f32],
rsq: &[f32],
n_symbols: usize,
es_no_metric: f32,
) -> f32 {
let big_m = self.M;
let nsamples = big_m * n_symbols;
assert_eq!(pix.len(), nsamples, "mfsk_bessel_metric: pix length");
assert_eq!(rsq.len(), nsamples, "mfsk_bessel_metric: rsq length");
let mut rsum = 0.0_f32;
for k in 0..nsamples {
rsum += rsq[k];
pix[k] = rsq[k].sqrt();
}
rsum /= nsamples as f32;
let sigmaest = (rsum / (1.0 + es_no_metric / big_m as f32) / 2.0).sqrt();
let cmetric = (2.0 * es_no_metric).sqrt() / sigmaest;
for k in 0..n_symbols {
let row = &mut pix[big_m * k..big_m * (k + 1)];
ioapprox(row, cmetric);
pdmath::norm(row);
}
sigmaest
}
pub fn extrinsic(
&self,
pex: &mut [f32],
pix: &[f32],
maxiter: u32,
scratch: &mut DecoderScratch,
) -> ExtrinsicResult {
let big_m = self.M;
let v = self.V;
let c = self.C;
let max_vdeg = self.MAXVDEG;
let max_cdeg = self.MAXCDEG;
assert_eq!(pex.len(), big_m * v, "extrinsic: pex length");
assert_eq!(pix.len(), big_m * v, "extrinsic: pix length");
assert_eq!(
scratch.v2cmsg.len(),
self.NMSG * big_m,
"extrinsic: scratch belongs to a different code"
);
let init_len = big_m * v;
scratch.c2vmsg[..init_len].copy_from_slice(pix);
for nv in 0..v {
let ndeg = self.vdeg[nv] as usize;
let msgbase = nv * max_vdeg;
let intrinsic_row = &pix[big_m * nv..big_m * (nv + 1)];
for k in 1..ndeg {
let imsg = self.v2cmidx[msgbase + k] as usize;
let dst = &mut scratch.v2cmsg[big_m * imsg..big_m * (imsg + 1)];
dst.copy_from_slice(intrinsic_row);
}
}
let mut rc = ExtrinsicResult::NotConverged;
for nit in 0..maxiter {
for nc in v..c {
let ndeg = self.cdeg[nc] as usize;
if ndeg == 1 {
return ExtrinsicResult::BadCodeTables;
}
let msgbase = nc * max_cdeg;
for k in 0..ndeg {
let imsg = self.c2vmidx[msgbase + k] as usize;
let row = &mut scratch.v2cmsg[big_m * imsg..big_m * (imsg + 1)];
npfwht::fwht(row);
}
for k in 0..ndeg {
scratch.msgout.copy_from_slice(pdmath::uniform(big_m));
for kk in 0..ndeg {
if kk != k {
let imsg = self.c2vmidx[msgbase + kk] as usize;
let src = &scratch.v2cmsg[big_m * imsg..big_m * (imsg + 1)];
pdmath::imul(&mut scratch.msgout, src);
}
}
scratch.msgout[0] += 1e-7;
npfwht::fwht(&mut scratch.msgout);
let imsg = self.c2vmidx[msgbase + k] as usize;
let wmsg = self.msgw[imsg];
let dst = &mut scratch.c2vmsg[big_m * imsg..big_m * (imsg + 1)];
if wmsg == 0 {
dst.copy_from_slice(&scratch.msgout);
} else {
let perm = self.gfpmat_row(wmsg as usize);
pdmath::bwdperm(dst, &scratch.msgout, perm);
}
}
}
for nv in 0..v {
let ndeg = self.vdeg[nv] as usize;
let msgbase = nv * max_vdeg;
for k in 0..ndeg {
scratch.msgout.copy_from_slice(pdmath::uniform(big_m));
for kk in 0..ndeg {
if kk != k {
let imsg = self.v2cmidx[msgbase + kk] as usize;
let src = &scratch.c2vmsg[big_m * imsg..big_m * (imsg + 1)];
pdmath::imul(&mut scratch.msgout, src);
}
}
pdmath::norm(&mut scratch.msgout);
let imsg = self.v2cmidx[msgbase + k] as usize;
let wmsg = self.msgw[imsg];
let dst = &mut scratch.v2cmsg[big_m * imsg..big_m * (imsg + 1)];
if wmsg == 0 {
dst.copy_from_slice(&scratch.msgout);
} else {
let perm = self.gfpmat_row(wmsg as usize);
pdmath::fwdperm(dst, &scratch.msgout, perm);
}
}
}
let mut totex = 0.0_f32;
for nv in 0..v {
let row = &scratch.v2cmsg[big_m * nv..big_m * (nv + 1)];
totex += pdmath::max(row);
}
if totex > v as f32 - 0.01 {
rc = ExtrinsicResult::Converged { iterations: nit };
break;
}
}
pex.copy_from_slice(&scratch.v2cmsg[..big_m * v]);
rc
}
pub fn map_decode(&self, pex: &mut [f32], pix: &[f32], xdec: &mut [i32]) {
let big_m = self.M;
assert_eq!(pex.len(), big_m * self.V, "map_decode: pex length");
assert_eq!(pix.len(), big_m * self.V, "map_decode: pix length");
assert_eq!(xdec.len(), self.K, "map_decode: xdec length");
for k in 0..self.K {
let row_e = &mut pex[big_m * k..big_m * (k + 1)];
let row_i = &pix[big_m * k..big_m * (k + 1)];
pdmath::imul(row_e, row_i);
let (_, idx) = pdmath::argmax(row_e).expect("MAP decode: empty distribution");
xdec[k] = idx as i32;
}
}
fn gfpmat_row(&self, logw: usize) -> &[i32] {
let m = self.M;
let start = logw * m;
&self.gfpmat[start..start + m]
}
}
fn ioapprox(buf: &mut [f32], c: f32) {
for x in buf.iter_mut() {
let v = *x * c;
let vsq = v * v;
let mut v = vsq * (v + 0.039) / (vsq * 0.9931 + v * 2.6936 + 0.5185);
if v > 80.0 {
v = 80.0;
}
*x = v.exp();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ioapprox_monotonic_increasing() {
let mut buf = [0.0_f32, 0.5, 1.0, 2.0, 4.0];
ioapprox(&mut buf, 1.0);
for w in buf.windows(2) {
assert!(
w[0] <= w[1],
"ioapprox not monotonic: {:?} > {:?}",
w[0],
w[1]
);
}
}
#[test]
fn ioapprox_clamps_at_80() {
let mut buf = [10.0_f32; 4];
ioapprox(&mut buf, 100.0);
let max_allowed = 80.0_f32.exp() * 1.001;
for &v in &buf {
assert!(v.is_finite(), "overflowed to {v}");
assert!(v <= max_allowed, "value {v} exceeds clamp");
}
}
}