use alloc::vec;
use crate::engine::{FecCodec, FecOpts};
use crate::fec::ConvFano;
pub(crate) const N: usize = 162;
pub(crate) const K: usize = 50;
#[cfg(any(test, feature = "internal-testing"))]
pub mod capture {
use alloc::vec::Vec;
use std::sync::Mutex;
pub static INPUTS: Mutex<Vec<[f32; super::N]>> = Mutex::new(Vec::new());
pub fn reset() {
INPUTS.lock().expect("capture mutex poisoned").clear();
}
#[must_use]
pub fn snapshot() -> Vec<[f32; super::N]> {
INPUTS.lock().expect("capture mutex poisoned").clone()
}
}
fn gen_matrix() -> &'static [[u8; N]; K] {
use std::sync::OnceLock;
static GEN: OnceLock<[[u8; N]; K]> = OnceLock::new();
GEN.get_or_init(|| {
let codec = ConvFano;
let mut g = [[0u8; N]; K];
let mut info = [0u8; K];
let mut codeword = vec![0u8; N];
for i in 0..K {
info.fill(0);
info[i] = 1;
codec.encode(&info, &mut codeword);
g[i].copy_from_slice(&codeword);
}
g
})
}
pub fn osd_decode(llrs: &[f32; N]) -> Option<([u8; K], u32)> {
#[cfg(any(test, feature = "internal-testing"))]
capture::INPUTS
.lock()
.expect("capture mutex poisoned")
.push(*llrs);
let mut hard = [0u8; N];
let mut absllr = [0.0f32; N];
for i in 0..N {
hard[i] = if llrs[i] < 0.0 { 1 } else { 0 };
absllr[i] = llrs[i].abs();
}
let mut indices: [usize; N] = core::array::from_fn(|i| i);
indices.sort_by(|&a, &b| {
absllr[b]
.partial_cmp(&absllr[a])
.unwrap_or(core::cmp::Ordering::Equal)
});
let g0 = gen_matrix();
let mut g: [[u8; N]; K] = [[0u8; N]; K];
let mut hard_perm = [0u8; N];
let mut abs_perm = [0.0f32; N];
let mut perm = indices;
for (col_out, &col_src) in indices.iter().enumerate() {
for row in 0..K {
g[row][col_out] = g0[row][col_src];
}
hard_perm[col_out] = hard[col_src];
abs_perm[col_out] = absllr[col_src];
}
for id in 0..K {
let mut pivot_col = None;
for icol in id..(K + 20).min(N) {
if g[id][icol] == 1 {
pivot_col = Some(icol);
break;
}
}
let icol = pivot_col?;
if icol != id {
for row in 0..K {
g[row].swap(id, icol);
}
hard_perm.swap(id, icol);
abs_perm.swap(id, icol);
perm.swap(id, icol);
}
for row in 0..K {
if row != id && g[row][id] == 1 {
for c in 0..N {
g[row][c] ^= g[id][c];
}
}
}
}
let mut m0 = [0u8; K];
m0.copy_from_slice(&hard_perm[..K]);
let encode = |me: &[u8; K]| -> [u8; N] {
let mut cw = [0u8; N];
cw[..K].copy_from_slice(me);
for i in 0..K {
if me[i] == 1 {
for c in K..N {
cw[c] ^= g[i][c];
}
}
}
cw
};
let distance = |cw: &[u8; N]| -> f32 {
let mut d = 0.0f32;
for i in 0..N {
if cw[i] != hard_perm[i] {
d += abs_perm[i];
}
}
d
};
let c0 = encode(&m0);
let mut best_d = distance(&c0);
let mut best_cw = c0;
for n1 in 0..K {
let mut me = m0;
me[n1] ^= 1;
let cw = encode(&me);
let d = distance(&cw);
if d < best_d {
best_d = d;
best_cw = cw;
}
}
for n1 in 0..K {
for n2 in (n1 + 1)..K {
let mut me = m0;
me[n1] ^= 1;
me[n2] ^= 1;
let cw = encode(&me);
let d = distance(&cw);
if d < best_d {
best_d = d;
best_cw = cw;
}
}
}
let mut cw_natural = [0u8; N];
for (perm_i, &orig_i) in perm.iter().enumerate() {
cw_natural[orig_i] = best_cw[perm_i];
}
let mut hard_llrs = [0.0f32; N];
for i in 0..N {
hard_llrs[i] = if cw_natural[i] == 0 { 127.0 } else { -127.0 };
}
let codec = ConvFano;
let res = codec.decode_soft(&hard_llrs, &FecOpts::default())?;
if res.hard_errors > 0 {
return None;
}
let mut info = [0u8; K];
info.copy_from_slice(&res.info);
let mut nhardmin = 0u32;
for i in 0..N {
if cw_natural[i] != hard[i] {
nhardmin += 1;
}
}
Some((info, nhardmin))
}
pub fn osd_decode_packed(llrs: &[f32; N]) -> Option<([u8; K], u32)> {
#[cfg(any(test, feature = "internal-testing"))]
capture::INPUTS
.lock()
.expect("capture mutex poisoned")
.push(*llrs);
const PACKED_WORDS: usize = N.div_ceil(32);
type Packed = [u32; PACKED_WORDS];
#[inline]
fn get_bit(row: &Packed, i: usize) -> u8 {
((row[i / 32] >> (i % 32)) & 1) as u8
}
#[inline]
fn set_bit(row: &mut Packed, i: usize, v: u8) {
let mask = 1u32 << (i % 32);
if v == 1 {
row[i / 32] |= mask;
} else {
row[i / 32] &= !mask;
}
}
#[inline]
fn xor_into(a: &mut Packed, b: &Packed) {
for w in 0..PACKED_WORDS {
a[w] ^= b[w];
}
}
fn pack(bits: &[u8]) -> Packed {
let mut out = [0u32; PACKED_WORDS];
for (i, &b) in bits.iter().enumerate() {
if b == 1 {
out[i / 32] |= 1 << (i % 32);
}
}
out
}
let mut hard = [0u8; N];
let mut absllr = [0.0f32; N];
for i in 0..N {
hard[i] = if llrs[i] < 0.0 { 1 } else { 0 };
absllr[i] = llrs[i].abs();
}
let mut indices: [usize; N] = core::array::from_fn(|i| i);
indices.sort_by(|&a, &b| {
absllr[b]
.partial_cmp(&absllr[a])
.unwrap_or(core::cmp::Ordering::Equal)
});
let g0 = gen_matrix();
let mut g: [Packed; K] = [[0u32; PACKED_WORDS]; K];
let mut hard_perm = [0u8; N];
let mut abs_perm = [0.0f32; N];
let mut perm = indices;
for (col_out, &col_src) in indices.iter().enumerate() {
for row in 0..K {
if g0[row][col_src] == 1 {
set_bit(&mut g[row], col_out, 1);
}
}
hard_perm[col_out] = hard[col_src];
abs_perm[col_out] = absllr[col_src];
}
for id in 0..K {
let mut pivot_col = None;
for icol in id..(K + 20).min(N) {
if get_bit(&g[id], icol) == 1 {
pivot_col = Some(icol);
break;
}
}
let icol = pivot_col?;
if icol != id {
for row in 0..K {
let a = get_bit(&g[row], id);
let b = get_bit(&g[row], icol);
set_bit(&mut g[row], id, b);
set_bit(&mut g[row], icol, a);
}
hard_perm.swap(id, icol);
abs_perm.swap(id, icol);
perm.swap(id, icol);
}
let pivot_row = g[id];
for row in 0..K {
if row != id && get_bit(&g[row], id) == 1 {
xor_into(&mut g[row], &pivot_row);
}
}
}
let mut m0 = [0u8; K];
m0.copy_from_slice(&hard_perm[..K]);
let hard_perm_packed = pack(&hard_perm);
let encode = |me: &[u8; K]| -> Packed {
let mut cw = [0u32; PACKED_WORDS];
for i in 0..K {
if me[i] == 1 {
xor_into(&mut cw, &g[i]);
}
}
cw
};
let distance = |cw: &Packed| -> f32 {
let mut d = 0.0f32;
for w in 0..PACKED_WORDS {
let mut diff = cw[w] ^ hard_perm_packed[w];
while diff != 0 {
let bit = diff.trailing_zeros() as usize;
let i = w * 32 + bit;
if i < N {
d += abs_perm[i];
}
diff &= diff - 1; }
}
d
};
let c0 = encode(&m0);
let mut best_d = distance(&c0);
let mut best_cw = c0;
for n1 in 0..K {
let mut me = m0;
me[n1] ^= 1;
let cw = encode(&me);
let d = distance(&cw);
if d < best_d {
best_d = d;
best_cw = cw;
}
}
for n1 in 0..K {
for n2 in (n1 + 1)..K {
let mut me = m0;
me[n1] ^= 1;
me[n2] ^= 1;
let cw = encode(&me);
let d = distance(&cw);
if d < best_d {
best_d = d;
best_cw = cw;
}
}
}
let mut cw_natural = [0u8; N];
for (perm_i, &orig_i) in perm.iter().enumerate() {
cw_natural[orig_i] = get_bit(&best_cw, perm_i);
}
let mut hard_llrs = [0.0f32; N];
for i in 0..N {
hard_llrs[i] = if cw_natural[i] == 0 { 127.0 } else { -127.0 };
}
let codec = ConvFano;
let res = codec.decode_soft(&hard_llrs, &FecOpts::default())?;
if res.hard_errors > 0 {
return None;
}
let mut info = [0u8; K];
info.copy_from_slice(&res.info);
let mut nhardmin = 0u32;
for i in 0..N {
if cw_natural[i] != hard[i] {
nhardmin += 1;
}
}
Some((info, nhardmin))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn osd_runs_without_panic() {
let llrs = [1.0f32; N];
let _ = osd_decode(&llrs); }
#[test]
fn osd_packed_runs_without_panic() {
let llrs = [1.0f32; N];
let _ = osd_decode_packed(&llrs);
}
#[test]
fn osd_packed_matches_unpacked_on_synthetic_inputs() {
let patterns: [[f32; N]; 4] = [
[1.0f32; N],
[-1.0f32; N],
core::array::from_fn(|i| if i % 2 == 0 { 3.0 } else { -3.0 }),
core::array::from_fn(|i| ((i as f32 * 37.0) % 41.0) - 20.0),
];
for (i, llrs) in patterns.iter().enumerate() {
let a = osd_decode(llrs);
let b = osd_decode_packed(llrs);
assert_eq!(a, b, "pattern {i} diverged: unpacked={a:?} packed={b:?}");
}
}
}