#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Subframe {
Sb2,
Sb34,
}
#[derive(Clone, Debug)]
pub struct Sparse {
pub rows: usize,
pub cols: usize,
pub ones: Vec<(u32, u32)>,
}
impl Sparse {
pub fn parse(text: &str, rows: usize, cols: usize) -> Result<Self, String> {
let mut ones = Vec::new();
for (n, line) in text.lines().enumerate() {
let line = line.trim();
if line.is_empty() {
continue;
}
let (r, c) = line
.split_once(',')
.ok_or_else(|| format!("line {}: expected `row,column`", n + 1))?;
let r: u32 = r
.trim()
.parse()
.map_err(|e| format!("line {}: {e}", n + 1))?;
let c: u32 = c
.trim()
.parse()
.map_err(|e| format!("line {}: {e}", n + 1))?;
if r as usize >= rows || c as usize >= cols {
return Err(format!("line {}: ({r},{c}) outside {rows}x{cols}", n + 1));
}
ones.push((r, c));
}
Ok(Sparse { rows, cols, ones })
}
pub fn mul(&self, x: &[u8]) -> Vec<u8> {
assert_eq!(x.len(), self.cols, "dimension mismatch");
let mut y = vec![0u8; self.rows];
for &(r, c) in &self.ones {
y[r as usize] ^= x[c as usize] & 1;
}
y
}
}
#[derive(Clone, Debug)]
pub struct LdpcCode {
pub subframe: Subframe,
pub info_bits: usize,
pub k: usize,
pub m1: usize,
pub m2: usize,
pub z: usize,
pub n_tx: usize,
pub a: Sparse,
pub b: Sparse,
pub b_inv: Sparse,
pub c: Sparse,
pub d: Sparse,
}
fn table_files(subframe: Subframe) -> [&'static str; 5] {
match subframe {
Subframe::Sb2 => [
"003a_lunanet_sf2_ldpc_submatrix_a_ind.csv",
"003b_lunanet_sf2_ldpc_submatrix_b_ind.csv",
"003c_lunanet_sf2_ldpc_submatrix_b_inv_ind.csv",
"003d_lunanet_sf2_ldpc_submatrix_c_ind.csv",
"003e_lunanet_sf2_ldpc_submatrix_d_ind.csv",
],
Subframe::Sb34 => [
"003f_lunanet_sf3_ldpc_submatrix_a_ind.csv",
"003g_lunanet_sf3_ldpc_submatrix_b_ind.csv",
"003h_lunanet_sf3_ldpc_submatrix_b_inv_ind.csv",
"003i_lunanet_sf3_ldpc_submatrix_c_ind.csv",
"003j_lunanet_sf3_ldpc_submatrix_d_ind.csv",
],
}
}
impl LdpcCode {
pub fn load(subframe: Subframe) -> Result<Self, String> {
let (info_bits, k, m1, m2, z, n_tx) = match subframe {
Subframe::Sb2 => (1200, 1200, 480, 4560, 240, 2400),
Subframe::Sb34 => (870, 880, 352, 3344, 176, 1740),
};
let f = table_files(subframe);
let p = |i: usize, r, c| -> Result<Sparse, String> {
let text = super::lsis::read_verified_text(f[i])?;
Sparse::parse(&text, r, c).map_err(|e| format!("{}: {e}", f[i]))
};
Ok(LdpcCode {
subframe,
info_bits,
k,
m1,
m2,
z,
n_tx,
a: p(0, m1, k)?,
b: p(1, m1, m1)?,
b_inv: p(2, m1, m1)?,
c: p(3, m2, k)?,
d: p(4, m2, m1)?,
})
}
pub fn n(&self) -> usize {
self.k + self.m1 + self.m2
}
pub fn encode_full(&self, info: &[u8]) -> Result<Vec<u8>, String> {
if info.len() != self.info_bits {
return Err(format!(
"LDPC {:?}: {} information bits expected, {} given",
self.subframe,
self.info_bits,
info.len()
));
}
let mut s = info.iter().map(|b| b & 1).collect::<Vec<u8>>();
s.resize(self.k, 0);
let p1 = self.b_inv.mul(&self.a.mul(&s));
let cs = self.c.mul(&s);
let dp = self.d.mul(&p1);
let p2: Vec<u8> = cs.iter().zip(&dp).map(|(x, y)| x ^ y).collect();
let mut cw = s;
cw.extend_from_slice(&p1);
cw.extend_from_slice(&p2);
Ok(cw)
}
pub fn transmitted_positions(&self) -> Vec<usize> {
let mut pos: Vec<usize> = (self.z..self.info_bits).collect();
let parity_start = self.k;
let remaining = self.n_tx - pos.len();
pos.extend(parity_start..parity_start + remaining);
debug_assert_eq!(pos.len(), self.n_tx);
pos
}
pub fn encode(&self, info: &[u8]) -> Result<Vec<u8>, String> {
let cw = self.encode_full(info)?;
Ok(self
.transmitted_positions()
.iter()
.map(|&i| cw[i])
.collect())
}
pub fn h_rows(&self) -> Vec<Vec<u32>> {
let mut rows = vec![Vec::new(); self.m1 + self.m2];
for &(r, c) in &self.a.ones {
rows[r as usize].push(c);
}
for &(r, c) in &self.b.ones {
rows[r as usize].push(self.k as u32 + c);
}
for &(r, c) in &self.c.ones {
rows[self.m1 + r as usize].push(c);
}
for &(r, c) in &self.d.ones {
rows[self.m1 + r as usize].push(self.k as u32 + c);
}
for r in 0..self.m2 {
rows[self.m1 + r].push((self.k + self.m1 + r) as u32);
}
rows
}
pub fn unsatisfied_checks(&self, codeword: &[u8]) -> usize {
assert_eq!(codeword.len(), self.n());
self.h_rows()
.iter()
.filter(|row| row.iter().fold(0u8, |a, &c| a ^ codeword[c as usize]) != 0)
.count()
}
pub fn decode_min_sum(&self, llr_tx: &[f64], max_iter: usize) -> (Vec<u8>, bool) {
assert_eq!(llr_tx.len(), self.n_tx);
let n = self.n();
let mut ch = vec![0.0f64; n];
for (&p, &l) in self.transmitted_positions().iter().zip(llr_tx) {
ch[p] = l;
}
for v in ch.iter_mut().take(self.k).skip(self.info_bits) {
*v = 1.0e3; }
let rows = self.h_rows();
let mut c2v: Vec<Vec<f64>> = rows.iter().map(|r| vec![0.0; r.len()]).collect();
let mut total = ch.clone();
let mut hard = vec![0u8; n];
const ALPHA: f64 = 0.75;
for _ in 0..max_iter.max(1) {
for (ri, row) in rows.iter().enumerate() {
let mut min1 = f64::INFINITY;
let mut min2 = f64::INFINITY;
let mut idx_min = usize::MAX;
let mut sign = 1.0f64;
let v2c: Vec<f64> = row
.iter()
.zip(&c2v[ri])
.map(|(&c, &m)| total[c as usize] - m)
.collect();
for (j, &x) in v2c.iter().enumerate() {
let a = x.abs();
if x < 0.0 {
sign = -sign;
}
if a < min1 {
min2 = min1;
min1 = a;
idx_min = j;
} else if a < min2 {
min2 = a;
}
}
for (j, &x) in v2c.iter().enumerate() {
let mag = if j == idx_min { min2 } else { min1 };
let s = if x < 0.0 { -sign } else { sign };
let new = ALPHA * s * mag;
let col = row[j] as usize;
total[col] += new - c2v[ri][j];
c2v[ri][j] = new;
}
}
for (h, &t) in hard.iter_mut().zip(&total) {
*h = u8::from(t < 0.0);
}
if rows
.iter()
.all(|row| row.iter().fold(0u8, |a, &c| a ^ hard[c as usize]) == 0)
{
return (hard[..self.info_bits].to_vec(), true);
}
}
(hard[..self.info_bits].to_vec(), false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn printed_b_inverse_inverts_printed_b() {
if !crate::lunar_afs::lsis::cache_present() {
eprintln!("SKIP: {}", crate::lunar_afs::lsis::fetch_hint());
return;
}
for sf in [Subframe::Sb2, Subframe::Sb34] {
let code = LdpcCode::load(sf).unwrap();
for col in 0..code.m1 {
let mut e = vec![0u8; code.m1];
e[col] = 1;
assert_eq!(code.b.mul(&code.b_inv.mul(&e)), e, "{sf:?} column {col}");
}
assert_eq!(code.transmitted_positions().len(), code.n_tx);
}
}
}