use crate::metric::BranchMetric;
use crate::packing::{get_bit, set_bit, CodedBlock};
use crate::params::CodeParams;
use crate::trellis::next_state;
use crate::MAX_SUPPORTED_INFO_BITS;
pub const MAX_PERIOD: usize = 128;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PunctureError {
EmptyPattern,
RowCountMismatch {
rows: usize,
n: usize,
},
RaggedRows,
PeriodTooLarge {
period: usize,
cap: usize,
},
AllVoidPeriod,
UnsupportedRate,
Catastrophic,
NMismatch {
matrix_n: usize,
params_n: usize,
},
MisalignedInput {
got: usize,
expected: usize,
},
AllocationFailed,
PayloadTooLarge {
got: usize,
cap: usize,
},
}
impl core::fmt::Display for PunctureError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for PunctureError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PunctureOrder {
ByColumn,
Interleaved,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PuncturedRate {
R2_3,
R3_4,
R5_6,
R7_8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PunctureMatrix {
keep: Vec<Vec<bool>>,
n: usize,
period: usize,
kept_per_period: usize,
order: PunctureOrder,
}
impl PunctureMatrix {
pub fn new(keep: Vec<Vec<bool>>, order: PunctureOrder) -> Result<Self, PunctureError> {
if keep.is_empty() || keep.iter().any(Vec::is_empty) {
return Err(PunctureError::EmptyPattern);
}
if keep.len() > 3 {
return Err(PunctureError::RowCountMismatch {
rows: keep.len(),
n: 3,
});
}
let period = keep[0].len();
if keep.iter().any(|row| row.len() != period) {
return Err(PunctureError::RaggedRows);
}
if period > MAX_PERIOD {
return Err(PunctureError::PeriodTooLarge {
period,
cap: MAX_PERIOD,
});
}
for col in 0..period {
if keep.iter().all(|row| !row[col]) {
return Err(PunctureError::AllVoidPeriod);
}
}
let n = keep.len();
let kept_per_period = keep.iter().flatten().filter(|&&b| b).count();
Ok(Self {
keep,
n,
period,
kept_per_period,
order,
})
}
pub fn ccsds_rate(n: usize, rate: PuncturedRate) -> Result<Self, PunctureError> {
if n != 2 {
return Err(PunctureError::UnsupportedRate);
}
Self::new(yasuda_pattern(rate), PunctureOrder::Interleaved)
}
#[must_use]
pub fn n(&self) -> usize {
self.n
}
#[must_use]
pub fn period(&self) -> usize {
self.period
}
#[must_use]
pub fn kept_per_period(&self) -> usize {
self.kept_per_period
}
#[must_use]
pub fn order(&self) -> PunctureOrder {
self.order
}
#[must_use]
pub fn code_rate(&self) -> (usize, usize) {
(self.period, self.kept_per_period)
}
}
fn yasuda_pattern(rate: PuncturedRate) -> Vec<Vec<bool>> {
let rows: &[&[u8]] = match rate {
PuncturedRate::R2_3 => &[&[1, 1], &[1, 0]],
PuncturedRate::R3_4 => &[&[1, 0, 1], &[1, 1, 0]],
PuncturedRate::R5_6 => &[&[1, 0, 1, 0, 1], &[1, 1, 0, 1, 0]],
PuncturedRate::R7_8 => &[&[1, 0, 0, 0, 1, 0, 1], &[1, 1, 1, 1, 0, 1, 0]],
};
rows.iter()
.map(|row| row.iter().map(|&b| b == 1).collect())
.collect()
}
fn raw_output_symbol(state: usize, input: u8, params: &CodeParams) -> u32 {
let m = (params.k() - 1) as u32;
let g = ((input as u32) << m) | state as u32;
let n = params.generators().len();
let mut sym = 0u32;
for (i, &gen) in params.generators().iter().enumerate() {
let bit = (g & gen).count_ones() & 1;
sym |= bit << (n - 1 - i); }
sym
}
fn output_weight_after_puncture(symbol: u32, keep_mask: u32) -> u32 {
(symbol & keep_mask).count_ones()
}
fn punctured_is_catastrophic(
matrix: &PunctureMatrix,
params: &CodeParams,
) -> Result<bool, PunctureError> {
let period = matrix.period;
let n = matrix.n;
let k = params.k();
let states = 1usize << (k - 1);
let masks: Vec<u32> = (0..period)
.map(|phase| {
let mut mask = 0u32;
for (j, row) in matrix.keep.iter().enumerate() {
if row[phase] {
mask |= 1u32 << (n - 1 - j);
}
}
mask
})
.collect();
let num_nodes = states * period;
let mut adj: Vec<Vec<usize>> = Vec::new();
adj.try_reserve_exact(num_nodes)
.map_err(|_| PunctureError::AllocationFailed)?;
adj.resize_with(num_nodes, Vec::new);
for state in 0..states {
for (phase, &mask) in masks.iter().enumerate() {
let node = state * period + phase;
adj[node]
.try_reserve(2)
.map_err(|_| PunctureError::AllocationFailed)?;
for input in 0u8..2 {
if state == 0 && input == 0 {
continue; }
let sym = raw_output_symbol(state, input, params);
if output_weight_after_puncture(sym, mask) == 0 {
let next = next_state(state, input, k);
let next_phase = (phase + 1) % period;
adj[node].push(next * period + next_phase);
}
}
}
}
product_graph_has_cycle(&adj)
}
fn product_graph_has_cycle(adj: &[Vec<usize>]) -> Result<bool, PunctureError> {
#[derive(Clone, Copy, PartialEq, Eq)]
enum Color {
White,
Grey,
Black,
}
let mut color: Vec<Color> = Vec::new();
color
.try_reserve_exact(adj.len())
.map_err(|_| PunctureError::AllocationFailed)?;
color.resize(adj.len(), Color::White);
for start in 0..adj.len() {
if color[start] != Color::White {
continue;
}
color[start] = Color::Grey;
let mut stack: Vec<(usize, usize)> = Vec::new();
stack
.try_reserve(1)
.map_err(|_| PunctureError::AllocationFailed)?;
stack.push((start, 0));
while let Some(&(node, child)) = stack.last() {
if child < adj[node].len() {
stack.last_mut().unwrap().1 += 1;
let neighbor = adj[node][child];
match color[neighbor] {
Color::Grey => return Ok(true), Color::White => {
color[neighbor] = Color::Grey;
stack
.try_reserve(1)
.map_err(|_| PunctureError::AllocationFailed)?;
stack.push((neighbor, 0));
}
Color::Black => {}
}
} else {
color[node] = Color::Black;
stack.pop();
}
}
}
Ok(false)
}
fn body_keep_map(
matrix: &PunctureMatrix,
n: usize,
nbits: usize,
) -> impl Iterator<Item = (usize, bool)> + '_ {
let period = matrix.period;
let order = matrix.order;
let d = nbits.max(1);
(0..(nbits * n)).map(move |e| {
let (t, j) = match order {
PunctureOrder::Interleaved => (e / n, e % n),
PunctureOrder::ByColumn => (e % d, e / d),
};
(t * n + j, matrix.keep[j][t % period])
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Puncturer {
matrix: PunctureMatrix,
n: usize,
m: usize,
}
impl Puncturer {
pub fn new(matrix: PunctureMatrix, params: &CodeParams) -> Result<Self, PunctureError> {
let matrix_n = matrix.n();
let params_n = params.n();
if matrix_n != params_n {
return Err(PunctureError::NMismatch { matrix_n, params_n });
}
if punctured_is_catastrophic(&matrix, params)? {
return Err(PunctureError::Catastrophic);
}
Ok(Self {
matrix,
n: matrix_n,
m: params.k() as usize - 1,
})
}
pub fn puncture(&self, coded: &CodedBlock, nbits: usize) -> Result<CodedBlock, PunctureError> {
let n = self.n;
if nbits > MAX_SUPPORTED_INFO_BITS {
return Err(PunctureError::PayloadTooLarge {
got: nbits,
cap: MAX_SUPPORTED_INFO_BITS,
});
}
match nbits
.checked_add(self.m)
.and_then(|stages| stages.checked_mul(n))
{
Some(e) if e == coded.nbits => {}
Some(e) => {
return Err(PunctureError::MisalignedInput {
got: coded.nbits,
expected: e,
})
}
None => {
return Err(PunctureError::MisalignedInput {
got: coded.nbits,
expected: usize::MAX,
})
}
}
if coded.nbits.div_ceil(8) > coded.bytes.len() {
return Err(PunctureError::MisalignedInput {
got: coded.nbits,
expected: coded.nbits.div_ceil(8).saturating_mul(8),
});
}
let period = self.matrix.period;
let full_periods = nbits / period; let rem = nbits % period;
let prefix_kept: usize = (0..rem)
.map(|col| self.matrix.keep.iter().filter(|row| row[col]).count())
.sum();
let kept_body = full_periods * self.matrix.kept_per_period + prefix_kept;
debug_assert!(
coded.nbits >= nbits * n,
"tail underflow: coded.nbits < nbits*n"
);
let tail = coded.nbits - nbits * n; let out_nbits = kept_body + tail;
let mut bytes: Vec<u8> = Vec::new();
bytes
.try_reserve(out_nbits.div_ceil(8))
.map_err(|_| PunctureError::AllocationFailed)?;
bytes.resize(out_nbits.div_ceil(8), 0);
let mut oi = 0usize;
for (idx, kept) in body_keep_map(&self.matrix, n, nbits) {
if kept {
if get_bit(&coded.bytes, idx) == 1 {
set_bit(&mut bytes, oi);
}
oi += 1;
}
}
for idx in (nbits * n)..coded.nbits {
if get_bit(&coded.bytes, idx) == 1 {
set_bit(&mut bytes, oi);
}
oi += 1;
}
debug_assert_eq!(oi, out_nbits);
Ok(CodedBlock {
bytes,
nbits: out_nbits,
})
}
}
pub struct DePuncturer<Me: BranchMetric> {
matrix: PunctureMatrix,
n: usize,
m: usize,
_marker: core::marker::PhantomData<Me>,
}
impl<Me: BranchMetric> core::fmt::Debug for DePuncturer<Me> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DePuncturer")
.field("matrix", &self.matrix)
.field("n", &self.n)
.field("m", &self.m)
.finish()
}
}
impl<Me: BranchMetric> Clone for DePuncturer<Me> {
fn clone(&self) -> Self {
Self {
matrix: self.matrix.clone(),
n: self.n,
m: self.m,
_marker: core::marker::PhantomData,
}
}
}
impl<Me: BranchMetric> PartialEq for DePuncturer<Me> {
fn eq(&self, other: &Self) -> bool {
self.matrix == other.matrix && self.n == other.n && self.m == other.m
}
}
impl<Me: BranchMetric> Eq for DePuncturer<Me> {}
impl<Me: BranchMetric> DePuncturer<Me> {
pub fn new(matrix: PunctureMatrix, params: &CodeParams) -> Result<Self, PunctureError> {
let matrix_n = matrix.n();
let params_n = params.n();
if matrix_n != params_n {
return Err(PunctureError::NMismatch { matrix_n, params_n });
}
if punctured_is_catastrophic(&matrix, params)? {
return Err(PunctureError::Catastrophic);
}
Ok(Self {
matrix,
n: matrix_n,
m: params.k() as usize - 1,
_marker: core::marker::PhantomData,
})
}
#[must_use]
pub fn expected_punctured_len(&self, nbits: usize) -> Option<usize> {
let period = self.matrix.period;
let n = self.n;
let full_periods = nbits / period; let rem = nbits % period;
let prefix_kept: usize = (0..rem).map(|col| self.column_kept(col)).sum();
let body = full_periods
.checked_mul(self.matrix.kept_per_period)?
.checked_add(prefix_kept)?;
let tail = self.m.checked_mul(n)?;
body.checked_add(tail)
}
pub fn depuncture(
&self,
received: &[Me::Sample],
nbits: usize,
) -> Result<Vec<Me::Sample>, PunctureError> {
let n = self.n;
if nbits > MAX_SUPPORTED_INFO_BITS {
return Err(PunctureError::PayloadTooLarge {
got: nbits,
cap: MAX_SUPPORTED_INFO_BITS,
});
}
let expected = self.expected_punctured_len(nbits);
match expected {
Some(e) if e == received.len() => {}
Some(e) => {
return Err(PunctureError::MisalignedInput {
got: received.len(),
expected: e,
})
}
None => {
return Err(PunctureError::MisalignedInput {
got: received.len(),
expected: usize::MAX,
})
}
}
let full_len = match nbits
.checked_add(self.m)
.and_then(|stages| stages.checked_mul(n))
{
Some(len) => len,
None => {
return Err(PunctureError::MisalignedInput {
got: received.len(),
expected: usize::MAX,
})
}
};
let mut out: Vec<Me::Sample> = Vec::new();
out.try_reserve(full_len)
.map_err(|_| PunctureError::AllocationFailed)?;
let body_len = full_len - self.m * n;
out.resize(body_len, Me::erasure());
let mut ri = 0usize;
for (idx, kept) in body_keep_map(&self.matrix, n, nbits) {
if kept {
out[idx] = received[ri];
ri += 1;
}
}
for &s in &received[ri..] {
out.push(s);
}
debug_assert_eq!(out.len(), full_len);
Ok(out)
}
fn column_kept(&self, col: usize) -> usize {
debug_assert!(
col < self.matrix.period,
"column_kept: col must be < period"
);
self.matrix.keep.iter().filter(|row| row[col]).count()
}
}
#[cfg(test)]
mod tests {
use super::raw_output_symbol;
use crate::params::CodeParams;
use crate::trellis::output_symbol;
#[test]
fn raw_output_symbol_matches_trellis_output_symbol_minus_inversion() {
for params in [CodeParams::ccsds_r1_2(), CodeParams::ccsds_r1_3()] {
let n = params.generators().len();
let inv_mask: u32 = params
.invert_outputs()
.iter()
.enumerate()
.filter(|(_, &inv)| inv)
.fold(0u32, |mask, (i, _)| mask | (1u32 << (n - 1 - i)));
let states = 1usize << (params.k() - 1);
for state in 0..states {
for input in 0u8..2 {
let raw = raw_output_symbol(state, input, ¶ms);
let inverted = output_symbol(state, input, ¶ms);
assert_eq!(
inverted,
raw ^ inv_mask,
"raw_output_symbol drifted from output_symbol at \
state={state} input={input} (k={})",
params.k()
);
}
}
}
}
}