comnum_fec/bounds.rs
1//! Information-theoretic limits of binary codes on the AWGN channel.
2//!
3//! With BPSK on real AWGN of variance $\sigma^{2}$ per dimension, the LLR of a
4//! transmitted 0 is $L \sim \mathcal{N}(2/\sigma^{2}, 4/\sigma^{2})$, and
5//!
6//! $$
7//! C = 1 - \mathbb{E}\left[\log_{2}(1 + e^{-L})\right], \qquad
8//! V = \operatorname{Var}\left[1 - \log_{2}(1 + e^{-L})\right]
9//! $$
10//!
11//! are the capacity and the dispersion of the binary-input AWGN channel (bits per
12//! channel use; [`biawgn_capacity`], [`biawgn_dispersion`]), both by Gauss–Hermite
13//! quadrature. At a finite length $n$ and $k$ information bits, the normal approximation
14//! of Polyanskiy, Poor & Verdú (2010) gives the smallest achievable block error
15//! probability,
16//!
17//! $$
18//! \varepsilon \approx Q\left(\frac{n C - k + \tfrac{1}{2}\log_{2} n}{\sqrt{n V}}\right)
19//! $$
20//!
21//! ([`normal_approximation`]), and [`shannon_limit_ebn0_db`] the $E_{b}/N_{0}$ at which a
22//! rate-$R$ code can work at all ($n \to \infty$). With $E_{b}/N_{0}$ and the rate
23//! $R = k/n$: $\sigma^{2} = 1 / (2 R E_{b}/N_{0})$. Without the binary constraint, the
24//! limit at the spectral efficiency $\eta = 2R$ is $(2^{\eta} - 1)/\eta$
25//! ([`gaussian_limit_ebn0_db`]).
26//!
27//! [`NormalApproximation`] is the same approximation for any channel of capacity $C$ and
28//! dispersion $V$, $\log_{2} M^{*}(n, \varepsilon) \approx n C - \sqrt{n V}\, Q^{-1}(\varepsilon) +
29//! \frac{1}{2} \log_{2} n$, with the channels of Polyanskiy, Poor & Verdú: the BSC,
30//! $C = 1 - h(\delta)$ and $V = \delta(1 - \delta) \log_{2}^{2}\frac{1 - \delta}{\delta}$; the BEC,
31//! $C = 1 - \delta$ and $V = \delta(1 - \delta)$, without the logarithmic term; the AWGN
32//! channel of SNR $P$ with Gaussian codebooks, $C = \frac{1}{2}\log_{2}(1 + P)$ and
33//! $V = \frac{P (P + 2)}{2 (P + 1)^{2}} \log_{2}^{2} e$ (their eqs. (184), (190), (296)) — and
34//! the binary-input AWGN channel above.
35//!
36//! # Examples
37//! ```
38//! use comnum_fec::bounds::{shannon_limit_ebn0_db, normal_approximation};
39//! // rate 1/2 BPSK: the Shannon limit is 0.187 dB
40//! assert!((shannon_limit_ebn0_db(0.5) - 0.187).abs() < 0.01);
41//! // a (128, 64) code cannot do much better than a block error rate of 1e-3 at 2.4 dB
42//! let p = normal_approximation(128, 64, 2.4);
43//! assert!(p > 5e-4 && p < 3e-3);
44//! ```
45//!
46//! # References
47//!
48//! 1. C. E. Shannon, "A mathematical theory of communication," *Bell Syst. Tech. J.*,
49//! 27, 379–423 and 623–656, 1948.
50//! 2. Y. Polyanskiy, H. V. Poor and S. Verdú, "Channel coding rate in the finite
51//! blocklength regime," *IEEE Trans. Inf. Theory*, 56(5), 2307–2359, 2010.
52//! 3. G. Liva, L. Gaudio, T. Ninacs and T. Jerkovits, "Code design for short blocks: a
53//! survey," *Proc. EuCNC*, 2016.
54
55use comnum_math::{db2lin, gaussian_nodes, ln_1p_exp, qfunc};
56
57/// The noise variance per dimension of BPSK at `ebn0_db` and the rate `rate`.
58pub fn bpsk_noise_variance(ebn0_db: f64, rate: f64) -> f64 {
59 1.0 / (2.0 * rate * db2lin(ebn0_db))
60}
61
62/// $1 - \log_{2}(1 + e^{-L})$ without overflow.
63fn info_density(l: f64) -> f64 {
64 1.0 - ln_1p_exp(-l) / std::f64::consts::LN_2
65}
66
67/// The first two moments of the information density at the noise variance `noise_var`:
68/// the channel LLR of a 0 is $L \sim \mathcal{N}(\mu, 2\mu)$, $\mu = 2/\sigma^{2}$, and
69/// $E f(L)$ is the 64-point Gauss–Hermite rule of [`gaussian_nodes`].
70fn moments(noise_var: f64) -> (f64, f64) {
71 let mu = 2.0 / noise_var;
72 let mut m1 = 0.0;
73 let mut m2 = 0.0;
74 for (w, a) in gaussian_nodes(2.0 * mu, false, 64) {
75 let i = info_density(mu + w.re);
76 m1 += a * i;
77 m2 += a * i * i;
78 }
79 (m1, m2)
80}
81
82/// Capacity of the binary-input AWGN channel (bits per channel use) at the noise
83/// variance `noise_var` per dimension.
84pub fn biawgn_capacity(noise_var: f64) -> f64 {
85 moments(noise_var).0
86}
87
88/// Dispersion of the binary-input AWGN channel (bits² per channel use).
89pub fn biawgn_dispersion(noise_var: f64) -> f64 {
90 let (m1, m2) = moments(noise_var);
91 (m2 - m1 * m1).max(0.0)
92}
93
94/// The normal approximation of the smallest block error probability of an $(n, k)$
95/// binary code at `ebn0_db` (Polyanskiy, Poor & Verdú 2010).
96pub fn normal_approximation(n: usize, k: usize, ebn0_db: f64) -> f64 {
97 let s2 = bpsk_noise_variance(ebn0_db, k as f64 / n as f64);
98 NormalApproximation::biawgn(s2).block_error(n, k)
99}
100
101/// The normal approximation of the coding rate at finite blocklength of a channel of
102/// capacity `capacity` (bits per channel use) and dispersion `dispersion` (bits² per
103/// channel use), with the term $\frac{1}{2}\log_{2} n$ when `half_log` (see the module).
104#[derive(Clone, Copy, Debug, PartialEq)]
105pub struct NormalApproximation {
106 /// The capacity $C$ (bits per channel use).
107 pub capacity: f64,
108 /// The dispersion $V$ (bits² per channel use).
109 pub dispersion: f64,
110 /// Whether the term $\frac{1}{2}\log_{2} n$ applies (all but the BEC).
111 pub half_log: bool,
112}
113
114impl NormalApproximation {
115 /// The binary symmetric channel of crossover probability `delta`.
116 pub fn bsc(delta: f64) -> NormalApproximation {
117 let h = comnum_math::entropy(&[delta, 1.0 - delta]);
118 let v = delta * (1.0 - delta) * ((1.0 - delta) / delta).log2().powi(2);
119 NormalApproximation { capacity: 1.0 - h, dispersion: v, half_log: true }
120 }
121
122 /// The binary erasure channel of erasure probability `delta`.
123 pub fn bec(delta: f64) -> NormalApproximation {
124 NormalApproximation { capacity: 1.0 - delta, dispersion: delta * (1.0 - delta), half_log: false }
125 }
126
127 /// The real AWGN channel of SNR `snr_lin` with Gaussian codebooks.
128 pub fn awgn(snr_lin: f64) -> NormalApproximation {
129 let p = snr_lin;
130 let v = p * (p + 2.0) / (2.0 * (p + 1.0).powi(2)) * std::f64::consts::LOG2_E.powi(2);
131 NormalApproximation { capacity: 0.5 * (1.0 + p).log2(), dispersion: v, half_log: true }
132 }
133
134 /// The binary-input AWGN channel of noise variance `noise_var` per dimension.
135 pub fn biawgn(noise_var: f64) -> NormalApproximation {
136 let (m1, m2) = moments(noise_var);
137 NormalApproximation { capacity: m1, dispersion: (m2 - m1 * m1).max(0.0), half_log: true }
138 }
139
140 /// $\log_{2} M^{*}(n, \varepsilon)$: the largest number of information bits of a code of
141 /// length `n` at the block error probability `eps`.
142 pub fn max_bits(&self, n: usize, eps: f64) -> f64 {
143 let nf = n as f64;
144 nf * self.capacity - (nf * self.dispersion).sqrt() * comnum_math::qfuncinv(eps) + self.third(nf)
145 }
146
147 /// The smallest block error probability of a code of length `n` carrying `k` bits.
148 pub fn block_error(&self, n: usize, k: usize) -> f64 {
149 let nf = n as f64;
150 qfunc((nf * self.capacity - k as f64 + self.third(nf)) / (nf * self.dispersion).sqrt())
151 }
152
153 /// The smallest blocklength that reaches the rate `rate` (bits per channel use) at the
154 /// block error probability `eps` (real-valued, by bisection), or `None` beyond
155 /// capacity.
156 pub fn blocklength(&self, rate: f64, eps: f64) -> Option<f64> {
157 if rate >= self.capacity {
158 return None;
159 }
160 let gap = |n: f64| {
161 n * (self.capacity - rate) - (n * self.dispersion).sqrt() * comnum_math::qfuncinv(eps) + self.third(n)
162 };
163 let mut hi = 2.0;
164 while gap(hi) < 0.0 {
165 hi *= 2.0;
166 if hi > 1e12 {
167 return None;
168 }
169 }
170 Some(comnum_math::bisect(|n| gap(n) < 0.0, 1.0, hi, 200))
171 }
172
173 fn third(&self, n: f64) -> f64 {
174 if self.half_log { 0.5 * n.log2() } else { 0.0 }
175 }
176}
177
178/// The smallest $E_{b}/N_{0}$ (dB) at which BPSK supports the rate `rate`
179/// ($n \to \infty$): the solution of $C = R$.
180pub fn shannon_limit_ebn0_db(rate: f64) -> f64 {
181 comnum_math::bisect(|e| biawgn_capacity(bpsk_noise_variance(e, rate)) < rate, -2.0, 20.0, 100)
182}
183
184/// The smallest $E_{b}/N_{0}$ (dB) of the AWGN channel at the spectral efficiency `eta`
185/// (bits per two dimensions) with Gaussian inputs: $E_{b}/N_{0} > (2^{\eta} - 1)/\eta$,
186/// which tends to $\ln 2$ ($-1.59$ dB, the ultimate Shannon limit) as $\eta \to 0$. A
187/// binary code of rate $R$ on BPSK has $\eta = 2R$; [`shannon_limit_ebn0_db`] is its
188/// binary-input limit, above this one.
189///
190/// # Examples
191/// ```
192/// use comnum_fec::bounds::{gaussian_limit_ebn0_db, shannon_limit_ebn0_db};
193/// assert!((gaussian_limit_ebn0_db(0.0) + 1.592).abs() < 1e-3);
194/// // binary inputs cost 0.19 dB at rate 1/2
195/// assert!((shannon_limit_ebn0_db(0.5) - gaussian_limit_ebn0_db(1.0) - 0.187).abs() < 0.01);
196/// ```
197pub fn gaussian_limit_ebn0_db(eta: f64) -> f64 {
198 let lin = if eta.abs() < 1e-9 { std::f64::consts::LN_2 } else { (eta.exp2() - 1.0) / eta };
199 comnum_math::lin2db(lin)
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn the_gaussian_limit_is_continuous_at_zero() {
208 assert!((gaussian_limit_ebn0_db(1e-6) - gaussian_limit_ebn0_db(0.0)).abs() < 1e-5);
209 assert!(gaussian_limit_ebn0_db(1.0).abs() < 1e-12);
210 // the binary-input limit lies above the Gaussian one
211 for r in [0.1, 0.25, 0.5, 0.75, 0.9] {
212 assert!(shannon_limit_ebn0_db(r) > gaussian_limit_ebn0_db(2.0 * r));
213 }
214 }
215
216 /// The quadrature as written before `gaussian_nodes`: the nodes scaled by hand.
217 fn former_moments(noise_var: f64) -> (f64, f64) {
218 let (t, w) = comnum_math::gauss_hermite(64);
219 let (mu, s) = (2.0 / noise_var, 2.0 / noise_var.sqrt());
220 let sp = std::f64::consts::PI.sqrt();
221 let (mut m1, mut m2) = (0.0, 0.0);
222 for (ti, wi) in t.iter().zip(&w) {
223 let i = info_density(mu + std::f64::consts::SQRT_2 * s * ti);
224 m1 += wi * i / sp;
225 m2 += wi * i * i / sp;
226 }
227 (m1, m2)
228 }
229
230 #[test]
231 fn the_shared_nodes_give_the_former_moments_to_the_rounding() {
232 for ebn0_db in [-2.0, 0.0, 2.0, 4.0, 8.0] {
233 let s2 = bpsk_noise_variance(ebn0_db, 0.5);
234 let ((a1, a2), (b1, b2)) = (moments(s2), former_moments(s2));
235 assert!((a1 - b1).abs() <= 1e-14 && (a2 - b2).abs() <= 1e-14, "{ebn0_db} dB: {a1} {b1}, {a2} {b2}");
236 }
237 }
238
239 #[test]
240 fn capacity_limits_and_monotonicity() {
241 assert!(biawgn_capacity(1e-3) > 0.999_999);
242 assert!(biawgn_capacity(1e3) < 1e-3);
243 // C(σ² = 1) = 0.4859 (the BI-AWGN capacity at Es/N0 = -3 dB)
244 assert!((biawgn_capacity(1.0) - 0.4859).abs() < 2e-3);
245 // the rate-1/3 limit is -0.495 dB, rate 1/2 0.187 dB
246 assert!((shannon_limit_ebn0_db(1.0 / 3.0) + 0.495).abs() < 0.02);
247 // longer codes get closer to capacity
248 assert!(normal_approximation(1024, 512, 1.5) < normal_approximation(128, 64, 1.5));
249 }
250
251 #[test]
252 fn the_approximation_inverts_itself() {
253 let c = NormalApproximation::bsc(0.11);
254 let k = c.max_bits(1000, 1e-3);
255 assert!((c.block_error(1000, k.round() as usize) / 1e-3).ln().abs() < 0.2);
256 assert!(NormalApproximation::bec(0.5).blocklength(0.5, 1e-3).is_none());
257 }
258}
259
260/// Checks against the published tables (`references/`).
261#[cfg(test)]
262mod literature {
263 use super::*;
264
265 /// Y. Polyanskiy, H. V. Poor and S. Verdú, "Channel coding rate in the finite
266 /// blocklength regime," IEEE Trans. Inf. Theory 56(5), 2307–2359, 2010, Table I, the
267 /// column "Normal Approx.": the blocklength that reaches $R = 0.9 C$ is about 955 for
268 /// the BEC(0.5) at $\varepsilon = 10^{-3}$, 3150 for the BSC(0.11), 2750 for the AWGN
269 /// channel at 0 dB and $10^{-3}$, 190 at 20 dB and $10^{-6}$ (here 955.0, 3164, 2748,
270 /// 188.8: within 1 %).
271 #[test]
272 fn polyanskiy_poor_verdu_2010_table_i() {
273 let cases = [
274 (NormalApproximation::bec(0.5), 1e-3, 955.0),
275 (NormalApproximation::bsc(0.11), 1e-3, 3150.0),
276 (NormalApproximation::awgn(1.0), 1e-3, 2750.0),
277 (NormalApproximation::awgn(100.0), 1e-6, 190.0),
278 ];
279 for (c, eps, want) in cases {
280 let n = c.blocklength(0.9 * c.capacity, eps).unwrap();
281 assert!((n / want - 1.0).abs() < 0.01, "{c:?}: n = {n} vs {want}");
282 }
283 }
284}