comnum_fec/interleaving.rs
1//! Interleavers: permutations of the coded bits (or of their LLRs).
2//!
3//! An [`Interleaver`] holds a permutation $\pi$: the interleaved sequence is
4//! $y_{i} = x_{\pi(i)}$. [`Interleaver::block`] writes row by row and reads column by
5//! column; [`Interleaver::random`] draws a uniform permutation from a seed; the
6//! quadratic permutation polynomial of LTE turbo codes is
7//! [`Interleaver::qpp`], $\pi(i) = (f_{1} i + f_{2} i^{2}) \bmod K$ (Sun & Takeshita 2005).
8//!
9//! A [`ConvolutionalInterleaver`] spreads a stream instead of a block (Forney 1971): a
10//! commutator sends symbol $t$ to branch $j = t \bmod I$, a FIFO of $M j$ cells clocked
11//! once every $I$ symbols, so that symbol $t$ leaves $I M j$ symbols late; its
12//! deinterleaver has the depths $M (I - 1 - j)$, and every symbol crosses the pair in
13//! $I M (I - 1)$ symbols. A burst of $b \le I$ consecutive channel errors lands, at the
14//! output of the deinterleaver, in $b$ symbols at least $I M - 1$ apart. DVB-S and DVB-T
15//! interleave the bytes of their Reed–Solomon codewords with $I = 12$, $M = 17$ (ETSI
16//! EN 300 421, Sec. 4.4.2, Figure 4).
17//!
18//! # Examples
19//! ```
20//! use comnum_fec::interleaving::Interleaver;
21//! let p = Interleaver::block(2, 3);
22//! assert_eq!(p.interleave(&[0, 1, 2, 3, 4, 5]), vec![0, 3, 1, 4, 2, 5]);
23//! assert_eq!(p.deinterleave(&p.interleave(&[9, 8, 7, 6, 5, 4])), vec![9, 8, 7, 6, 5, 4]);
24//! ```
25//!
26//! # References
27//!
28//! 1. J. Sun and O. Y. Takeshita, "Interleavers for turbo codes using permutation
29//! polynomials over integer rings," *IEEE Trans. Inf. Theory*, 51(1), 101–119, 2005.
30//! 2. G. D. Forney, "Burst-correcting codes for the classic bursty channel," *IEEE
31//! Trans. Commun. Technol.*, 19(5), 772–781, 1971.
32//! 3. ETSI EN 300 421 V1.1.2, *DVB; Framing structure, channel coding and modulation for
33//! 11/12 GHz satellite services*, 1997, Sec. 4.4.2.
34
35use comnum_core::rng::Rng;
36use comnum_core::{Error, Result, ensure};
37use std::collections::VecDeque;
38
39/// A permutation of the positions of a block.
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct Interleaver {
42 /// $\pi$: output position $i$ takes input position `perm[i]`.
43 perm: Vec<usize>,
44}
45
46impl Interleaver {
47 /// The interleaver of the permutation `perm`: output position $i$ takes input
48 /// position `perm[i]`.
49 ///
50 /// # Errors
51 /// [`comnum_core::Error::Config`] if `perm` is not a permutation of $0, \dots, n - 1$.
52 ///
53 /// # Examples
54 /// ```
55 /// use comnum_fec::interleaving::Interleaver;
56 /// let p = Interleaver::from_permutation(vec![2, 0, 1])?;
57 /// assert_eq!(p.interleave(&['a', 'b', 'c']), ['c', 'a', 'b']);
58 /// assert!(Interleaver::from_permutation(vec![0, 0, 1]).is_err());
59 /// # Ok::<(), comnum_core::Error>(())
60 /// ```
61 pub fn from_permutation(perm: Vec<usize>) -> Result<Interleaver> {
62 ensure_permutation(&perm, || "the interleaver is not a permutation".into())?;
63 Ok(Interleaver { perm })
64 }
65
66 /// $\pi$: output position $i$ takes input position `perm()[i]`.
67 pub fn perm(&self) -> &[usize] {
68 &self.perm
69 }
70
71 /// Write row by row into `rows × cols`, read column by column.
72 pub fn block(rows: usize, cols: usize) -> Interleaver {
73 Interleaver { perm: (0..rows * cols).map(|i| (i % rows) * cols + i / rows).collect() }
74 }
75
76 /// A uniform random permutation of `n` positions (seeded).
77 pub fn random(n: usize, seed: u64) -> Interleaver {
78 let mut perm: Vec<usize> = (0..n).collect();
79 Rng::new(seed).shuffle(&mut perm);
80 Interleaver { perm }
81 }
82
83 /// The quadratic permutation polynomial $\pi(i) = (f_{1} i + f_{2} i^{2}) \bmod K$.
84 ///
85 /// # Errors
86 /// [`comnum_core::Error::Config`] if the polynomial is not a permutation.
87 pub fn qpp(k: usize, f1: usize, f2: usize) -> Result<Interleaver> {
88 let perm: Vec<usize> = (0..k).map(|i| ((f1 * i) % k + (f2 * ((i * i) % k)) % k) % k).collect();
89 ensure_permutation(&perm, || format!("({f1}, {f2}) is not a permutation polynomial modulo {k}"))?;
90 Ok(Interleaver { perm })
91 }
92
93 /// The interleaver of a standard by name, for blocks of `k` bits: `lte`, the QPP
94 /// interleaver of the LTE turbo code ([`crate::turbo::lte_qpp`], TS 36.212,
95 /// Table 5.1.3-3), case-insensitive.
96 ///
97 /// # Errors
98 /// [`comnum_core::Error::Config`] for another name, or a size `k` outside the table.
99 pub fn from_name(name: &str, k: usize) -> Result<Interleaver> {
100 match name.to_ascii_lowercase().as_str() {
101 "lte" => {
102 let (f1, f2) = crate::turbo::lte_qpp(k).ok_or_else(|| {
103 Error::Config(format!("K = {k} is not a block size of LTE (TS 36.212, Table 5.1.3-3)"))
104 })?;
105 Interleaver::qpp(k, f1, f2)
106 }
107 _ => Err(Error::Config(format!("unknown interleaver {name:?}: lte"))),
108 }
109 }
110
111 /// Length of the block.
112 pub fn len(&self) -> usize {
113 self.perm.len()
114 }
115
116 /// Whether the block is empty.
117 pub fn is_empty(&self) -> bool {
118 self.perm.is_empty()
119 }
120
121 /// $y_{i} = x_{\pi(i)}$.
122 ///
123 /// # Panics
124 /// If `x` is shorter than the permutation.
125 pub fn interleave<T: Copy>(&self, x: &[T]) -> Vec<T> {
126 self.perm.iter().map(|&p| x[p]).collect()
127 }
128
129 /// The inverse permutation.
130 ///
131 /// # Panics
132 /// If `y` is shorter than the permutation.
133 pub fn deinterleave<T: Copy + Default>(&self, y: &[T]) -> Vec<T> {
134 let mut x = vec![T::default(); self.perm.len()];
135 for (i, &p) in self.perm.iter().enumerate() {
136 x[p] = y[i];
137 }
138 x
139 }
140}
141
142/// [`comnum_core::Error::Config`] with the message of `msg` unless `perm` holds every
143/// position below its length once.
144fn ensure_permutation(perm: &[usize], msg: impl FnOnce() -> String) -> Result<()> {
145 let mut seen = vec![false; perm.len()];
146 for &p in perm {
147 if p >= perm.len() || seen[p] {
148 return ensure(false, msg);
149 }
150 seen[p] = true;
151 }
152 Ok(())
153}
154
155/// A convolutional (Forney) interleaver or deinterleaver of $I$ branches of unit depth
156/// $M$ (see the module): a stream processed symbol by symbol, its FIFOs holding the
157/// symbols still in flight.
158///
159/// Branch $j$ of the interleaver holds $M j$ cells; branch $j$ of the deinterleaver
160/// holds $M (I - 1 - j)$ cells. Both start filled with `T::default()`; the commutator
161/// starts on branch 0, so that the first symbol of a stream (the sync byte of DVB) takes
162/// the branch without delay. Symbol $t$ leaves the interleaver $I M (t \bmod I)$ symbols
163/// late, and the pair delays every symbol by [`ConvolutionalInterleaver::latency`].
164///
165/// # Examples
166/// ```
167/// use comnum_fec::interleaving::ConvolutionalInterleaver;
168/// // the interleaver of DVB-S and DVB-T: I = 12, M = 17
169/// let mut tx = ConvolutionalInterleaver::new(12, 17)?;
170/// let mut rx = ConvolutionalInterleaver::deinterleaver(12, 17)?;
171/// let data: Vec<u8> = (0..5000).map(|i| (i % 251) as u8).collect();
172/// let y = rx.process(&tx.process(&data));
173/// let lat = tx.latency(); // 12 · 17 · 11 = 2244 symbols
174/// assert_eq!(&y[lat..], &data[..data.len() - lat]);
175/// # Ok::<(), comnum_core::Error>(())
176/// ```
177#[derive(Clone, Debug)]
178pub struct ConvolutionalInterleaver<T> {
179 unit: usize,
180 deinterleave: bool,
181 /// The FIFO of every branch (empty for a branch without delay).
182 fifos: Vec<VecDeque<T>>,
183 /// The branch of the next symbol.
184 branch: usize,
185}
186
187impl<T: Copy + Default> ConvolutionalInterleaver<T> {
188 /// The interleaver of `branches` branches of unit depth `unit` ($I$, $M$).
189 ///
190 /// # Errors
191 /// [`comnum_core::Error::Config`] without a branch.
192 pub fn new(branches: usize, unit: usize) -> Result<Self> {
193 Self::build(branches, unit, false)
194 }
195
196 /// The deinterleaver of [`ConvolutionalInterleaver::new`] with the same parameters.
197 ///
198 /// # Errors
199 /// As [`ConvolutionalInterleaver::new`].
200 pub fn deinterleaver(branches: usize, unit: usize) -> Result<Self> {
201 Self::build(branches, unit, true)
202 }
203
204 fn build(branches: usize, unit: usize, deinterleave: bool) -> Result<Self> {
205 ensure(branches >= 1, || "a convolutional interleaver needs at least one branch".into())?;
206 let depth = |j: usize| unit * if deinterleave { branches - 1 - j } else { j };
207 let fifos = (0..branches).map(|j| std::iter::repeat_n(T::default(), depth(j)).collect()).collect();
208 Ok(ConvolutionalInterleaver { unit, deinterleave, fifos, branch: 0 })
209 }
210
211 /// The number of branches $I$.
212 pub fn branches(&self) -> usize {
213 self.fifos.len()
214 }
215
216 /// The unit depth $M$.
217 pub fn unit_depth(&self) -> usize {
218 self.unit
219 }
220
221 /// Whether this is the deinterleaver.
222 pub fn is_deinterleaver(&self) -> bool {
223 self.deinterleave
224 }
225
226 /// The delay (in symbols) of the symbols of branch `branch`: $I M j$ for the
227 /// interleaver, $I M (I - 1 - j)$ for the deinterleaver.
228 pub fn delay(&self, branch: usize) -> usize {
229 self.branches() * self.fifos[branch % self.branches()].len()
230 }
231
232 /// The delay of the interleaver and deinterleaver together, $I M (I - 1)$ symbols.
233 pub fn latency(&self) -> usize {
234 let i = self.branches();
235 i * self.unit * (i - 1)
236 }
237
238 /// Back to the initial state: the FIFOs filled with `T::default()`, the commutator on
239 /// branch 0.
240 pub fn reset(&mut self) {
241 for f in &mut self.fifos {
242 for v in f.iter_mut() {
243 *v = T::default();
244 }
245 }
246 self.branch = 0;
247 }
248
249 /// One symbol in, one symbol out.
250 pub fn push(&mut self, x: T) -> T {
251 let n = self.fifos.len();
252 let fifo = &mut self.fifos[self.branch];
253 self.branch = (self.branch + 1) % n;
254 match fifo.pop_front() {
255 Some(y) => {
256 fifo.push_back(x);
257 y
258 }
259 None => x,
260 }
261 }
262
263 /// The symbols of `x` through the interleaver, into `out` (as many).
264 ///
265 /// # Errors
266 /// [`comnum_core::Error::Shape`] if `out` does not have the length of `x`.
267 pub fn process_into(&mut self, x: &[T], out: &mut [T]) -> Result<()> {
268 comnum_core::same_len(out.len(), x.len(), "interleaved symbols")?;
269 for (o, &v) in out.iter_mut().zip(x) {
270 *o = self.push(v);
271 }
272 Ok(())
273 }
274
275 /// The symbols of `x` through the interleaver.
276 pub fn process(&mut self, x: &[T]) -> Vec<T> {
277 x.iter().map(|&v| self.push(v)).collect()
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn the_lte_interleaver_is_read_by_name() {
287 assert_eq!(Interleaver::from_name("LTE", 40).unwrap(), Interleaver::qpp(40, 3, 10).unwrap());
288 assert!(Interleaver::from_name("lte", 41).is_err());
289 assert!(Interleaver::from_name("umts", 40).is_err());
290 }
291
292 #[test]
293 fn permutations_invert() {
294 let x: Vec<u32> = (0..40).collect();
295 for p in [Interleaver::random(40, 3), Interleaver::block(5, 8), Interleaver::qpp(40, 3, 10).unwrap()] {
296 let mut sorted = p.perm.clone();
297 sorted.sort_unstable();
298 assert_eq!(sorted, x.iter().map(|v| *v as usize).collect::<Vec<_>>());
299 assert_eq!(p.deinterleave(&p.interleave(&x)), x);
300 }
301 // the QPP of LTE for K = 40 is (3, 10); (2, 10) is not a permutation
302 assert!(Interleaver::qpp(40, 2, 10).is_err());
303 }
304 #[test]
305 fn a_convolutional_interleaver_delays_branch_j_by_i_m_j_symbols() {
306 // EN 300 421 Sec. 4.4.2: branch j holds M j cells, clocked once every I symbols
307 let mut tx = ConvolutionalInterleaver::<u32>::new(12, 17).unwrap();
308 let x: Vec<u32> = (1..=20_000).collect();
309 let y = tx.process(&x);
310 for t in 0..x.len() {
311 let d = 12 * 17 * (t % 12);
312 if t + d < y.len() {
313 assert_eq!(y[t + d], x[t], "symbol {t} of branch {}", t % 12);
314 }
315 }
316 assert_eq!((tx.delay(0), tx.delay(11)), (0, 12 * 17 * 11));
317 }
318
319 #[test]
320 fn the_deinterleaver_undoes_the_interleaver_after_its_latency() {
321 for (i, m) in [(12, 17), (5, 3), (1, 4)] {
322 let mut tx = ConvolutionalInterleaver::new(i, m).unwrap();
323 let mut rx = ConvolutionalInterleaver::deinterleaver(i, m).unwrap();
324 let x: Vec<u16> = (0..3000).map(|v| (v * 7 + 1) as u16).collect();
325 let y = rx.process(&tx.process(&x));
326 let lat = tx.latency();
327 assert_eq!(lat, i * m * (i - 1));
328 assert_eq!(&y[lat..], &x[..x.len() - lat], "I = {i}, M = {m}");
329 assert!(y[..lat].iter().all(|&v| v == 0), "the initial content");
330 }
331 assert!(ConvolutionalInterleaver::<u8>::new(0, 3).is_err());
332 }
333
334 #[test]
335 fn a_burst_is_spread_over_symbols_i_m_apart() {
336 // I consecutive channel errors land in I symbols of the stream, I M - 1 apart at least
337 let (i, m) = (12, 17);
338 let mut tx = ConvolutionalInterleaver::new(i, m).unwrap();
339 let mut rx = ConvolutionalInterleaver::deinterleaver(i, m).unwrap();
340 let mut y = tx.process(&vec![0u8; 20_000]);
341 y[9_000..9_000 + i].iter_mut().for_each(|v| *v = 1);
342 let out = rx.process(&y);
343 let hit: Vec<usize> = (0..out.len()).filter(|&t| out[t] == 1).collect();
344 assert_eq!(hit.len(), i);
345 let gaps: Vec<usize> = hit.windows(2).map(|w| w[1] - w[0]).collect();
346 assert!(gaps.iter().all(|&g| g >= i * m - 1), "{gaps:?}");
347 }
348
349 #[test]
350 fn reset_restores_the_initial_state() {
351 let mut tx = ConvolutionalInterleaver::new(3, 2).unwrap();
352 let a = tx.process(&[1u8, 2, 3, 4, 5, 6, 7, 8, 9]);
353 tx.reset();
354 assert_eq!(tx.process(&[1u8, 2, 3, 4, 5, 6, 7, 8, 9]), a);
355 }
356}