Skip to main content

comnum_fec/
polar.rs

1//! Polar codes.
2//!
3//! Arıkan's transform $x = u\, F^{\otimes m}$, $F = \begin{pmatrix} 1 & 0 \\\\ 1 & 1
4//! \end{pmatrix}$, $N = 2^{m}$, turns $N$ uses of a channel into $N$ synthetic channels that
5//! polarise: some become almost noiseless, the others almost useless. The $K$ most
6//! reliable positions carry information (with the CRC bits, for CRC-aided list
7//! decoding), the others are frozen to zero.
8//!
9//! # Construction
10//!
11//! The reliabilities come from the Gaussian approximation of density evolution
12//! (Trifonov 2012) at a design $E_{b}/N_{0}$: the mean LLR $\mu$ of a synthetic channel
13//! becomes $\phi^{-1}\left(1 - (1 - \phi(\mu))^{2}\right)$ for the worse branch and $2 \mu$
14//! for the better one, with the function $\phi$ of Chung, Richardson & Urbanke (2001).
15//!
16//! # Decoding
17//!
18//! - successive cancellation (SC, Arıkan 2009), with the min-sum $f$ and the $g$ updates
19//!   $f(a, b) = \operatorname{sgn}(a)\operatorname{sgn}(b)\min(|a|, |b|)$,
20//!   $g(a, b, \hat u) = b + (1 - 2\hat u) a$;
21//! - SC list (Tal & Vardy 2015) of size $L$ with the LLR-based path metric
22//!   $\mathrm{PM} \mathrel{+}= \ln\left(1 + e^{-(1 - 2\hat u)\lambda}\right)$
23//!   (Balatsoukas-Stimming, Parizi & Burg 2015), and the CRC choosing among the
24//!   surviving paths (CA-SCL, the decoder of 5G NR control channels). The CRC bits are
25//!   not coding gain: on short blocks a short CRC (6 bits) pays off, a long one costs
26//!   more rate than it brings.
27//!
28//! Both run on the per-layer LLR and partial-sum arrays of Tal & Vardy (2015, Sec. IV),
29//! shared between the paths and copied on write, in $O(L n \log n)$ per frame.
30//!
31//! # Examples
32//! ```
33//! use comnum_fec::block::BlockCode;
34//! use comnum_fec::crc::Crc;
35//! use comnum_fec::polar::PolarCode;
36//! let code = PolarCode::new(128, 64, 2.0)?.with_crc(Crc::crc6())?.with_list(8)?;
37//! let info: Vec<u8> = (0..64).map(|i| (i % 3 == 0) as u8).collect();
38//! let c = code.encode_block(&info)?;
39//! let llr: Vec<f64> = c.iter().map(|&b| if b == 0 { 4.0 } else { -4.0 }).collect(); // noiseless
40//! assert_eq!(code.decode_block(&llr)?, info);
41//! # Ok::<(), comnum_core::Error>(())
42//! ```
43//!
44//! # References
45//!
46//! 1. E. Arıkan, "Channel polarization: a method for constructing capacity-achieving
47//!    codes for symmetric binary-input memoryless channels," *IEEE Trans. Inf. Theory*,
48//!    55(7), 3051–3073, 2009.
49//! 2. P. Trifonov, "Efficient design and decoding of polar codes," *IEEE Trans.
50//!    Commun.*, 60(11), 3221–3227, 2012.
51//! 3. S.-Y. Chung, T. J. Richardson and R. L. Urbanke, "Analysis of sum-product decoding
52//!    of low-density parity-check codes using a Gaussian approximation," *IEEE Trans.
53//!    Inf. Theory*, 47(2), 657–670, 2001.
54//! 4. I. Tal and A. Vardy, "List decoding of polar codes," *IEEE Trans. Inf. Theory*,
55//!    61(5), 2213–2226, 2015.
56//! 5. A. Balatsoukas-Stimming, M. B. Parizi and A. Burg, "LLR-based successive
57//!    cancellation list decoding of polar codes," *IEEE Trans. Signal Process.*, 63(19),
58//!    5165–5179, 2015.
59
60use crate::block::{BlockCode, container_traits, expect_len};
61use crate::crc::Crc;
62use comnum_core::{Result, ensure};
63use comnum_math::{db2lin, ln_1p_exp};
64
65/// The function $\phi$ of the Gaussian approximation (Chung, Richardson & Urbanke 2001).
66fn phi(x: f64) -> f64 {
67    if x <= 0.0 {
68        1.0
69    } else if x < 10.0 {
70        (-0.4527 * x.powf(0.86) + 0.0218).exp()
71    } else {
72        (std::f64::consts::PI / x).sqrt() * (-x / 4.0).exp() * (1.0 - 10.0 / (7.0 * x))
73    }
74}
75
76/// $\phi^{-1}$ by bisection ($\phi$ decreases from 1 to 0).
77fn phi_inv(y: f64) -> f64 {
78    if y >= 1.0 {
79        return 0.0;
80    }
81    let (lo, mut hi) = (0.0f64, 1.0f64);
82    while phi(hi) > y {
83        hi *= 2.0;
84        if hi > 1e6 {
85            return hi;
86        }
87    }
88    comnum_math::bisect(|x| phi(x) > y, lo, hi, 100)
89}
90
91/// The mean LLR of every synthetic channel of a transform of size `n` (a power of two)
92/// whose channels have the mean LLR `mu`.
93///
94/// The outer stage of the transform acts first on the channel LLRs: the most
95/// significant bit of a channel index chooses its branch (the check node
96/// $\phi^{-1}(1 - (1 - \phi(\mu))^{2})$ for 0, the variable node $2\mu$ for 1), the
97/// lower bits the next stages. After $s$ stages the $2^{s}$ values of the prefixes are
98/// distinct, so each is computed once: $n - 1$ inversions of $\phi$ in all, instead of
99/// one per channel and stage.
100fn reliabilities(n: usize, mu: f64) -> Vec<f64> {
101    let mut v = vec![mu];
102    while v.len() < n {
103        // prefix p (the high bits of the index) becomes 2p (bit 0) and 2p + 1 (bit 1)
104        v = v.iter().flat_map(|&m| [phi_inv(1.0 - (1.0 - phi(m)).powi(2)), 2.0 * m]).collect();
105    }
106    v
107}
108
109/// The transform $x = u F^{\otimes m}$ in place.
110fn transform(u: &mut [u8]) {
111    let n = u.len();
112    let mut s = 1;
113    while s < n {
114        for i in 0..n {
115            if i & s == 0 {
116                u[i] ^= u[i + s];
117            }
118        }
119        s <<= 1;
120    }
121}
122
123/// The min-sum $f(a, b) = \operatorname{sgn}(a)\operatorname{sgn}(b)\min(|a|, |b|)$.
124#[inline]
125fn f(a: f64, b: f64) -> f64 {
126    a.signum() * b.signum() * a.abs().min(b.abs())
127}
128
129/// The update $g(a, b, \hat u) = b + (1 - 2\hat u) a$.
130#[inline]
131fn g(a: f64, b: f64, u: u8) -> f64 {
132    b + if u == 0 { a } else { -a }
133}
134
135/// The arrays of one layer of the list decoder, shared between the paths with
136/// reference counts and copied on write (the lazy copy of Tal & Vardy 2015, Sec. IV).
137struct Pool<T> {
138    /// The length of an array.
139    len: usize,
140    /// The arrays, one after the other.
141    data: Vec<T>,
142    /// `of[path]`: the array of a path.
143    of: Vec<usize>,
144    /// `refs[array]`: the number of paths that use an array.
145    refs: Vec<usize>,
146    /// The arrays no path uses.
147    free: Vec<usize>,
148}
149
150impl<T: Copy + Default> Pool<T> {
151    fn new(len: usize, arrays: usize, paths: usize) -> Pool<T> {
152        Pool {
153            len,
154            data: vec![T::default(); len * arrays],
155            of: vec![0; paths],
156            refs: vec![0; arrays],
157            free: Vec::new(),
158        }
159    }
160
161    /// Path 0 alone, on array 0.
162    fn reset(&mut self) {
163        self.refs.fill(0);
164        self.refs[0] = 1;
165        self.of[0] = 0;
166        self.free.clear();
167        self.free.extend((1..self.refs.len()).rev());
168    }
169
170    /// Path `to` shares the array of path `from`.
171    fn share(&mut self, from: usize, to: usize) {
172        let a = self.of[from];
173        self.of[to] = a;
174        self.refs[a] += 1;
175    }
176
177    /// Path `path` stops using its array.
178    fn release(&mut self, path: usize) {
179        let a = self.of[path];
180        self.refs[a] -= 1;
181        if self.refs[a] == 0 {
182            self.free.push(a);
183        }
184    }
185
186    /// The array of `path`, made its own before it writes into it: a shared array is
187    /// replaced by a free one that receives the first `keep` values of the shared one.
188    fn writable(&mut self, path: usize, keep: usize) -> usize {
189        let a = self.of[path];
190        if self.refs[a] == 1 {
191            return a;
192        }
193        self.refs[a] -= 1;
194        let b = self.free.pop().unwrap_or_else(|| unreachable!("at most one array per path"));
195        self.refs[b] = 1;
196        self.of[path] = b;
197        self.data.copy_within(a * self.len..a * self.len + keep, b * self.len);
198        b
199    }
200
201    fn array(&self, path: usize) -> &[T] {
202        let a = self.of[path];
203        &self.data[a * self.len..(a + 1) * self.len]
204    }
205
206    fn array_mut(&mut self, a: usize) -> &mut [T] {
207        &mut self.data[a * self.len..(a + 1) * self.len]
208    }
209}
210
211/// The SC list decoder on the arrays of Tal & Vardy (2015, Sec. IV), in the LLR form of
212/// Balatsoukas-Stimming, Bastani Parizi & Burg (2015): layer $\lambda = 0, \dots, m$ of
213/// the decoding tree holds, for every path, the LLRs of its current node
214/// ($2^{m - \lambda}$ values, the channel at $\lambda = 0$) and the partial sums of the
215/// codeword under construction below it. Leaf $\varphi$ recomputes the layers from the one where
216/// its branch turns right ($m$ minus the trailing zeros of $\varphi$) down, which is
217/// $O(n \log n)$ per path and frame instead of $O(n^{2} \log n)$ for a leaf computed from
218/// the channel; the paths share the arrays they have not written since they split.
219struct ListDecoder {
220    m: usize,
221    list: usize,
222    /// `llr[λ]`, λ = 0 (the channel, a single array) to m (the leaf LLR).
223    llr: Vec<Pool<f64>>,
224    /// `bits[λ]`, λ = 0 to m − 1: the codeword of the node of the layer, its left half
225    /// written when its left child is decided, its right half then.
226    bits: Vec<Pool<u8>>,
227    /// The slots of the active paths, by rank: the order of the sorted candidates,
228    /// which breaks the ties of the metrics.
229    order: Vec<usize>,
230    /// The unused path slots.
231    free: Vec<usize>,
232    /// Path metric, per slot.
233    pm: Vec<f64>,
234    /// The parity-check register of every slot after the decisions of its path.
235    reg: Vec<[u8; 5]>,
236    /// The candidates of a leaf: metric, rank of the parent path, bit.
237    cand: Vec<(f64, usize, u8)>,
238    /// The leaf LLR of every path, by rank.
239    lam: Vec<f64>,
240    /// Whether a parent rank already gave its slot to a child.
241    claimed: Vec<bool>,
242    /// The slots of the next paths, by rank.
243    next: Vec<usize>,
244    /// The decisions: (rank of the parent, bit), `start[φ]..start[φ + 1]` for leaf φ.
245    hist: Vec<(u32, u8)>,
246    start: Vec<usize>,
247}
248
249impl ListDecoder {
250    fn new(n: usize, list: usize) -> ListDecoder {
251        let m = n.trailing_zeros() as usize;
252        let llr = (0..=m).map(|l| Pool::new(n >> l, if l == 0 { 1 } else { list }, list)).collect();
253        let bits = (0..m).map(|l| Pool::new(n >> l, list, list)).collect();
254        ListDecoder {
255            m,
256            list,
257            llr,
258            bits,
259            order: Vec::with_capacity(list),
260            free: Vec::with_capacity(list),
261            pm: vec![0.0; list],
262            reg: vec![[0; 5]; list],
263            cand: Vec::with_capacity(2 * list),
264            lam: Vec::with_capacity(list),
265            claimed: Vec::with_capacity(list),
266            next: Vec::with_capacity(list),
267            hist: Vec::with_capacity(n * list),
268            start: Vec::with_capacity(n + 1),
269        }
270    }
271
272    /// The LLR of leaf `phi` for the path in slot `path`.
273    fn leaf_llr(&mut self, path: usize, phi: usize) -> f64 {
274        let m = self.m;
275        let first = if phi == 0 { 1 } else { m - phi.trailing_zeros() as usize };
276        for layer in first..=m {
277            let half = 1usize << (m - layer);
278            let (above, below) = self.llr.split_at_mut(layer);
279            let (a, b) = above[layer - 1].array(path).split_at(half);
280            let pool = &mut below[0];
281            let dst = pool.writable(path, 0);
282            let child = pool.array_mut(dst);
283            if (phi >> (m - layer)) & 1 == 0 {
284                for ((c, x), y) in child.iter_mut().zip(a).zip(b) {
285                    *c = f(*x, *y);
286                }
287            } else {
288                let partial = &self.bits[layer - 1].array(path)[..half];
289                for (((c, x), y), &s) in child.iter_mut().zip(a).zip(b).zip(partial) {
290                    *c = g(*x, *y, s);
291                }
292            }
293        }
294        self.llr[m].array(path)[0]
295    }
296
297    /// Record the decision `bit` of leaf `phi` in the partial sums of slot `path`.
298    fn set_bit(&mut self, path: usize, phi: usize, bit: u8) {
299        let m = self.m;
300        let mut side = phi & 1;
301        let pool = &mut self.bits[m - 1];
302        let a = pool.writable(path, side);
303        pool.array_mut(a)[side] = bit;
304        // a completed right child completes its parent: x = (x_left ⊕ x_right, x_right)
305        let (mut layer, mut len) = (m - 1, 1);
306        while side == 1 && layer > 0 {
307            let (above, below) = self.bits.split_at_mut(layer);
308            let pool = &mut below[0];
309            let a = pool.of[path];
310            let node = pool.array_mut(a);
311            let (l, r) = node.split_at_mut(len);
312            for (x, y) in l.iter_mut().zip(r.iter()) {
313                *x ^= y;
314            }
315            side = (phi >> (m - layer)) & 1;
316            let parent = &mut above[layer - 1];
317            let b = parent.writable(path, side * 2 * len);
318            parent.array_mut(b)[side * 2 * len..(side + 1) * 2 * len].copy_from_slice(node);
319            layer -= 1;
320            len *= 2;
321        }
322    }
323
324    /// The decisions $u$ and the metrics of the surviving paths, the most likely first.
325    fn decode(&mut self, llr: &[f64], info: &[bool], pc: &[bool]) -> Vec<(Vec<u8>, f64)> {
326        let n = llr.len();
327        self.reset(llr);
328        for phi in 0..n {
329            self.candidates(phi, info, pc);
330            self.release_unclaimed();
331            self.assign_slots();
332            self.commit(phi, n, info[phi] && !pc[phi]);
333        }
334        self.start.push(self.hist.len());
335        self.trace_back(n)
336    }
337
338    /// One path, slot 0, on the channel LLRs `llr`.
339    fn reset(&mut self, llr: &[f64]) {
340        for p in &mut self.llr {
341            p.reset();
342        }
343        for p in &mut self.bits {
344            p.reset();
345        }
346        self.llr[0].data.copy_from_slice(llr);
347        self.order.clear();
348        self.order.push(0);
349        self.free.clear();
350        self.free.extend((1..self.list).rev());
351        self.pm[0] = 0.0;
352        self.reg[0] = [0; 5];
353        self.hist.clear();
354        self.start.clear();
355    }
356
357    /// The `list` best children (metric, rank of the parent, bit) of the paths at leaf
358    /// `phi`: the frozen bit, the parity-check bit of the register, or both values of
359    /// an information bit.
360    fn candidates(&mut self, phi: usize, info: &[bool], pc: &[bool]) {
361        self.lam.clear();
362        for r in 0..self.order.len() {
363            let lam = self.leaf_llr(self.order[r], phi);
364            self.lam.push(lam);
365        }
366        self.cand.clear();
367        for (r, (&slot, &lam)) in self.order.iter().zip(&self.lam).enumerate() {
368            let pm = self.pm[slot];
369            let cost = |bit: u8| {
370                let s = if bit == 0 { lam } else { -lam };
371                pm + ln_1p_exp(-s)
372            };
373            if pc[phi] {
374                // the register before u_φ, rotated once more: its second cell
375                let bit = self.reg[slot][1];
376                self.cand.push((cost(bit), r, bit));
377            } else if info[phi] {
378                self.cand.push((cost(0), r, 0));
379                self.cand.push((cost(1), r, 1));
380            } else {
381                self.cand.push((cost(0), r, 0));
382            }
383        }
384        // a stable sort by metric: the ties keep the order (rank, bit)
385        self.cand.sort_unstable_by(|x, y| x.0.total_cmp(&y.0).then((x.1, x.2).cmp(&(y.1, y.2))));
386        self.cand.truncate(self.list);
387    }
388
389    /// The paths without a surviving child free their slot and arrays.
390    fn release_unclaimed(&mut self) {
391        self.claimed.clear();
392        self.claimed.resize(self.order.len(), false);
393        for c in &self.cand {
394            self.claimed[c.1] = true;
395        }
396        for r in 0..self.order.len() {
397            if !self.claimed[r] {
398                let slot = self.order[r];
399                for p in &mut self.llr {
400                    p.release(slot);
401                }
402                for p in &mut self.bits {
403                    p.release(slot);
404                }
405                self.free.push(slot);
406            }
407        }
408    }
409
410    /// The slot of every candidate: the first child of a parent keeps its slot, a
411    /// second one takes a free slot that shares the arrays of the parent.
412    fn assign_slots(&mut self) {
413        self.claimed.fill(false);
414        self.next.clear();
415        for k in 0..self.cand.len() {
416            let r = self.cand[k].1;
417            let parent = self.order[r];
418            let slot = if self.claimed[r] {
419                let slot = self.free.pop().unwrap_or_else(|| unreachable!("at most L paths"));
420                for p in &mut self.llr {
421                    p.share(parent, slot);
422                }
423                for p in &mut self.bits {
424                    p.share(parent, slot);
425                }
426                self.reg[slot] = self.reg[parent];
427                slot
428            } else {
429                self.claimed[r] = true;
430                parent
431            };
432            self.next.push(slot);
433        }
434    }
435
436    /// The metrics, registers, history and partial sums of the candidates at leaf
437    /// `phi` of `n`; `data` when the leaf carries an information bit (not a parity
438    /// check), which enters the register.
439    fn commit(&mut self, phi: usize, n: usize, data: bool) {
440        self.start.push(self.hist.len());
441        for k in 0..self.cand.len() {
442            let (metric, r, bit) = self.cand[k];
443            let slot = self.next[k];
444            self.pm[slot] = metric;
445            let reg = &mut self.reg[slot];
446            reg.rotate_left(1);
447            if data {
448                reg[0] ^= bit;
449            }
450            self.hist.push((r as u32, bit));
451            if phi + 1 < n {
452                self.set_bit(slot, phi, bit);
453            }
454        }
455        std::mem::swap(&mut self.order, &mut self.next);
456    }
457
458    /// The decisions of every surviving path, from the history of its ancestors.
459    fn trace_back(&self, n: usize) -> Vec<(Vec<u8>, f64)> {
460        (0..self.order.len())
461            .map(|rank| {
462                let mut u = vec![0u8; n];
463                let mut r = rank;
464                for phi in (0..n).rev() {
465                    let (parent, bit) = self.hist[self.start[phi] + r];
466                    u[phi] = bit;
467                    r = parent as usize;
468                }
469                (u, self.pm[self.order[rank]])
470            })
471            .collect()
472    }
473}
474
475/// A polar code of length $N$ with $K$ information bits.
476#[derive(Clone, Debug, PartialEq)]
477pub struct PolarCode {
478    n: usize,
479    k: usize,
480    /// The positions that carry information (and CRC) bits, in increasing order.
481    info_positions: Vec<usize>,
482    /// The design $E_{b}/N_{0}$ of the construction (dB); `None` for the positions of a
483    /// table ([`PolarCode::with_positions`]).
484    design_ebn0_db: Option<f64>,
485    /// The CRC appended to the information bits (CA-SCL).
486    crc: Option<Crc>,
487    /// List size $L$ (1: successive cancellation).
488    list: usize,
489    /// The parity-check positions of a PC-polar code (see [`PolarCode::with_positions`]).
490    pc_positions: Vec<usize>,
491}
492
493/// The parity-check bits of PC-polar codes (TS 38.212, Sec. 5.3.1.2): a cyclic register
494/// of length 5 runs over $u_{0}, u_{1}, \dots$; a parity-check position takes its first
495/// cell, an information position adds its bit to it.
496fn pc_register(u: &[u8], info: &[bool], pc: &[bool], upto: usize) -> u8 {
497    let mut y = [0u8; 5];
498    for i in 0..upto {
499        y.rotate_left(1);
500        if info[i] && !pc[i] {
501            y[0] ^= u[i];
502        }
503    }
504    y.rotate_left(1);
505    y[0]
506}
507
508impl PolarCode {
509    /// The code of length `n` (a power of two) with `k` information bits, built by the
510    /// Gaussian approximation at the design $E_{b}/N_{0}$ `design_ebn0_db`.
511    ///
512    /// # Errors
513    /// [`comnum_core::Error::Config`] unless $N$ is a power of two, $0 < K \le N$ and the
514    /// design $E_{b}/N_{0}$ is finite.
515    pub fn new(n: usize, k: usize, design_ebn0_db: f64) -> Result<PolarCode> {
516        ensure(n.is_power_of_two() && n >= 2, || format!("the length must be a power of two, got {n}"))?;
517        ensure(k >= 1 && k <= n, || format!("0 < K ≤ N, got K = {k}"))?;
518        ensure(design_ebn0_db.is_finite(), || format!("the design Eb/N0 must be finite, got {design_ebn0_db}"))?;
519        let mut code = PolarCode {
520            n,
521            k,
522            info_positions: Vec::new(),
523            design_ebn0_db: Some(design_ebn0_db),
524            crc: None,
525            list: 1,
526            pc_positions: Vec::new(),
527        };
528        code.construct(design_ebn0_db)?;
529        Ok(code)
530    }
531
532    fn construct(&mut self, design_ebn0_db: f64) -> Result<()> {
533        let extra = self.crc.as_ref().map_or(0, |c| c.width() as usize);
534        let a = self.k + extra;
535        ensure(a <= self.n, || format!("{} information and CRC bits exceed N = {}", a, self.n))?;
536        // BPSK: mean channel LLR 2/σ² = 4 R Eb/N0 with the rate of the information bits
537        let rate = self.k as f64 / self.n as f64;
538        let mu = 4.0 * rate * db2lin(design_ebn0_db);
539        let mut order: Vec<(f64, usize)> = reliabilities(self.n, mu).into_iter().zip(0..self.n).collect();
540        order.sort_by(|x, y| y.0.total_cmp(&x.0).then(y.1.cmp(&x.1)));
541        let mut pos: Vec<usize> = order.iter().take(a).map(|o| o.1).collect();
542        pos.sort_unstable();
543        self.info_positions = pos;
544        Ok(())
545    }
546
547    /// The same code with a CRC appended to the information bits (the information set
548    /// grows by its degree, $K$ is kept; CA-SCL keeps the path that passes it). A second
549    /// call replaces the CRC of the first.
550    ///
551    /// # Errors
552    /// [`comnum_core::Error::Config`] if the information bits and the CRC exceed the block,
553    /// or for a code built by [`PolarCode::with_positions`], whose information set is
554    /// fixed by its table (a standard with a CRC checks it outside the code, as
555    /// [`crate::nr::NrPolar`] with [`PolarCode::decode_list`]).
556    pub fn with_crc(mut self, crc: Crc) -> Result<PolarCode> {
557        let Some(design_ebn0_db) = self.design_ebn0_db else {
558            return Err(comnum_core::Error::Config(
559                "with_crc: the information positions of a code built by with_positions are fixed; \
560                 check the CRC outside the code (decode_list)"
561                    .into(),
562            ));
563        };
564        self.crc = Some(crc);
565        self.construct(design_ebn0_db)?;
566        Ok(self)
567    }
568
569    /// The same code decoded by SC list with `list` paths (1: successive
570    /// cancellation).
571    ///
572    /// # Errors
573    /// [`comnum_core::Error::Config`] for `list = 0`.
574    pub fn with_list(mut self, list: usize) -> Result<PolarCode> {
575        ensure(list >= 1, || "the list decoder needs at least one path".into())?;
576        self.list = list;
577        Ok(self)
578    }
579
580    /// The CRC appended to the information bits (CA-SCL), if any ([`PolarCode::with_crc`]).
581    pub fn crc(&self) -> Option<&Crc> {
582        self.crc.as_ref()
583    }
584
585    /// The list size $L$ of the decoder (1: successive cancellation).
586    pub fn list(&self) -> usize {
587        self.list
588    }
589
590    /// The code of length `n` (a power of two) whose information (and CRC) bits sit at
591    /// `info_positions` and parity-check bits at `pc_positions`, all other positions
592    /// frozen to zero: the codes of a standard whose construction is a table (5G NR,
593    /// [`crate::nr::NrPolar`]). A parity-check bit is the first cell of a cyclic
594    /// register of length 5 that every information bit before it updates (TS 38.212,
595    /// Sec. 5.3.1.2); the list decoder sets it from the decisions of its path.
596    ///
597    /// # Errors
598    /// [`comnum_core::Error::Config`] unless $N$ is a power of two and the positions are
599    /// distinct and below $N$.
600    pub fn with_positions(n: usize, info_positions: &[usize], pc_positions: &[usize]) -> Result<PolarCode> {
601        ensure(n.is_power_of_two() && n >= 2, || format!("the length must be a power of two, got {n}"))?;
602        let mut seen = vec![false; n];
603        for &p in info_positions.iter().chain(pc_positions) {
604            ensure(p < n && !seen[p], || format!("position {p} repeated or beyond N = {n}"))?;
605            seen[p] = true;
606        }
607        let mut info = info_positions.to_vec();
608        info.sort_unstable();
609        let mut pc = pc_positions.to_vec();
610        pc.sort_unstable();
611        ensure(!info.is_empty(), || "the code has no information positions".to_string())?;
612        Ok(PolarCode {
613            n,
614            k: info.len(),
615            info_positions: info,
616            design_ebn0_db: None,
617            crc: None,
618            list: 1,
619            pc_positions: pc,
620        })
621    }
622
623    /// The positions carrying information and CRC bits.
624    pub fn info_positions(&self) -> &[usize] {
625        &self.info_positions
626    }
627
628    /// The parity-check positions (empty but for [`PolarCode::with_positions`]).
629    pub fn pc_positions(&self) -> &[usize] {
630        &self.pc_positions
631    }
632
633    /// The information (and CRC) bits of the surviving paths of the list decoder, the
634    /// most likely first, for a CRC checked outside the code (after a deinterleaver).
635    ///
636    /// # Errors
637    /// [`comnum_core::Error::Shape`] if `llr` does not have $N$ values.
638    pub fn decode_list(&self, llr: &[f64]) -> Result<Vec<Vec<u8>>> {
639        expect_len(llr.len(), self.n, "LLRs")?;
640        Ok(self.decode_paths(llr).iter().map(|(u, _)| self.info_positions.iter().map(|&p| u[p]).collect()).collect())
641    }
642
643    fn masks(&self) -> (Vec<bool>, Vec<bool>) {
644        let mut info = vec![false; self.n];
645        let mut pc = vec![false; self.n];
646        for &p in &self.info_positions {
647            info[p] = true;
648        }
649        for &p in &self.pc_positions {
650            info[p] = true;
651            pc[p] = true;
652        }
653        (info, pc)
654    }
655
656    /// The decisions $u$ and the metrics of the surviving paths of the list decoder, the
657    /// most likely first.
658    fn decode_paths(&self, llr: &[f64]) -> Vec<(Vec<u8>, f64)> {
659        let (info, pc) = self.masks();
660        // the workspace (about L n values) costs less than the noise of the timing to
661        // allocate per frame; `BlockCode` takes `&self` and cannot keep it
662        ListDecoder::new(self.n, self.list).decode(llr, &info, &pc)
663    }
664}
665
666impl BlockCode for PolarCode {
667    fn k(&self) -> usize {
668        self.k
669    }
670
671    fn n(&self) -> usize {
672        self.n
673    }
674
675    fn encode_block(&self, info: &[u8]) -> Result<Vec<u8>> {
676        expect_len(info.len(), self.k, "bits")?;
677        let payload = match &self.crc {
678            Some(c) => c.attach(info),
679            None => info.to_vec(),
680        };
681        let mut u = vec![0u8; self.n];
682        for (&p, &b) in self.info_positions.iter().zip(&payload) {
683            u[p] = b & 1;
684        }
685        if !self.pc_positions.is_empty() {
686            let (info, pc) = self.masks();
687            for &p in &self.pc_positions {
688                u[p] = pc_register(&u, &info, &pc, p);
689            }
690        }
691        transform(&mut u);
692        Ok(u)
693    }
694
695    fn decode_block(&self, llr: &[f64]) -> Result<Vec<u8>> {
696        expect_len(llr.len(), self.n, "LLRs")?;
697        let paths = self.decode_paths(llr);
698        let payload = |u: &[u8]| -> Vec<u8> { self.info_positions.iter().map(|&p| u[p]).collect() };
699        let chosen = match &self.crc {
700            Some(c) => {
701                paths.iter().map(|(u, _)| payload(u)).find(|w| c.check(w)).unwrap_or_else(|| payload(&paths[0].0))
702            }
703            None => payload(&paths[0].0),
704        };
705        Ok(chosen[..self.k].to_vec())
706    }
707}
708
709container_traits!(PolarCode);
710
711#[cfg(test)]
712mod tests {
713    use super::*;
714    use crate::block::testing::awgn_llr;
715    use comnum_core::rng::Rng;
716
717    #[test]
718    fn the_transform_is_an_involution_and_sc_decodes_noiseless_words() {
719        let mut r = Rng::new(1);
720        let mut u: Vec<u8> = (0..64).map(|_| r.bit()).collect();
721        let u0 = u.clone();
722        transform(&mut u);
723        transform(&mut u);
724        assert_eq!(u, u0);
725        let code = PolarCode::new(64, 32, 1.0).unwrap();
726        let info: Vec<u8> = (0..32).map(|_| r.bit()).collect();
727        let c = code.encode_block(&info).unwrap();
728        let y: Vec<f64> = c.iter().map(|b| 4.0 * (1.0 - 2.0 * f64::from(*b))).collect();
729        assert_eq!(code.decode_block(&y).unwrap(), info);
730    }
731
732    #[test]
733    fn the_construction_freezes_the_worst_channels() {
734        // N = 8, K = 4: the classical information set {3, 5, 6, 7}
735        let code = PolarCode::new(8, 4, 2.0).unwrap();
736        assert_eq!(code.info_positions(), &[3, 5, 6, 7]);
737    }
738
739    #[test]
740    fn the_list_and_the_crc_lower_the_block_error_rate() {
741        let base = PolarCode::new(128, 64, 2.0).unwrap();
742        // a short CRC: 11 bits would cost 17 % of the rate of a block of 64 bits
743        let scl = base.clone().with_crc(Crc::crc6()).unwrap().with_list(8).unwrap();
744        let mut r = Rng::new(2);
745        let (mut e_sc, mut e_scl) = (0, 0);
746        let s2 = 1.0 / 10f64.powf(0.2); // Eb/N0 = 2 dB at rate 1/2
747        for _ in 0..300 {
748            let info: Vec<u8> = (0..64).map(|_| r.bit()).collect();
749            for (code, e) in [(&base, &mut e_sc), (&scl, &mut e_scl)] {
750                let c = code.encode_block(&info).unwrap();
751                let y = awgn_llr(&c, s2, &mut r);
752                if code.decode_block(&y).unwrap() != info {
753                    *e += 1;
754                }
755            }
756        }
757        assert!(e_scl * 2 < e_sc, "SC {e_sc} vs CA-SCL {e_scl} block errors");
758    }
759
760    #[test]
761    fn with_crc_rejects_the_fixed_positions_of_a_table() {
762        let table = PolarCode::with_positions(16, &[7, 11, 13, 14, 15], &[12]).unwrap();
763        let err = table.clone().with_crc(Crc::crc6()).unwrap_err();
764        assert!(matches!(err, comnum_core::Error::Config(_)), "{err}");
765        // the table code is untouched: no CRC, positions disjoint from the parity checks
766        assert!(table.crc().is_none());
767        assert!(table.info_positions().iter().all(|p| !table.pc_positions().contains(p)));
768    }
769
770    #[test]
771    fn with_crc_twice_keeps_k_and_the_last_crc() {
772        let once = PolarCode::new(128, 64, 2.0).unwrap().with_crc(Crc::crc6()).unwrap();
773        let twice =
774            PolarCode::new(128, 64, 2.0).unwrap().with_crc(Crc::crc11()).unwrap().with_crc(Crc::crc6()).unwrap();
775        assert_eq!(once, twice);
776        assert_eq!(BlockCode::k(&twice), 64);
777        assert_eq!(twice.info_positions().len(), 64 + 6);
778        assert_eq!(twice.crc().map(Crc::width), Some(6));
779    }
780
781    /// The decoder before the arrays of Tal & Vardy: every leaf computed from the channel
782    /// with the decisions of its path, $O(L n^{2} \log n)$, and the paths copied whole.
783    fn reference_paths(code: &PolarCode, llr: &[f64]) -> Vec<(Vec<u8>, f64)> {
784        fn leaf_llr(i: usize, ch: &[f64], u: &[u8]) -> f64 {
785            let n = ch.len();
786            if n == 1 {
787                return ch[0];
788            }
789            let h = n / 2;
790            let (a, b) = ch.split_at(h);
791            if i < h {
792                let v: Vec<f64> = a.iter().zip(b).map(|(x, y)| f(*x, *y)).collect();
793                leaf_llr(i, &v, u)
794            } else {
795                let mut x = u[..h].to_vec();
796                transform(&mut x);
797                let v: Vec<f64> =
798                    a.iter().zip(b).zip(&x).map(|((p, q), &xb)| q + if xb == 0 { *p } else { -p }).collect();
799                leaf_llr(i - h, &v, &u[h..])
800            }
801        }
802        let (info, pc) = code.masks();
803        let mut paths: Vec<(Vec<u8>, f64)> = vec![(Vec::with_capacity(code.n), 0.0)];
804        for i in 0..code.n {
805            let mut next = Vec::with_capacity(paths.len() * 2);
806            for (u, pm) in &paths {
807                let mut full = u.clone();
808                full.resize(code.n, 0);
809                let lam = leaf_llr(i, llr, &full);
810                let cost = |bit: u8| {
811                    let s = if bit == 0 { lam } else { -lam };
812                    pm + ln_1p_exp(-s)
813                };
814                let bits: &[u8] = if pc[i] {
815                    &[pc_register(&full, &info, &pc, i)]
816                } else if info[i] {
817                    &[0, 1]
818                } else {
819                    &[0]
820                };
821                for &bit in bits {
822                    let mut v = u.clone();
823                    v.push(bit);
824                    next.push((v, cost(bit)));
825                }
826            }
827            next.sort_by(|a, b| a.1.total_cmp(&b.1));
828            next.truncate(code.list);
829            paths = next;
830        }
831        paths
832    }
833
834    #[test]
835    fn the_list_decoder_matches_the_leaf_by_leaf_reference() {
836        let mut r = Rng::new(2015);
837        let mut cases = 0;
838        for m in 3..=10 {
839            let n = 1usize << m;
840            for list in [1, 2, 8, 32] {
841                let frames = 3;
842                for frame in 0..frames {
843                    let k = 1 + r.below(n - 1);
844                    let code = match frame % 3 {
845                        0 => PolarCode::new(n, k, -1.0 + 4.0 * r.uniform()).unwrap(),
846                        1 if k + 6 <= n => PolarCode::new(n, k, 2.0).unwrap().with_crc(Crc::crc6()).unwrap(),
847                        _ => {
848                            // a table code with parity-check positions
849                            let mut pos: Vec<usize> = (0..n).collect();
850                            r.shuffle(&mut pos);
851                            let n_pc = (n / 8).min(n - k);
852                            PolarCode::with_positions(n, &pos[..k], &pos[k..k + n_pc]).unwrap()
853                        }
854                    }
855                    .with_list(list)
856                    .unwrap();
857                    let info: Vec<u8> = (0..code.k).map(|_| r.bit()).collect();
858                    let c = code.encode_block(&info).unwrap();
859                    let s2 = 0.3 + 1.5 * r.uniform();
860                    let mut llr = awgn_llr(&c, s2, &mut r);
861                    if frame == 1 {
862                        // integer LLRs and zeros: ties between the paths
863                        for l in &mut llr {
864                            *l = (*l / 2.0).round();
865                        }
866                    }
867                    let got = code.decode_paths(&llr);
868                    let want = reference_paths(&code, &llr);
869                    assert_eq!(got.len(), want.len(), "n {n}, L {list}");
870                    for ((gu, gm), (wu, wm)) in got.iter().zip(&want) {
871                        assert_eq!(gu, wu, "n {n}, K {k}, L {list}, frame {frame}");
872                        assert_eq!(gm.to_bits(), wm.to_bits(), "n {n}, K {k}, L {list}, frame {frame}");
873                    }
874                    cases += 1;
875                }
876            }
877        }
878        assert!(cases > 40);
879    }
880
881    #[test]
882    fn a_non_finite_design_ebn0_is_rejected() {
883        assert!(PolarCode::new(64, 32, f64::NAN).is_err());
884        assert!(PolarCode::new(64, 32, f64::INFINITY).is_err());
885        assert!(PolarCode::new(64, 32, 1.0).unwrap().with_list(0).is_err());
886    }
887
888    /// The reliability of one channel by the recursion over the stages, as the
889    /// construction computed it before the values of the prefixes were shared.
890    fn former_reliability(i: usize, n: usize, mu: f64) -> f64 {
891        if n == 1 {
892            return mu;
893        }
894        let m = if i < n / 2 { phi_inv(1.0 - (1.0 - phi(mu)).powi(2)) } else { 2.0 * mu };
895        former_reliability(i % (n / 2), n / 2, m)
896    }
897
898    #[test]
899    fn the_shared_reliabilities_are_those_of_the_recursion() {
900        for n in [2usize, 8, 64, 1024] {
901            for mu in [0.05, 1.3, 7.0, 40.0] {
902                let r = reliabilities(n, mu);
903                assert_eq!(r.len(), n);
904                for (i, v) in r.iter().enumerate() {
905                    assert_eq!(v.to_bits(), former_reliability(i, n, mu).to_bits(), "n {n}, mu {mu}, i {i}");
906                }
907            }
908        }
909    }
910}