use crate::block::{BlockCode, SoftDecode, container_traits, expect_len};
use crate::conv::Trellis;
use crate::turbo::{TurboCode, TurboDecoder};
use comnum_core::{Error, Result, ensure, ensure_shape};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BitSelection {
positions: Vec<usize>,
full_len: usize,
}
impl BitSelection {
pub fn new(positions: Vec<usize>, full_len: usize) -> Result<BitSelection> {
ensure(positions.iter().all(|&p| p < full_len), || {
format!("a selected bit lies beyond the word of {full_len} bits")
})?;
Ok(BitSelection { positions, full_len })
}
pub fn len(&self) -> usize {
self.positions.len()
}
pub fn is_empty(&self) -> bool {
self.positions.is_empty()
}
pub fn full_len(&self) -> usize {
self.full_len
}
pub fn positions(&self) -> &[usize] {
&self.positions
}
pub fn select<T: Copy>(&self, word: &[T]) -> Result<Vec<T>> {
expect_len(word.len(), self.full_len, "bits of the word")?;
Ok(self.positions.iter().map(|&p| word[p]).collect())
}
pub fn accumulate_into(&self, llr: &[f64], out: &mut [f64]) -> Result<()> {
expect_len(llr.len(), self.positions.len(), "LLRs")?;
expect_len(out.len(), self.full_len, "LLRs of the word")?;
out.fill(0.0);
for (&p, &l) in self.positions.iter().zip(llr) {
out[p] += l;
}
Ok(())
}
pub fn word_llr(&self, llr: &[f64]) -> Result<Vec<f64>> {
let mut out = vec![0.0; self.full_len];
self.accumulate_into(llr, &mut out)?;
Ok(out)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Puncturer {
n_out: usize,
period: usize,
keep: Vec<bool>,
}
impl Puncturer {
pub fn new(pattern: &[&[u8]]) -> Result<Puncturer> {
let n_out = pattern.len();
ensure(n_out >= 1, || "puncturing: the pattern needs at least one row".into())?;
let period = pattern[0].len();
ensure(period >= 1, || "puncturing: the pattern needs at least one column".into())?;
ensure(pattern.iter().all(|r| r.len() == period), || "puncturing: the rows must have the same length".into())?;
ensure(pattern.iter().flat_map(|r| r.iter()).all(|&b| b <= 1), || {
"puncturing: the pattern entries must be 0 or 1".into()
})?;
let keep: Vec<bool> = (0..period).flat_map(|p| pattern.iter().map(move |r| r[p] == 1)).collect();
ensure(keep.iter().any(|&k| k), || "puncturing: the pattern keeps no bit".into())?;
Ok(Puncturer { n_out, period, keep })
}
pub fn identity(n_out: usize) -> Result<Puncturer> {
let row = [1u8];
let rows: Vec<&[u8]> = vec![&row; n_out];
Puncturer::new(&rows)
}
fn of(pattern: &[&[u8]]) -> Puncturer {
Puncturer::new(pattern).unwrap_or_else(|_| unreachable!("a valid pattern"))
}
pub fn ieee80211_2_3() -> Puncturer {
Puncturer::of(&[&[1, 1], &[1, 0]])
}
pub fn ieee80211_3_4() -> Puncturer {
Puncturer::of(&[&[1, 1, 0], &[1, 0, 1]])
}
pub fn ieee80211_5_6() -> Puncturer {
Puncturer::of(&[&[1, 1, 0, 1, 0], &[1, 0, 1, 0, 1]])
}
pub fn dvbs_2_3() -> Puncturer {
Puncturer::ieee80211_2_3()
}
pub fn dvbs_3_4() -> Puncturer {
Puncturer::ieee80211_3_4()
}
pub fn dvbs_5_6() -> Puncturer {
Puncturer::ieee80211_5_6()
}
pub fn dvbs_7_8() -> Puncturer {
Puncturer::of(&[&[1, 1, 1, 1, 0, 1, 0], &[1, 0, 0, 0, 1, 0, 1]])
}
pub fn turbo_1_2() -> Puncturer {
Puncturer::of(&[&[1, 1], &[1, 0], &[0, 1]])
}
pub fn from_name(name: &str) -> Result<Puncturer> {
Ok(match name.to_ascii_lowercase().as_str() {
"ieee80211_2_3" => Puncturer::ieee80211_2_3(),
"ieee80211_3_4" => Puncturer::ieee80211_3_4(),
"ieee80211_5_6" => Puncturer::ieee80211_5_6(),
"dvbs_2_3" => Puncturer::dvbs_2_3(),
"dvbs_3_4" => Puncturer::dvbs_3_4(),
"dvbs_5_6" => Puncturer::dvbs_5_6(),
"dvbs_7_8" => Puncturer::dvbs_7_8(),
"turbo_1_2" => Puncturer::turbo_1_2(),
other => {
return Err(Error::Config(format!(
"unknown puncturing pattern {other:?} (ieee80211_2_3, ieee80211_3_4, ieee80211_5_6, \
dvbs_2_3, dvbs_3_4, dvbs_5_6, dvbs_7_8, turbo_1_2)"
)));
}
})
}
pub fn n_out(&self) -> usize {
self.n_out
}
pub fn period(&self) -> usize {
self.period
}
pub fn kept_per_period(&self) -> usize {
self.keep.iter().filter(|&&k| k).count()
}
pub fn keeps(&self, step: usize, i: usize) -> bool {
self.keep[(step % self.period) * self.n_out + i]
}
pub fn punctured_len(&self, n_coded: usize) -> Result<usize> {
self.check_coded(n_coded)?;
Ok((0..n_coded).filter(|&j| self.keep[j % (self.period * self.n_out)]).count())
}
pub fn selection(&self, n_coded: usize) -> Result<BitSelection> {
self.check_coded(n_coded)?;
let cycle = self.period * self.n_out;
Ok(BitSelection { positions: (0..n_coded).filter(|&j| self.keep[j % cycle]).collect(), full_len: n_coded })
}
fn check_coded(&self, n_coded: usize) -> Result<()> {
if n_coded % self.n_out == 0 {
Ok(())
} else {
Err(Error::Shape(format!("{n_coded} coded bits is not a multiple of the {} outputs", self.n_out)))
}
}
pub fn puncture(&self, bits: &[u8]) -> Result<Vec<u8>> {
self.selection(bits.len())?.select(bits)
}
pub fn depuncture(&self, llr: &[f64], n_coded: usize) -> Result<Vec<f64>> {
self.selection(n_coded)?.word_llr(llr)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Punctured<C> {
code: C,
puncturer: Puncturer,
selection: BitSelection,
}
impl<C: BlockCode> Punctured<C> {
pub fn new(code: C, puncturer: Puncturer) -> Result<Punctured<C>> {
let selection = puncturer.selection(code.n())?;
Ok(Punctured { code, puncturer, selection })
}
pub fn code(&self) -> &C {
&self.code
}
pub fn puncturer(&self) -> &Puncturer {
&self.puncturer
}
pub fn selection(&self) -> &BitSelection {
&self.selection
}
}
impl<C: BlockCode> BlockCode for Punctured<C> {
fn k(&self) -> usize {
self.code.k()
}
fn n(&self) -> usize {
self.selection.len()
}
fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
self.selection.select(&self.code.encode_block(info)?)
}
fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
self.code.decode_block(&self.selection.word_llr(llr)?)
}
}
impl<C: SoftDecode> SoftDecode for Punctured<C> {
fn app_into(&self, llr: &[f64], apriori: Option<&[f64]>, out: &mut [f64]) -> Result<usize> {
self.code.app_into(&self.selection.word_llr(llr)?, apriori, out)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PuncturedTurbo {
code: TurboCode,
puncturer: Puncturer,
selection: BitSelection,
}
impl PuncturedTurbo {
pub fn new(code: TurboCode, puncturer: Puncturer) -> Result<PuncturedTurbo> {
ensure(puncturer.n_out() == 3, || {
format!("puncturing a turbo code needs a pattern of 3 rows, got {}", puncturer.n_out())
})?;
let k = code.k();
let mut positions: Vec<usize> =
puncturer.selection(3 * k)?.positions().iter().map(|&j| (j % 3) * k + j / 3).collect();
positions.extend(3 * k..code.n());
let selection = BitSelection { positions, full_len: code.n() };
Ok(PuncturedTurbo { code, puncturer, selection })
}
pub fn code(&self) -> &TurboCode {
&self.code
}
pub fn puncturer(&self) -> &Puncturer {
&self.puncturer
}
pub fn decoder_mut(&mut self) -> &mut TurboDecoder {
&mut self.code.decoder
}
pub fn selection(&self) -> &BitSelection {
&self.selection
}
}
impl BlockCode for PuncturedTurbo {
fn k(&self) -> usize {
self.code.k()
}
fn n(&self) -> usize {
self.selection.len()
}
fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
self.selection.select(&self.code.encode_block(info)?)
}
fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
self.code.decode_block(&self.selection.word_llr(llr)?)
}
}
impl SoftDecode for PuncturedTurbo {
fn app_into(&self, llr: &[f64], apriori: Option<&[f64]>, out: &mut [f64]) -> Result<usize> {
self.code.app_into(&self.selection.word_llr(llr)?, apriori, out)
}
}
container_traits!(Punctured<C>, C: BlockCode);
container_traits!(PuncturedTurbo);
pub fn free_distance(trellis: &Trellis, puncturer: &Puncturer) -> Result<u32> {
let n = trellis.n_out;
ensure_shape(puncturer.n_out() == n, || {
format!("the pattern has {} rows for a code of {n} outputs", puncturer.n_out())
})?;
let (ns, p) = (trellis.n_states(), puncturer.period());
let weight = |o: u32, phase: usize| {
(0..n).filter(|&i| puncturer.keeps(phase, i) && (o >> (n - 1 - i)) & 1 == 1).count() as u32
};
let mut best = u32::MAX;
for start in 0..p {
let mut dist = vec![u32::MAX; ns * p];
let s1 = trellis.next[0][1];
let w1 = weight(trellis.out[0][1], start);
let ph1 = (start + 1) % p;
dist[s1 * p + ph1] = dist[s1 * p + ph1].min(w1);
if s1 == 0 {
best = best.min(w1);
}
for _ in 0..ns * p {
let mut changed = false;
for s in 0..ns {
for ph in 0..p {
let d = dist[s * p + ph];
if d == u32::MAX || s == 0 {
continue;
}
for u in 0..2 {
let s2 = trellis.next[s][u];
let d2 = d + weight(trellis.out[s][u], ph);
let ph2 = (ph + 1) % p;
if s2 == 0 {
best = best.min(d2);
} else if d2 < dist[s2 * p + ph2] {
dist[s2 * p + ph2] = d2;
changed = true;
}
}
}
}
if !changed {
break;
}
}
}
Ok(best)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::testing::awgn_llr;
use crate::bounds::bpsk_noise_variance;
use crate::conv::ConvolutionalCode;
use comnum_core::rng::Rng;
#[test]
fn puncture_and_depuncture_restore_the_positions() {
let p = Puncturer::ieee80211_3_4();
assert_eq!((p.n_out(), p.period(), p.kept_per_period()), (2, 3, 4));
let bits: Vec<u8> = (0..12).map(|i| (i % 3 == 0) as u8).collect();
let punctured = p.puncture(&bits).unwrap();
assert_eq!(punctured, [bits[0], bits[1], bits[2], bits[5], bits[6], bits[7], bits[8], bits[11]]);
let llr: Vec<f64> = punctured
.iter()
.enumerate()
.map(|(i, &b)| if b == 1 { -(i as f64 + 1.0) } else { i as f64 + 1.0 })
.collect();
let full = p.depuncture(&llr, 12).unwrap();
assert_eq!(full.len(), 12);
for (j, v) in full.iter().enumerate() {
if p.keeps(j / 2, j % 2) {
assert_eq!(u8::from(*v < 0.0), bits[j]);
} else {
assert_eq!(*v, 0.0, "a punctured position carries a zero LLR");
}
}
assert!(p.puncture(&bits[..5]).is_err());
assert!(p.depuncture(&llr[..7], 12).is_err());
assert!(Puncturer::new(&[&[1, 1], &[1]]).is_err());
assert!(Puncturer::new(&[&[0, 0], &[0, 0]]).is_err());
assert!(Puncturer::new(&[&[2]]).is_err());
assert!(Puncturer::new(&[]).is_err());
assert_eq!(p.punctured_len(8).unwrap(), 6);
}
fn former_streams_to_steps<T: Copy>(k: usize, v: &[T]) -> Vec<T> {
(0..k).flat_map(|i| [v[i], v[k + i], v[2 * k + i]]).collect()
}
fn former_depuncture(p: &Puncturer, llr: &[f64], n_coded: usize) -> Vec<f64> {
let cycle = p.period * p.n_out;
let mut src = llr.iter();
(0..n_coded).map(|j| if p.keep[j % cycle] { src.next().copied().unwrap_or(0.0) } else { 0.0 }).collect()
}
#[test]
fn the_selections_give_the_former_punctured_words_and_llrs() {
let mut rng = Rng::new(12);
for p in [Puncturer::ieee80211_3_4(), Puncturer::dvbs_7_8(), Puncturer::turbo_1_2()] {
for n_coded in [0, p.n_out(), 7 * p.n_out(), 120] {
if n_coded % p.n_out() != 0 {
continue;
}
let bits: Vec<u8> = (0..n_coded).map(|_| rng.bit()).collect();
let cycle = p.period * p.n_out;
let former: Vec<u8> =
bits.iter().enumerate().filter(|(j, _)| p.keep[j % cycle]).map(|(_, &b)| b).collect();
assert_eq!(p.puncture(&bits).unwrap(), former);
let llr: Vec<f64> = (0..former.len()).map(|_| rng.gaussian()).collect();
let (new, old) = (p.depuncture(&llr, n_coded).unwrap(), former_depuncture(&p, &llr, n_coded));
assert!(new.len() == old.len() && new.iter().zip(&old).all(|(a, b)| a.to_bits() == b.to_bits()));
}
}
let turbo = PuncturedTurbo::new(TurboCode::lte(40).unwrap(), Puncturer::turbo_1_2()).unwrap();
let llr: Vec<f64> = (0..turbo.n()).map(|_| rng.gaussian()).collect();
let body = former_depuncture(&Puncturer::turbo_1_2(), &llr[..80], 120);
let mut former: Vec<f64> =
(0..3).flat_map(|s| (0..40).map(move |i| (s, i))).map(|(s, i)| body[3 * i + s]).collect();
former.extend_from_slice(&llr[80..]);
let new = turbo.selection().word_llr(&llr).unwrap();
assert!(new.iter().zip(&former).all(|(a, b)| a.to_bits() == b.to_bits()));
assert!(BitSelection::new(vec![0, 4], 4).is_err());
}
#[test]
fn the_soft_output_passes_through_the_puncturing() {
let mut rng = Rng::new(13);
let code = Punctured::new(ConvolutionalCode::k7(50).unwrap(), Puncturer::ieee80211_2_3()).unwrap();
let llr: Vec<f64> = (0..code.n()).map(|_| rng.gaussian()).collect();
let app = code.app(&llr, None).unwrap();
let mother = code.code().app(&code.puncturer().depuncture(&llr, code.code().n()).unwrap(), None).unwrap();
assert!(app.iter().zip(&mother).all(|(a, b)| a.to_bits() == b.to_bits()));
let mut turbo = PuncturedTurbo::new(TurboCode::lte(40).unwrap(), Puncturer::turbo_1_2()).unwrap();
turbo.decoder_mut().iterations = 3;
let llr: Vec<f64> = (0..turbo.n()).map(|_| rng.gaussian()).collect();
assert_eq!(turbo.code().decoder.iterations, 3);
assert_eq!(
comnum_core::signal::hard_decisions(&turbo.app(&llr, None).unwrap()),
turbo.decode_block(&llr).unwrap()
);
}
#[test]
fn punctured_codes_decode_without_error_at_high_snr() {
let mut rng = Rng::new(7);
for puncturer in [
Puncturer::ieee80211_2_3(),
Puncturer::ieee80211_3_4(),
Puncturer::ieee80211_5_6(),
Puncturer::dvbs_2_3(),
Puncturer::dvbs_3_4(),
Puncturer::dvbs_5_6(),
Puncturer::dvbs_7_8(),
] {
let code = Punctured::new(ConvolutionalCode::k7(300).unwrap(), puncturer).unwrap();
let info: Vec<u8> = (0..300).map(|_| rng.bit()).collect();
let c = code.encode_block(&info).unwrap();
assert_eq!(c.len(), code.n());
let y = awgn_llr(&c, bpsk_noise_variance(10.0, code.code_rate()), &mut rng);
assert_eq!(code.decode_block(&y).unwrap(), info, "{:?}", code.puncturer());
let mut hard = code.code().clone();
hard.hard_decision = true;
let hard_code = Punctured::new(hard, code.puncturer().clone()).unwrap();
assert_eq!(hard_code.decode_block(&y).unwrap(), info);
}
let turbo = PuncturedTurbo::new(TurboCode::lte(40).unwrap(), Puncturer::turbo_1_2()).unwrap();
assert_eq!(turbo.n(), 2 * 40 + 12);
assert!((turbo.code_rate() - 40.0 / 92.0).abs() < 1e-12);
let info: Vec<u8> = (0..40).map(|_| rng.bit()).collect();
let c = turbo.encode_block(&info).unwrap();
let mother = turbo.code().encode_block(&info).unwrap();
assert_eq!(
&c[..80],
&former_streams_to_steps(40, &mother[..120])
.iter()
.enumerate()
.filter(|(j, _)| Puncturer::turbo_1_2().keep[j % 6])
.map(|(_, &b)| b)
.collect::<Vec<u8>>()[..]
);
assert_eq!(c[80..], mother[120..]);
let y = awgn_llr(&c, bpsk_noise_variance(6.0, turbo.code_rate()), &mut rng);
assert_eq!(turbo.decode_block(&y).unwrap(), info);
assert!(PuncturedTurbo::new(TurboCode::lte(40).unwrap(), Puncturer::ieee80211_2_3()).is_err());
}
#[test]
fn the_rate_3_4_code_lies_between_rate_1_2_and_uncoded_bpsk() {
let ebn0 = 4.0;
let (k, blocks) = (2000, 25);
let half = ConvolutionalCode::k7(k).unwrap();
let three_quarters = Punctured::new(ConvolutionalCode::k7(k).unwrap(), Puncturer::ieee80211_3_4()).unwrap();
let mut rng = Rng::new(34);
let (mut e_half, mut e_34, mut e_uncoded) = (0usize, 0usize, 0usize);
for _ in 0..blocks {
let info: Vec<u8> = (0..k).map(|_| rng.bit()).collect();
let errors = |decoded: &[u8]| decoded.iter().zip(&info).filter(|(a, b)| a != b).count();
let c = half.encode_block(&info).unwrap();
let y = awgn_llr(&c, bpsk_noise_variance(ebn0, half.code_rate()), &mut rng);
e_half += errors(&half.decode_block(&y).unwrap());
let c = three_quarters.encode_block(&info).unwrap();
let y = awgn_llr(&c, bpsk_noise_variance(ebn0, three_quarters.code_rate()), &mut rng);
e_34 += errors(&three_quarters.decode_block(&y).unwrap());
let y = awgn_llr(&info, bpsk_noise_variance(ebn0, 1.0), &mut rng);
e_uncoded += errors(&comnum_core::signal::hard_decisions(&y));
}
assert!(e_half < e_34 && e_34 < e_uncoded, "rate 1/2: {e_half}, rate 3/4: {e_34}, uncoded: {e_uncoded}");
assert!(
e_34 * 2 < e_uncoded && e_half * 2 < e_34,
"rate 1/2: {e_half}, rate 3/4: {e_34}, uncoded: {e_uncoded}"
);
}
}
#[cfg(test)]
mod literature {
use super::*;
#[test]
fn yasuda_1984_table_i_free_distances_of_the_k7_punctured_codes() {
let t = Trellis::feedforward(7, &[0o133, 0o171]).unwrap();
assert_eq!(free_distance(&t, &Puncturer::identity(2).unwrap()).unwrap(), 10);
assert_eq!(free_distance(&t, &Puncturer::dvbs_2_3()).unwrap(), 6);
assert_eq!(free_distance(&t, &Puncturer::dvbs_3_4()).unwrap(), 5);
assert_eq!(free_distance(&t, &Puncturer::dvbs_5_6()).unwrap(), 4);
assert_eq!(free_distance(&t, &Puncturer::dvbs_7_8()).unwrap(), 3);
assert_eq!(free_distance(&t, &Puncturer::ieee80211_2_3()).unwrap(), 6);
assert_eq!(free_distance(&t, &Puncturer::ieee80211_3_4()).unwrap(), 5);
assert_eq!(free_distance(&t, &Puncturer::ieee80211_5_6()).unwrap(), 4);
assert!(free_distance(&t, &Puncturer::turbo_1_2()).is_err());
assert_eq!(free_distance(&t, &Puncturer::new(&[&[1, 0], &[1, 1]]).unwrap()).unwrap(), 5);
}
#[test]
fn etsi_en_300_421_table_2_is_the_80211_pattern_at_the_shared_rates() {
let table: [(&[u8], &[u8], Puncturer); 3] = [
(&[1, 0], &[1, 1], Puncturer::dvbs_2_3()),
(&[1, 0, 1], &[1, 1, 0], Puncturer::dvbs_3_4()),
(&[1, 0, 1, 0, 1], &[1, 1, 0, 1, 0], Puncturer::dvbs_5_6()),
];
for (x, y, alias) in table {
assert_eq!(Puncturer::new(&[y, x]).unwrap(), alias);
}
}
#[test]
fn yasuda_1984_tables_i_ii_every_punctured_code() {
let generators =
[(0o5, 0o7), (0o15, 0o17), (0o23, 0o35), (0o53, 0o75), (0o133, 0o171), (0o247, 0o371), (0o561, 0o753)];
let mother: [u32; 7] = [5, 6, 7, 8, 10, 10, 12];
let table: [[(&str, &str, u32); 7]; 12] = [
[
("10", "11", 3),
("11", "10", 4),
("11", "10", 4),
("10", "11", 6),
("11", "10", 6),
("10", "11", 7),
("11", "10", 7),
],
[
("101", "110", 3),
("110", "101", 4),
("101", "110", 3),
("100", "111", 4),
("110", "101", 5),
("110", "101", 6),
("111", "100", 6),
],
[
("1011", "1100", 2),
("1011", "1100", 3),
("1010", "1101", 3),
("1000", "1111", 4),
("1111", "1000", 4),
("1010", "1101", 5),
("1101", "1010", 5),
],
[
("10111", "11000", 2),
("10100", "11011", 3),
("10111", "11000", 3),
("10000", "11111", 4),
("11010", "10101", 4),
("11100", "10011", 4),
("10110", "11001", 5),
],
[
("101111", "110000", 2),
("100011", "111100", 2),
("101010", "110101", 3),
("110110", "101001", 3),
("111010", "100101", 3),
("101001", "110110", 4),
("110110", "101001", 4),
],
[
("1011111", "1100000", 2),
("1000010", "1111101", 2),
("1010011", "1101100", 3),
("1011101", "1100010", 3),
("1111010", "1000101", 3),
("1010100", "1101011", 4),
("1101011", "1010100", 4),
],
[
("10111111", "11000000", 2),
("10000011", "11111100", 2),
("10100011", "11011100", 3),
("11100010", "10011101", 3),
("11110100", "10001011", 3),
("10110110", "11001001", 3),
("11100000", "10011111", 4),
],
[
("101111111", "110000000", 2),
("101000000", "110111111", 2),
("111110011", "100001100", 2),
("100001111", "111110000", 3),
("111101110", "100010001", 3),
("101100110", "110011001", 4),
("111000101", "100111010", 4),
],
[
("1011111111", "1100000000", 2),
("1000000011", "1111111100", 2),
("1000000101", "1111111010", 2),
("1001110100", "1110001011", 3),
("1110110111", "1001001000", 3),
("1001000011", "1110111100", 3),
("1000101100", "1111010011", 4),
],
[
("10111111111", "11000000000", 2),
("10000000010", "11111111101", 2),
("10101101101", "11010010010", 2),
("10001110100", "11110001011", 3),
("11110111110", "10001000001", 3),
("10110000110", "11001111001", 3),
("11000010001", "10111101110", 4),
],
[
("101111111111", "110000000000", 2),
("100000000011", "111111111100", 2),
("101101111011", "110010000100", 2),
("110100110110", "101011001001", 3),
("111111110101", "100000001010", 3),
("100100001100", "111011110011", 3),
("110000011010", "101111100101", 4),
],
[
("1011111111111", "1100000000000", 2),
("1010000000000", "1101111111111", 2),
("1110110110111", "1001001001000", 2),
("1100011000100", "1011100111011", 3),
("1101000001111", "1010111110000", 3),
("1010100100000", "1101011011111", 3),
("1100000100001", "1011111011110", 4),
],
];
let bits = |s: &str| s.bytes().map(|b| b - b'0').collect::<Vec<u8>>();
for (j, &(g1, g2)) in generators.iter().enumerate() {
let t = Trellis::feedforward(j as u32 + 3, &[g1, g2]).unwrap();
assert_eq!(free_distance(&t, &Puncturer::identity(2).unwrap()).unwrap(), mother[j], "nu = {}", j + 2);
for row in &table {
let (a, b, d) = row[j];
let (a, b) = (bits(a), bits(b));
let p = Puncturer::new(&[&a, &b]).unwrap();
assert_eq!(free_distance(&t, &p).unwrap(), d, "nu = {}, rate {}/{}", j + 2, a.len(), a.len() + 1);
}
}
}
}