Skip to main content

comnum_fec/
conv.rs

1//! Convolutional codes: trellis, Viterbi and BCJR decoders.
2//!
3//! A [`Trellis`] describes a rate-$1/n$ code with $2^{K-1}$ states: from every state, an
4//! input bit leads to a next state and emits $n$ coded bits. Two families are built:
5//!
6//! - feed-forward codes ([`Trellis::feedforward`]) from octal generators, the most
7//!   significant bit tapping the current input — the $(133, 171)_{8}$ code of constraint
8//!   length 7 ([`ConvolutionalCode::k7`]) is the classical one (802.11, DVB-S, LTE);
9//! - recursive systematic codes ([`Trellis::recursive`]), the constituents of turbo
10//!   codes, with a feedback and a feed-forward polynomial (LTE: $(13, 15)_{8}$).
11//!
12//! # Decoders
13//!
14//! - [`viterbi`]: maximum-likelihood sequence detection; with LLR inputs, the branch
15//!   metric is the correlation $\sum_{i} (1 - 2 c_{i}) L_{i} / 2$ (soft decisions), with
16//!   hard decisions it is the Hamming distance — about 2 dB apart (Viterbi 1967;
17//!   Forney 1973);
18//! - [`viterbi_tail_biting`]: a tail-biting code, whose path ends in the state it starts
19//!   from ([`Trellis::encode_tail_biting`], the code of the PBCH of LTE), by the
20//!   wrap-around Viterbi algorithm of Shao, Lin and Fossorier (2003);
21//! - [`bcjr`]: the a posteriori LLR of every input bit (Bahl, Cocke, Jelinek & Raviv
22//!   1974), in the log domain with the exact Jacobian logarithm (log-MAP) or its
23//!   maximum (max-log-MAP).
24//!
25//! # Examples
26//! ```
27//! use comnum_fec::block::BlockCode;
28//! use comnum_fec::conv::ConvolutionalCode;
29//! let code = ConvolutionalCode::k7(100)?;
30//! let info: Vec<u8> = (0..100).map(|i| ((i * 7) % 3 == 0) as u8).collect();
31//! let c = code.encode_block(&info)?;
32//! assert_eq!(c.len(), 2 * (100 + 6));                           // terminated
33//! let llr: Vec<f64> = c.iter().map(|&b| if b == 0 { 4.0 } else { -4.0 }).collect(); // noiseless
34//! assert_eq!(code.decode_block(&llr)?, info);
35//! # Ok::<(), comnum_core::Error>(())
36//! ```
37//!
38//! # References
39//!
40//! 1. A. J. Viterbi, "Error bounds for convolutional codes and an asymptotically optimum
41//!    decoding algorithm," *IEEE Trans. Inf. Theory*, 13(2), 260–269, 1967.
42//! 2. G. D. Forney, "The Viterbi algorithm," *Proc. IEEE*, 61(3), 268–278, 1973.
43//! 3. L. R. Bahl, J. Cocke, F. Jelinek and J. Raviv, "Optimal decoding of linear codes
44//!    for minimizing symbol error rate," *IEEE Trans. Inf. Theory*, 20(2), 284–287, 1974.
45//! 4. P. Robertson, E. Villebrun and P. Hoeher, "A comparison of optimal and sub-optimal
46//!    MAP decoding algorithms operating in the log domain," *Proc. IEEE ICC*, 1995.
47//! 5. R. Y. Shao, S. Lin and M. P. C. Fossorier, "Two decoding algorithms for tailbiting
48//!    codes," *IEEE Trans. Commun.* 51(10), 1658–1665, 2003 (tail biting, the WAVA).
49
50use crate::block::{BlockCode, Jacobian, SoftDecode, container_traits, expect_len, max_star};
51use comnum_core::{Result, ensure, ensure_shape};
52
53fn parity(x: u32) -> u8 {
54    (x.count_ones() & 1) as u8
55}
56
57/// The trellis of a rate-$1/n$ binary convolutional code, built by
58/// [`Trellis::feedforward`] or [`Trellis::recursive`] (its fields are read through
59/// getters, so that the tables always agree with the constraint length and the number
60/// of outputs).
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct Trellis {
63    /// Constraint length $K$ (memory $K - 1$).
64    pub(crate) constraint: u32,
65    /// Coded bits per input bit $n$.
66    pub(crate) n_out: usize,
67    /// `next[s][u]`: the state after input `u` in state `s`.
68    pub(crate) next: Vec<[usize; 2]>,
69    /// `out[s][u]`: the coded bits, first output in the most significant position.
70    pub(crate) out: Vec<[u32; 2]>,
71    /// `tail[s]`: the input that drives state `s` towards zero (0 for feed-forward codes).
72    pub(crate) tail: Vec<u8>,
73}
74
75impl Trellis {
76    /// Number of states $2^{K-1}$.
77    pub fn n_states(&self) -> usize {
78        self.next.len()
79    }
80
81    /// Constraint length $K$ (memory $K - 1$).
82    pub fn constraint(&self) -> u32 {
83        self.constraint
84    }
85
86    /// Coded bits per input bit $n$ (at least 1).
87    pub fn n_out(&self) -> usize {
88        self.n_out
89    }
90
91    /// `next()[s][u]`: the state after input `u` in state `s`.
92    pub fn next(&self) -> &[[usize; 2]] {
93        &self.next
94    }
95
96    /// `out()[s][u]`: the coded bits of the branch, the first output in the most
97    /// significant position.
98    pub fn out(&self) -> &[[u32; 2]] {
99        &self.out
100    }
101
102    /// `tail()[s]`: the input that drives state `s` towards zero (0 for feed-forward
103    /// codes).
104    pub fn tail(&self) -> &[u8] {
105        &self.tail
106    }
107
108    /// The feed-forward code of constraint length `constraint` with the generators
109    /// `generators` (octal digits written as integers, e.g. `0o133`).
110    ///
111    /// # Errors
112    /// [`comnum_core::Error::Config`] for a constraint length outside 1 to 24 or no generator.
113    pub fn feedforward(constraint: u32, generators: &[u32]) -> Result<Trellis> {
114        let m = Self::memory(constraint)?;
115        ensure(!generators.is_empty(), || "a convolutional code needs at least one generator".into())?;
116        let states = 1usize << m;
117        let mut next = Vec::with_capacity(states);
118        let mut out = Vec::with_capacity(states);
119        for s in 0..states as u32 {
120            let mut nx = [0usize; 2];
121            let mut o = [0u32; 2];
122            for u in 0..2u32 {
123                let reg = (u << m) | s;
124                nx[u as usize] = (reg >> 1) as usize;
125                o[u as usize] = generators.iter().fold(0, |acc, g| (acc << 1) | u32::from(parity(reg & g)));
126            }
127            next.push(nx);
128            out.push(o);
129        }
130        Ok(Trellis { constraint, n_out: generators.len(), next, out, tail: vec![0; states] })
131    }
132
133    /// The memory `constraint - 1` of a code, checked.
134    fn memory(constraint: u32) -> Result<u32> {
135        ensure((1..=24).contains(&constraint), || format!("the constraint length must be 1 to 24, got {constraint}"))?;
136        Ok(constraint - 1)
137    }
138
139    /// The recursive systematic code with the feedback polynomial `feedback` and the
140    /// feed-forward polynomial `forward` (octal, e.g. `0o13`, `0o15`): outputs the input
141    /// bit, then the parity bit.
142    ///
143    /// # Errors
144    /// [`comnum_core::Error::Config`] for a constraint length outside 1 to 24.
145    pub fn recursive(constraint: u32, feedback: u32, forward: u32) -> Result<Trellis> {
146        Trellis::recursive_parities(constraint, feedback, &[forward])
147    }
148
149    /// The recursive systematic code with the feedback polynomial `feedback` and several
150    /// feed-forward polynomials `forwards` (octal, the most significant bit the current
151    /// register input $w_{t} = u_{t} \oplus \dots$): outputs the input bit, then one parity
152    /// bit per polynomial, in their order ([`Trellis::recursive`] is the case of one
153    /// polynomial); the constituent codes of the turbo codes of CCSDS 131.0-B (Sec. 6.3)
154    /// have up to three.
155    ///
156    /// # Errors
157    /// [`comnum_core::Error::Config`] for a constraint length outside 1 to 24, no
158    /// polynomial, or more than 31 outputs.
159    ///
160    /// # Examples
161    /// ```
162    /// use comnum_fec::conv::Trellis;
163    /// // the 16-state code of CCSDS rate 1/6, encoder a: G0 = 10011, G1, G2, G3
164    /// let t = Trellis::recursive_parities(5, 0o23, &[0o33, 0o25, 0o37])?;
165    /// assert_eq!((t.n_states(), t.n_out()), (16, 4));
166    /// assert_eq!(Trellis::recursive_parities(4, 0o13, &[0o15])?, Trellis::recursive(4, 0o13, 0o15)?);
167    /// # Ok::<(), comnum_core::Error>(())
168    /// ```
169    pub fn recursive_parities(constraint: u32, feedback: u32, forwards: &[u32]) -> Result<Trellis> {
170        let m = Self::memory(constraint)?;
171        ensure(!forwards.is_empty() && forwards.len() < 31, || {
172            format!("a recursive code needs 1 to 30 feed-forward polynomials, got {}", forwards.len())
173        })?;
174        let states = 1usize << m;
175        let fb = feedback & ((1 << m) - 1);
176        let mut next = Vec::with_capacity(states);
177        let mut out = Vec::with_capacity(states);
178        let mut tail = Vec::with_capacity(states);
179        for s in 0..states as u32 {
180            let mut nx = [0usize; 2];
181            let mut o = [0u32; 2];
182            for u in 0..2u32 {
183                let w = u ^ u32::from(parity(s & fb));
184                let reg = (w << m) | s;
185                nx[u as usize] = (reg >> 1) as usize;
186                o[u as usize] = forwards.iter().fold(u, |acc, &g| (acc << 1) | u32::from(parity(reg & g)));
187            }
188            next.push(nx);
189            out.push(o);
190            tail.push(parity(s & fb));
191        }
192        Ok(Trellis { constraint, n_out: forwards.len() + 1, next, out, tail })
193    }
194
195    /// Encode `bits` from state 0; with `terminate`, append the $K - 1$ tail inputs that
196    /// return to state 0. Returns the coded bits.
197    pub fn encode(&self, bits: &[u8], terminate: bool) -> Vec<u8> {
198        let tail = if terminate { self.constraint as usize - 1 } else { 0 };
199        let mut out = Vec::with_capacity((bits.len() + tail) * self.n_out);
200        let mut s = self.encode_from(0, bits, &mut out);
201        for _ in 0..tail {
202            let u = self.tail[s];
203            s = self.encode_from(s, &[u], &mut out);
204        }
205        out
206    }
207
208    /// Encode `bits` with tail biting: the register starts in the state that its last
209    /// $K - 1$ inputs leave it in, so that the path ends in the state it starts from and
210    /// no tail is sent (the code of LTE, TS 36.212 Sec. 5.1.3.1). Returns the $n$ coded
211    /// bits of every input bit.
212    ///
213    /// # Errors
214    /// [`comnum_core::Error::Config`] for a recursive code (its state depends on more than
215    /// the last $K - 1$ inputs); [`comnum_core::Error::Shape`] for fewer than $K - 1$ bits.
216    ///
217    /// # Examples
218    /// ```
219    /// use comnum_fec::conv::{Trellis, viterbi_tail_biting};
220    /// let t = Trellis::feedforward(7, &[0o133, 0o171, 0o165])?;   // the code of LTE
221    /// let bits: Vec<u8> = (0..40).map(|i| ((i * 5) % 7 < 3) as u8).collect();
222    /// let c = t.encode_tail_biting(&bits)?;
223    /// assert_eq!(c.len(), 3 * 40);                                 // no tail
224    /// let llr: Vec<f64> = c.iter().map(|&b| if b == 0 { 2.0 } else { -2.0 }).collect();
225    /// assert_eq!(viterbi_tail_biting(&t, &llr, 4)?, bits);
226    /// # Ok::<(), comnum_core::Error>(())
227    /// ```
228    pub fn encode_tail_biting(&self, bits: &[u8]) -> Result<Vec<u8>> {
229        self.check_feedforward()?;
230        let m = self.constraint as usize - 1;
231        ensure_shape(bits.len() >= m, || format!("tail biting needs at least K - 1 = {m} bits, got {}", bits.len()))?;
232        let start = bits[bits.len() - m..].iter().fold(0, |s, &b| self.next[s][usize::from(b & 1)]);
233        let mut out = Vec::with_capacity(bits.len() * self.n_out);
234        self.encode_from(start, bits, &mut out);
235        Ok(out)
236    }
237
238    /// Encode `bits` from the state `s` into `out`; returns the final state.
239    fn encode_from(&self, mut s: usize, bits: &[u8], out: &mut Vec<u8>) -> usize {
240        for &b in bits {
241            let u = usize::from(b & 1);
242            let o = self.out[s][u];
243            for i in (0..self.n_out).rev() {
244                out.push(((o >> i) & 1) as u8);
245            }
246            s = self.next[s][u];
247        }
248        s
249    }
250
251    /// Whether the next state is the register shifted by the input (a feed-forward code).
252    fn check_feedforward(&self) -> Result<()> {
253        let m = self.constraint - 1;
254        let ff = self.next.iter().enumerate().all(|(s, nx)| (0..2).all(|u| nx[u] == ((u << m) | s) >> 1));
255        ensure(ff, || "tail biting is defined here for feed-forward codes only".into())
256    }
257
258    /// The log-likelihood of a branch, $\sum_{i} \pm L_{i}/2$.
259    fn branch(&self, o: u32, llr: &[f64]) -> f64 {
260        llr.iter().enumerate().map(|(i, l)| if (o >> (self.n_out - 1 - i)) & 1 == 0 { l / 2.0 } else { -l / 2.0 }).sum()
261    }
262
263    /// The log-likelihoods of the branches of one step into `metrics[s][u]` (flat,
264    /// `2 s + u`): through the $2^{n}$ output words when they are fewer than the
265    /// branches, each computed once.
266    fn branches(&self, llr: &[f64], words: &mut Vec<f64>, metrics: &mut [f64]) {
267        let n_words = if self.n_out < 16 { 1usize << self.n_out } else { usize::MAX };
268        if n_words <= metrics.len() {
269            words.clear();
270            words.extend((0..n_words as u32).map(|o| self.branch(o, llr)));
271            for (m, o) in metrics.iter_mut().zip(self.out.iter().flatten()) {
272                *m = words[*o as usize];
273            }
274        } else {
275            for (m, o) in metrics.iter_mut().zip(self.out.iter().flatten()) {
276                *m = self.branch(*o, llr);
277            }
278        }
279    }
280}
281
282/// Viterbi decoding of `steps = llr.len() / n` trellis steps from state 0; with
283/// `terminated`, the path ends in state 0 and the last $K - 1$ inputs (the tail) are
284/// dropped. Returns the decided input bits.
285///
286/// # Errors
287/// [`comnum_core::Error::Shape`] if the number of LLRs is not a multiple of $n$.
288pub fn viterbi(trellis: &Trellis, llr: &[f64], terminated: bool) -> Result<Vec<u8>> {
289    let steps = steps_of(trellis, llr)?;
290    let ns = trellis.n_states();
291    let mut metric = vec![f64::NEG_INFINITY; ns];
292    metric[0] = 0.0;
293    let mut from = vec![0u32; steps * ns];
294    forward(trellis, llr, &mut metric, &mut from);
295    let end = if terminated { 0 } else { best_state(&metric) };
296    let (mut bits, _) = traceback(&from, ns, end);
297    if terminated {
298        bits.truncate(steps.saturating_sub(trellis.constraint as usize - 1));
299    }
300    Ok(bits)
301}
302
303/// Viterbi decoding of a tail-biting code ([`Trellis::encode_tail_biting`]) by the
304/// wrap-around Viterbi algorithm (WAVA) of Shao, Lin and Fossorier (2003): a pass starts
305/// from every state with the metrics that the previous pass ended with (all zero for
306/// the first), and the decoding stops at the first pass whose best path starts and ends
307/// in the same state; after `passes` passes, the best path among those that start and
308/// end in the same state is kept (the best path if there is none). Returns the decided
309/// input bits, one per step.
310///
311/// # Errors
312/// [`comnum_core::Error::Shape`] if the number of LLRs is not a multiple of $n$;
313/// [`comnum_core::Error::Config`] for `passes = 0`.
314///
315/// # References
316/// R. Y. Shao, S. Lin and M. P. C. Fossorier, "Two decoding algorithms for tailbiting
317/// codes," *IEEE Trans. Commun.* 51(10), 1658–1665, 2003, doi:10.1109/TCOMM.2003.818084
318/// (Sec. III, the WAVA).
319pub fn viterbi_tail_biting(trellis: &Trellis, llr: &[f64], passes: usize) -> Result<Vec<u8>> {
320    ensure(passes > 0, || "the wrap-around Viterbi algorithm needs at least one pass".into())?;
321    let steps = steps_of(trellis, llr)?;
322    let ns = trellis.n_states();
323    let mut metric = vec![0.0; ns];
324    let mut from = vec![0u32; steps * ns];
325    for _ in 1..passes {
326        forward(trellis, llr, &mut metric, &mut from);
327        let end = best_state(&metric);
328        let (bits, first) = traceback(&from, ns, end);
329        if first == end {
330            return Ok(bits);
331        }
332        let top = metric[end];
333        for m in &mut metric {
334            *m -= top;
335        }
336    }
337    let start = metric.clone();
338    forward(trellis, llr, &mut metric, &mut from);
339    let end = best_state(&metric);
340    let (bits, first) = traceback(&from, ns, end);
341    if first == end {
342        return Ok(bits);
343    }
344    // the best tail-biting survivor of the last pass: the metric it gained in the pass
345    let best = (0..ns)
346        .map(|s| (s, traceback(&from, ns, s)))
347        .filter(|(s, (_, first))| first == s)
348        .max_by(|(a, (_, fa)), (b, (_, fb))| (metric[*a] - start[*fa]).total_cmp(&(metric[*b] - start[*fb])));
349    Ok(best.map_or(bits, |(_, (b, _))| b))
350}
351
352/// The trellis steps of `llr`, checked.
353fn steps_of(trellis: &Trellis, llr: &[f64]) -> Result<usize> {
354    let n = trellis.n_out;
355    ensure_shape(llr.len() % n == 0, || format!("{} LLRs is not a multiple of {n}", llr.len()))?;
356    Ok(llr.len() / n)
357}
358
359/// The add-compare-select recursion over the steps of `llr`, from the path metrics
360/// `metric` (updated in place); `from[t * ns + s]` receives the survivor of every step
361/// and state, its predecessor and input `2 s + u` (the states number at most 2^23).
362fn forward(trellis: &Trellis, llr: &[f64], metric: &mut Vec<f64>, from: &mut [u32]) {
363    let (n, ns) = (trellis.n_out, trellis.n_states());
364    let mut next = vec![f64::NEG_INFINITY; ns];
365    let (mut words, mut gamma) = (Vec::new(), vec![0.0; 2 * ns]);
366    for (t, row) in from.chunks_exact_mut(ns).enumerate() {
367        trellis.branches(&llr[t * n..(t + 1) * n], &mut words, &mut gamma);
368        next.fill(f64::NEG_INFINITY);
369        for s in 0..ns {
370            if metric[s] == f64::NEG_INFINITY {
371                continue;
372            }
373            for u in 0..2 {
374                let ns2 = trellis.next[s][u];
375                let m = metric[s] + gamma[2 * s + u];
376                if m > next[ns2] {
377                    next[ns2] = m;
378                    row[ns2] = (2 * s + u) as u32;
379                }
380            }
381        }
382        std::mem::swap(metric, &mut next);
383    }
384}
385
386/// The state of the largest path metric.
387fn best_state(metric: &[f64]) -> usize {
388    (0..metric.len()).max_by(|a, b| metric[*a].total_cmp(&metric[*b])).unwrap_or(0)
389}
390
391/// The inputs of the survivor that ends in `end`, and the state it starts from.
392fn traceback(from: &[u32], ns: usize, end: usize) -> (Vec<u8>, usize) {
393    let steps = from.len() / ns;
394    let mut s = end;
395    let mut bits = vec![0u8; steps];
396    for t in (0..steps).rev() {
397        let su = from[t * ns + s] as usize;
398        bits[t] = (su & 1) as u8;
399        s = su >> 1;
400    }
401    (bits, s)
402}
403
404/// The BCJR algorithm in the log domain: the a posteriori LLR of every input bit,
405/// given the channel LLRs of the coded bits (`n` per step) and the a priori LLRs of the
406/// inputs (`apriori`, zero if empty). `jacobian` selects log-MAP ([`Jacobian::Exact`])
407/// or max-log-MAP ([`Jacobian::MaxLog`]); `terminated` forces the final state to zero.
408///
409/// # Errors
410/// [`comnum_core::Error::Shape`] for inconsistent lengths.
411pub fn bcjr(trellis: &Trellis, llr: &[f64], apriori: &[f64], terminated: bool, jacobian: Jacobian) -> Result<Vec<f64>> {
412    let mut app = Vec::new();
413    bcjr_into(trellis, llr, apriori, terminated, jacobian, &mut BcjrWorkspace::default(), &mut app)?;
414    Ok(app)
415}
416
417/// The arrays of [`bcjr`], kept between the calls of an iterative decoder.
418#[derive(Default)]
419pub(crate) struct BcjrWorkspace {
420    /// $\gamma_{t}(s, u)$ at `(t ns + s) 2 + u`.
421    gamma: Vec<f64>,
422    /// $\alpha_{t}(s)$, then $\beta_{t}(s)$, at `t ns + s`, $t = 0, \dots, T$.
423    alpha: Vec<f64>,
424    beta: Vec<f64>,
425    words: Vec<f64>,
426}
427
428/// [`bcjr`] into `app`, with the arrays of `ws`.
429pub(crate) fn bcjr_into(
430    trellis: &Trellis,
431    llr: &[f64],
432    apriori: &[f64],
433    terminated: bool,
434    jacobian: Jacobian,
435    ws: &mut BcjrWorkspace,
436    app: &mut Vec<f64>,
437) -> Result<()> {
438    let n = trellis.n_out;
439    if llr.len() % n != 0 || (!apriori.is_empty() && apriori.len() != llr.len() / n) {
440        return Err(comnum_core::Error::Shape("inconsistent channel and a priori LLRs".into()));
441    }
442    let steps = llr.len() / n;
443    let ns = trellis.n_states();
444    let ninf = f64::NEG_INFINITY;
445    // the branch metrics, once per step and branch
446    ws.gamma.resize(steps * 2 * ns, 0.0);
447    for (t, g) in ws.gamma.chunks_exact_mut(2 * ns).enumerate() {
448        trellis.branches(&llr[t * n..(t + 1) * n], &mut ws.words, g);
449        let la = apriori.get(t).copied().unwrap_or(0.0);
450        for (i, g) in g.iter_mut().enumerate() {
451            *g += if i % 2 == 0 { la / 2.0 } else { -la / 2.0 };
452        }
453    }
454    let gamma = &ws.gamma;
455    let alpha = &mut ws.alpha;
456    alpha.clear();
457    alpha.resize((steps + 1) * ns, ninf);
458    alpha[0] = 0.0;
459    for t in 0..steps {
460        let (now, later) = alpha[t * ns..(t + 2) * ns].split_at_mut(ns);
461        for s in 0..ns {
462            if now[s] == ninf {
463                continue;
464            }
465            for u in 0..2 {
466                let s2 = trellis.next[s][u];
467                later[s2] = max_star(later[s2], now[s] + gamma[(t * ns + s) * 2 + u], jacobian);
468            }
469        }
470    }
471    let beta = &mut ws.beta;
472    beta.clear();
473    beta.resize((steps + 1) * ns, ninf);
474    if terminated {
475        beta[steps * ns] = 0.0;
476    } else {
477        beta[steps * ns..].fill(0.0);
478    }
479    for t in (0..steps).rev() {
480        let (now, later) = beta[t * ns..(t + 2) * ns].split_at_mut(ns);
481        for s in 0..ns {
482            let mut b = ninf;
483            for u in 0..2 {
484                b = max_star(b, gamma[(t * ns + s) * 2 + u] + later[trellis.next[s][u]], jacobian);
485            }
486            now[s] = b;
487        }
488    }
489    app.clear();
490    for t in 0..steps {
491        let mut l = [ninf; 2];
492        for s in 0..ns {
493            let a = alpha[t * ns + s];
494            if a == ninf {
495                continue;
496            }
497            for u in 0..2 {
498                l[u] =
499                    max_star(l[u], a + gamma[(t * ns + s) * 2 + u] + beta[(t + 1) * ns + trellis.next[s][u]], jacobian);
500            }
501        }
502        app.push(l[0] - l[1]);
503    }
504    Ok(())
505}
506
507/// A terminated rate-$1/n$ convolutional code over blocks of `k` information bits,
508/// decoded by soft-decision Viterbi.
509#[derive(Clone, Debug, PartialEq, Eq)]
510pub struct ConvolutionalCode {
511    /// The trellis.
512    trellis: Trellis,
513    /// Information bits per block.
514    k: usize,
515    /// Decode hard decisions (the signs of the LLRs) instead of the LLRs.
516    pub hard_decision: bool,
517}
518
519impl ConvolutionalCode {
520    /// The code of `trellis` terminated on blocks of `k` bits, decoded by soft-decision
521    /// Viterbi.
522    ///
523    /// # Errors
524    /// [`comnum_core::Error::Config`] for $k = 0$.
525    pub fn new(trellis: Trellis, k: usize) -> Result<ConvolutionalCode> {
526        ensure(k >= 1, || "a convolutional code needs at least one information bit per block".into())?;
527        Ok(ConvolutionalCode { trellis, k, hard_decision: false })
528    }
529
530    /// The $(133, 171)_{8}$ code of constraint length 7, rate 1/2, on blocks of `k` bits.
531    ///
532    /// # Errors
533    /// [`comnum_core::Error::Config`] for $k = 0$.
534    pub fn k7(k: usize) -> Result<ConvolutionalCode> {
535        let trellis = Trellis::feedforward(7, &[0o133, 0o171]).unwrap_or_else(|_| unreachable!("a valid code"));
536        ConvolutionalCode::new(trellis, k)
537    }
538
539    /// The trellis.
540    pub fn trellis(&self) -> &Trellis {
541        &self.trellis
542    }
543}
544
545impl BlockCode for ConvolutionalCode {
546    fn k(&self) -> usize {
547        self.k
548    }
549
550    fn n(&self) -> usize {
551        self.trellis.n_out * (self.k + self.trellis.constraint as usize - 1)
552    }
553
554    fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
555        expect_len(info.len(), self.k, "bits")?;
556        Ok(self.trellis.encode(info, true))
557    }
558
559    fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
560        expect_len(llr.len(), self.n(), "LLRs")?;
561        if self.hard_decision {
562            // a zero LLR (a punctured bit) keeps a zero metric
563            let h: Vec<f64> = llr
564                .iter()
565                .map(|l| {
566                    if *l < 0.0 {
567                        -1.0
568                    } else if *l > 0.0 {
569                        1.0
570                    } else {
571                        0.0
572                    }
573                })
574                .collect();
575            viterbi(&self.trellis, &h, true)
576        } else {
577            viterbi(&self.trellis, llr, true)
578        }
579    }
580}
581
582impl SoftDecode for ConvolutionalCode {
583    /// The a posteriori LLRs of the information bits by [`bcjr`] (log-MAP, the tail
584    /// forced to state zero, its inputs without a priori); one iteration.
585    fn app_into(&self, llr: &[f64], apriori: Option<&[f64]>, out: &mut [f64]) -> Result<usize> {
586        expect_len(llr.len(), self.n(), "LLRs")?;
587        expect_len(out.len(), self.k, "a posteriori LLRs")?;
588        let la: Vec<f64> = match apriori {
589            None => Vec::new(),
590            Some(a) => {
591                expect_len(a.len(), self.k, "a priori LLRs")?;
592                a.iter().copied().chain(std::iter::repeat_n(0.0, self.trellis.constraint as usize - 1)).collect()
593            }
594        };
595        let app = bcjr(&self.trellis, llr, &la, true, Jacobian::Exact)?;
596        out.copy_from_slice(&app[..self.k]);
597        Ok(1)
598    }
599}
600
601container_traits!(ConvolutionalCode);
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606    use crate::block::testing::awgn_llr;
607    use comnum_core::rng::Rng;
608    use comnum_core::signal::hard_decisions;
609
610    #[test]
611    fn the_k7_code_has_free_distance_10() {
612        // the weight of the impulse response is the free distance of (133, 171)
613        let t = Trellis::feedforward(7, &[0o133, 0o171]).unwrap();
614        let c = t.encode(&[1, 0, 0, 0, 0, 0, 0], true);
615        assert_eq!(c.iter().map(|b| u32::from(*b)).sum::<u32>(), 10);
616    }
617
618    #[test]
619    fn viterbi_corrects_errors_and_bcjr_agrees() {
620        let code = ConvolutionalCode::k7(200).unwrap();
621        let mut r = Rng::new(1);
622        let info: Vec<u8> = (0..200).map(|_| r.bit()).collect();
623        let c = code.encode_block(&info).unwrap();
624        let y = awgn_llr(&c, 0.6, &mut r);
625        assert!(hard_decisions(&y).iter().zip(&c).filter(|(a, b)| a != b).count() > 5);
626        assert_eq!(code.decode_block(&y).unwrap(), info);
627        let app = bcjr(&code.trellis, &y, &[], true, Jacobian::Exact).unwrap();
628        assert_eq!(hard_decisions(&app[..200]), info);
629        let maxlog = bcjr(&code.trellis, &y, &[], true, Jacobian::MaxLog).unwrap();
630        assert_eq!(hard_decisions(&maxlog[..200]), info);
631    }
632
633    #[test]
634    fn branch_tables_and_reused_workspaces_change_nothing() {
635        let mut r = Rng::new(5);
636        // a table of the 2^n output words (k7, the turbo constituent) and the branches
637        // computed one by one (five outputs, four states)
638        let codes = [
639            Trellis::feedforward(7, &[0o133, 0o171]).unwrap(),
640            Trellis::recursive(4, 0o13, 0o15).unwrap(),
641            Trellis::feedforward(3, &[5, 7, 7, 5, 6]).unwrap(),
642        ];
643        let (mut ws, mut app) = (BcjrWorkspace::default(), Vec::new());
644        for t in &codes {
645            let (ns, n) = (t.n_states(), t.n_out);
646            let l: Vec<f64> = (0..n).map(|_| r.gaussian()).collect();
647            let (mut words, mut m) = (Vec::new(), vec![0.0; 2 * ns]);
648            t.branches(&l, &mut words, &mut m);
649            for s in 0..ns {
650                for u in 0..2 {
651                    assert_eq!(m[2 * s + u].to_bits(), t.branch(t.out[s][u], &l).to_bits());
652                }
653            }
654            for len in [30, 7, 50] {
655                let bits: Vec<u8> = (0..len).map(|_| r.bit()).collect();
656                let c = t.encode(&bits, true);
657                let clean: Vec<f64> = c.iter().map(|b| 4.0 * (1.0 - 2.0 * f64::from(*b))).collect();
658                assert_eq!(viterbi(t, &clean, true).unwrap(), bits);
659                let y: Vec<f64> = clean.iter().map(|v| v + 2.0 * r.gaussian()).collect();
660                let la: Vec<f64> = (0..y.len() / n).map(|_| r.gaussian()).collect();
661                for (terminated, jacobian) in [(true, Jacobian::Exact), (false, Jacobian::MaxLog)] {
662                    bcjr_into(t, &y, &la, terminated, jacobian, &mut ws, &mut app).unwrap();
663                    let fresh = bcjr(t, &y, &la, terminated, jacobian).unwrap();
664                    assert!(
665                        app.iter().zip(&fresh).all(|(a, b)| a.to_bits() == b.to_bits()) && app.len() == fresh.len()
666                    );
667                }
668            }
669        }
670    }
671
672    #[test]
673    fn the_soft_output_is_the_bcjr_of_the_information_bits() {
674        let code = ConvolutionalCode::k7(60).unwrap();
675        let mut r = Rng::new(3);
676        let info: Vec<u8> = (0..60).map(|_| r.bit()).collect();
677        let y = awgn_llr(&code.encode_block(&info).unwrap(), 0.8, &mut r);
678        let la: Vec<f64> = (0..60).map(|_| r.gaussian()).collect();
679        let former = bcjr(&code.trellis, &y, &[], true, Jacobian::Exact).unwrap();
680        let app = code.app(&y, None).unwrap();
681        assert!(app.iter().zip(&former[..60]).all(|(a, b)| a.to_bits() == b.to_bits()));
682        let padded: Vec<f64> = la.iter().copied().chain([0.0; 6]).collect();
683        let former = bcjr(&code.trellis, &y, &padded, true, Jacobian::Exact).unwrap();
684        let app = code.app(&y, Some(&la)).unwrap();
685        assert!(app.iter().zip(&former[..60]).all(|(a, b)| a.to_bits() == b.to_bits()));
686        assert!(code.app(&y[1..], None).is_err() && code.app(&y, Some(&la[1..])).is_err());
687        assert!(ConvolutionalCode::k7(0).is_err());
688    }
689
690    fn tail_biting_ml(t: &Trellis, llr: &[f64]) -> Vec<u8> {
691        // maximum likelihood: the best of the Viterbi paths forced to start and end in s
692        let ns = t.n_states();
693        let steps = llr.len() / t.n_out();
694        let mut best = (f64::NEG_INFINITY, Vec::new());
695        for s0 in 0..ns {
696            let mut metric = vec![f64::NEG_INFINITY; ns];
697            metric[s0] = 0.0;
698            let mut from = vec![0u32; steps * ns];
699            forward(t, llr, &mut metric, &mut from);
700            if metric[s0] > best.0 {
701                best = (metric[s0], traceback(&from, ns, s0).0);
702            }
703        }
704        best.1
705    }
706
707    #[test]
708    fn the_wava_decodes_the_lte_code_as_the_maximum_likelihood_decoder() {
709        let t = Trellis::feedforward(7, &[0o133, 0o171, 0o165]).unwrap();
710        let mut rng = comnum_core::rng::Rng::new(36_212);
711        let (mut agree, mut errors, trials) = (0, 0, 300);
712        for _ in 0..trials {
713            let bits: Vec<u8> = (0..40).map(|_| (rng.uniform() < 0.5) as u8).collect();
714            let c = t.encode_tail_biting(&bits).unwrap();
715            // BPSK at Es/N0 = -2 dB (rate 1/3: Eb/N0 = 2.8 dB): LLR = 4 y Es/N0
716            let es_n0 = 10f64.powf(-0.2);
717            let llr: Vec<f64> = c
718                .iter()
719                .map(|&b| {
720                    let y = 1.0 - 2.0 * f64::from(b) + rng.gaussian() / (2.0 * es_n0).sqrt();
721                    4.0 * es_n0 * y
722                })
723                .collect();
724            let wava = viterbi_tail_biting(&t, &llr, 4).unwrap();
725            agree += usize::from(wava == tail_biting_ml(&t, &llr));
726            errors += usize::from(wava != bits);
727        }
728        assert!(agree >= trials - 3, "{agree} of {trials}");
729        assert!(errors < trials / 10, "{errors} block errors of {trials}");
730    }
731
732    #[test]
733    fn tail_biting_starts_and_ends_in_the_state_of_the_last_inputs() {
734        let t = Trellis::feedforward(7, &[0o133, 0o171, 0o165]).unwrap();
735        let bits = [1u8, 0, 1, 1, 0, 0, 1, 0, 1, 1];
736        let c = t.encode_tail_biting(&bits).unwrap();
737        // the same as encoding from state 0 the last six bits, then all of them
738        let mut warm = bits[4..].to_vec();
739        warm.extend_from_slice(&bits);
740        assert_eq!(c, t.encode(&warm, false)[3 * 6..]);
741        assert!(t.encode_tail_biting(&bits[..5]).is_err());
742        assert!(Trellis::recursive(4, 0o13, 0o15).unwrap().encode_tail_biting(&bits).is_err());
743        assert!(viterbi_tail_biting(&t, &[0.0; 4], 2).is_err());
744        assert!(viterbi_tail_biting(&t, &[0.0; 3], 0).is_err());
745    }
746
747    #[test]
748    fn recursive_codes_terminate_and_are_systematic() {
749        let t = Trellis::recursive(4, 0o13, 0o15).unwrap();
750        let bits = [1, 0, 1, 1, 0, 0, 1, 0];
751        let c = t.encode(&bits, true);
752        assert_eq!(c.len(), 2 * (8 + 3));
753        // systematic: every other bit is the input
754        assert_eq!((0..8).map(|i| c[2 * i]).collect::<Vec<_>>(), bits);
755        // the tail returns to state 0
756        let mut s = 0;
757        for &b in &bits {
758            s = t.next[s][usize::from(b)];
759        }
760        for _ in 0..3 {
761            s = t.next[s][usize::from(t.tail[s])];
762        }
763        assert_eq!(s, 0);
764    }
765}