comnum_mapping/bicm.rs
1//! Cutoff rates of coded modulation (CM) and bit-interleaved coded modulation (BICM)
2//! with uniform inputs (Caire, Taricco & Biglieri 1998, Sec. III); their capacities are
3//! the mutual information and the generalized mutual information of
4//! [`crate::theory::constellation_mi`] and [`crate::theory::constellation_gmi`].
5//!
6//! A constellation $\mathcal{X}$ of $M = 2^{m}$ points with unit mean energy carries
7//! the label of `points()[i]` in the bits of `i` (Gray for the families of
8//! [`Constellation::new`], set partitioning for [`Constellation::set_partitioning`]).
9//! The channel is $y = g x + n$, $n \sim \mathcal{CN}(0, \sigma^{2})$ (a real
10//! constellation sees a real channel, as in [`crate::theory`]), the SNR is
11//! $1/\sigma^{2}$, and the gain $g$ is 1 ([`RateChannel::Awgn`]) or Rayleigh,
12//! $\mathbb{E}|g|^{2} = 1$, known at the receiver ([`RateChannel::Rayleigh`], perfect CSI
13//! and ideal interleaving; [`RateChannel`] is that of [`crate::theory`]). With $\mathcal{X}_{b}^{i}$ the points whose bit $i$ is $b$:
14//!
15//! - the CM capacity (eq. (10)), the mutual information
16//! ([`crate::theory::constellation_mi`] with `px = None`),
17//! $\tilde{C} = m - \mathbb{E}\left[\log_{2} \sum_{z \in \mathcal{X}} p(y|z) / p(y|x)\right]$;
18//! - the BICM capacity (eq. (14)) ([`crate::theory::constellation_gmi`] with
19//! `px = None`),
20//! $\hat{C} = m - \sum_{i=1}^{m} \mathbb{E}_{b,y}\left[\log_{2} \sum_{z \in \mathcal{X}} p(y|z)
21//! / \sum_{z \in \mathcal{X}_{b}^{i}} p(y|z)\right] \le \tilde{C}$ (eq. (16)), the
22//! generalized mutual information;
23//! - the CM cutoff rate (eq. (17)), in closed form, [`cm_cutoff_rate`],
24//! $\tilde{R}_{0} = m - \log_{2} \frac{1}{M} \sum_{x, z} \mathbb{E}_{g}\, e^{-|g|^{2}
25//! |x - z|^{2} / (4\sigma^{2})}$, where the mean over Rayleigh fading is
26//! $1 / (1 + |x - z|^{2} / (4\sigma^{2}))$;
27//! - the BICM cutoff rate (eqs. (19), (21)), [`bicm_cutoff_rate`],
28//! $\hat{R}_{0} = m\left(1 - \log_{2}(1 + B)\right)$ with the Bhattacharyya factor
29//! $B = \frac{1}{m} \sum_{i} \mathbb{E}_{b,y}\sqrt{\sum_{z \in \mathcal{X}_{\bar{b}}^{i}}
30//! p(y|z) / \sum_{z \in \mathcal{X}_{b}^{i}} p(y|z)}$.
31//!
32//! The expectations over the noise use Gauss–Hermite quadrature with `points` nodes
33//! per real dimension (the paper uses Monte Carlo); the mean over Rayleigh fading is
34//! the integral over $u = \ln |g|^{2}$, of density $e^{u - e^{u}}$, by Simpson's rule
35//! with a step of 1/8.
36//!
37//! # Examples
38//! ```
39//! use comnum_mapping::bicm::bicm_cutoff_rate;
40//! use comnum_mapping::constellation::{Constellation, ConstellationKind};
41//! use comnum_mapping::theory::{RateChannel, constellation_gmi, constellation_mi};
42//! // 16-QAM at 10 dB: BICM with Gray labels loses little, with set partitioning much
43//! let gray = Constellation::qam(16)?;
44//! let sp = Constellation::set_partitioning(ConstellationKind::Qam, 16)?;
45//! let cm = constellation_mi(&gray, 10.0, None, RateChannel::Awgn, 16)?;
46//! let g = constellation_gmi(&gray, 10.0, None, RateChannel::Awgn, 16)?;
47//! let s = constellation_gmi(&sp, 10.0, None, RateChannel::Awgn, 16)?;
48//! assert!(cm > g && cm - g < 0.05 && g - s > 0.3);
49//! // the cutoff rate lies below the capacity
50//! assert!(bicm_cutoff_rate(&gray, 10.0, RateChannel::Awgn, 16)? < g);
51//! # Ok::<(), comnum_core::Error>(())
52//! ```
53//!
54//! # References
55//!
56//! 1. G. Caire, G. Taricco and E. Biglieri, "Bit-interleaved coded modulation," *IEEE
57//! Trans. Inf. Theory*, 44(3), 927–946, 1998.
58//! 2. E. Zehavi, "8-PSK trellis codes for a Rayleigh channel," *IEEE Trans. Commun.*,
59//! 40(5), 873–884, 1992.
60
61use crate::constellation::Constellation;
62use crate::theory::{RateChannel, TheorySetup, bit_expectations, over_fading, setup};
63use comnum_core::Result;
64use comnum_math::{db2lin, log_sum_exp_iter};
65
66/// The CM cutoff rate (bits per channel use) of a constellation with uniform inputs,
67/// eq. (17) of Caire et al. 1998 in closed form (see the module).
68///
69/// # Examples
70/// ```
71/// use comnum_mapping::bicm::cm_cutoff_rate;
72/// use comnum_mapping::theory::RateChannel;
73/// use comnum_mapping::constellation::Constellation;
74/// // BPSK: 1 - log2(1 + e^{-Es/N0})
75/// let c = Constellation::psk(2)?;
76/// let r0 = cm_cutoff_rate(&c, 0.0, RateChannel::Awgn);
77/// assert!((r0 - (1.0 - (1.0 + (-1f64).exp()).log2())).abs() < 1e-12);
78/// # Ok::<(), comnum_core::Error>(())
79/// ```
80pub fn cm_cutoff_rate(c: &Constellation, snr_db: f64, fading: RateChannel) -> f64 {
81 let x = c.normalized();
82 let x = x.points();
83 let m = x.len() as f64;
84 // |x - z|²/(4σ²) for a complex channel, |x - z|²/(8σ²) for a real one
85 let scale = db2lin(snr_db) / if c.kind().is_real() { 8.0 } else { 4.0 };
86 let mut sum = 0.0;
87 for xi in x {
88 for xj in x {
89 let a = scale * (xi - xj).norm_sqr();
90 sum += match fading {
91 RateChannel::Awgn => (-a).exp(),
92 RateChannel::Rayleigh => 1.0 / (1.0 + a),
93 };
94 }
95 }
96 m.log2() - (sum / m).log2()
97}
98
99/// The BICM cutoff rate (bits per channel use) of a labelled constellation with uniform
100/// inputs, eqs. (19) and (21) of Caire et al. 1998 (see the module).
101///
102/// # Errors
103/// [`comnum_core::Error::Config`] for an invalid constellation.
104pub fn bicm_cutoff_rate(c: &Constellation, snr_db: f64, fading: RateChannel, points: usize) -> Result<f64> {
105 let b = c.bits_per_symbol() as f64;
106 let factor = over_fading(|s| Ok(bhattacharyya(&setup(c, None, s, points)?)), snr_db, fading)?;
107 Ok(b * (1.0 - factor.ln_1p() / std::f64::consts::LN_2))
108}
109
110/// The Bhattacharyya factor $B$ of eq. (19), from the log-metrics of every bit.
111fn bhattacharyya(s: &TheorySetup) -> f64 {
112 // √(Σ_{other} / Σ_{same}) = e^{(ln Σ_{other} - ln Σ_{same})/2}, with ln Σ_{other}
113 // = ln(Σ_all - Σ_same) computed without cancellation from the log-sums
114 let terms = bit_expectations(s, |all, same| {
115 let total = log_sum_exp_iter(all.iter().copied());
116 let other = total + (-(same - total).exp()).ln_1p();
117 (0.5 * (other - same)).exp()
118 });
119 terms.iter().sum::<f64>() / terms.len() as f64
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::constellation::ConstellationKind;
126 use crate::theory::{constellation_gmi, constellation_mi};
127 use comnum_core::rng::Rng;
128
129 #[test]
130 fn the_rayleigh_mean_of_the_cm_cutoff_rate_is_its_closed_form() {
131 // the numerical mean over fading of the AWGN closed form (inside the log)
132 let c = Constellation::qam(16).unwrap();
133 let m = 16f64;
134 for snr_db in [-5.0, 5.0, 20.0, 40.0] {
135 let inner = |s: f64| Ok((m.log2() - cm_cutoff_rate(&c, s, RateChannel::Awgn)).exp2());
136 let mean = over_fading(inner, snr_db, RateChannel::Rayleigh).unwrap();
137 let want = m.log2() - mean.log2();
138 let got = cm_cutoff_rate(&c, snr_db, RateChannel::Rayleigh);
139 assert!((got - want).abs() < 1e-6, "{snr_db} dB: {got} vs {want}");
140 }
141 }
142
143 #[test]
144 fn the_capacities_over_rayleigh_fading_match_a_simulation() {
145 // Monte Carlo of the information densities of eqs. (10) and (14), 16-QAM Gray
146 let c = Constellation::qam(16).unwrap().normalized();
147 let x = c.points();
148 let snr_db = 8.0;
149 let sigma2 = db2lin(-snr_db);
150 let mut r = Rng::new(11);
151 let n = 40_000;
152 let (mut cm, mut bicm) = (Vec::with_capacity(n), Vec::with_capacity(n));
153 for _ in 0..n {
154 let i = r.below(16);
155 let g = r.complex_gaussian();
156 let y = g * x[i] + r.complex_gaussian() * sigma2.sqrt();
157 let l: Vec<f64> = x.iter().map(|z| -(y - g * z).norm_sqr() / sigma2).collect();
158 let all = log_sum_exp_iter(l.iter().copied());
159 cm.push(4.0 - (all - l[i]) / std::f64::consts::LN_2);
160 let mut v = 4.0;
161 for k in 0..4 {
162 let bit = (i >> (3 - k)) & 1;
163 let same = log_sum_exp_iter((0..16).filter(|&j| (j >> (3 - k)) & 1 == bit).map(|j| l[j]));
164 v -= (all - same) / std::f64::consts::LN_2;
165 }
166 bicm.push(v);
167 }
168 for (v, want) in [
169 (&cm, constellation_mi(&c, snr_db, None, RateChannel::Rayleigh, 16).unwrap()),
170 (&bicm, constellation_gmi(&c, snr_db, None, RateChannel::Rayleigh, 16).unwrap()),
171 ] {
172 let mean = v.iter().sum::<f64>() / n as f64;
173 let sd = (v.iter().map(|a| (a - mean).powi(2)).sum::<f64>() / (n * (n - 1)) as f64).sqrt();
174 assert!((mean - want).abs() < 4.0 * sd, "{mean} ± {sd} vs {want}");
175 }
176 }
177
178 #[test]
179 fn the_bhattacharyya_factor_of_bpsk_is_closed() {
180 // one bit: B = e^{-Es/N0} over AWGN, 1/(1 + Es/N0) over Rayleigh fading
181 let c = Constellation::psk(2).unwrap();
182 for snr_db in [-3.0, 0.0, 6.0] {
183 let s = db2lin(snr_db);
184 for (fading, b) in [(RateChannel::Awgn, (-s).exp()), (RateChannel::Rayleigh, 1.0 / (1.0 + s))] {
185 let want = 1.0 - b.ln_1p() / std::f64::consts::LN_2;
186 let got = bicm_cutoff_rate(&c, snr_db, fading, 40).unwrap();
187 assert!((got - want).abs() < 1e-6, "{snr_db} dB {fading:?}: {got} vs {want}");
188 assert!((got - cm_cutoff_rate(&c, snr_db, fading)).abs() < 1e-6);
189 }
190 }
191 }
192
193 #[test]
194 fn bicm_never_beats_cm_capacity_and_gray_beats_set_partitioning() {
195 for (kind, m) in [(ConstellationKind::Psk, 8), (ConstellationKind::Qam, 16)] {
196 let gray = Constellation::new(kind, m).unwrap();
197 let sp = Constellation::set_partitioning(kind, m).unwrap();
198 for snr_db in [0.0, 6.0, 12.0] {
199 let cm = constellation_mi(&gray, snr_db, None, RateChannel::Awgn, 16).unwrap();
200 let g = constellation_gmi(&gray, snr_db, None, RateChannel::Awgn, 16).unwrap();
201 let s = constellation_gmi(&sp, snr_db, None, RateChannel::Awgn, 16).unwrap();
202 assert!(cm >= g - 1e-12 && g > s, "{kind:?} {snr_db} dB: {cm} {g} {s}");
203 // relabelling does not change the CM capacity
204 assert!((constellation_mi(&sp, snr_db, None, RateChannel::Awgn, 16).unwrap() - cm).abs() < 1e-9);
205 }
206 }
207 }
208}
209
210#[cfg(test)]
211mod literature {
212 use super::*;
213 use crate::constellation::ConstellationKind;
214 use crate::theory::{constellation_gmi, constellation_mi};
215 use comnum_math::bisect;
216
217 fn pair(kind: ConstellationKind, m: usize) -> (Constellation, Constellation) {
218 (Constellation::new(kind, m).unwrap(), Constellation::set_partitioning(kind, m).unwrap())
219 }
220
221 /// G. Caire, G. Taricco and E. Biglieri, "Bit-interleaved coded modulation," IEEE
222 /// Trans. Inf. Theory 44(3), 1998, Figs. 4 (AWGN) and 5 (Rayleigh fading, perfect
223 /// CSI): values read on the figures within 0.1 bit (AWGN at 10 dB: CM 8PSK 2.7, CM
224 /// 16QAM 3.15, BICM-SP 16QAM 2.4; Rayleigh: CM 16QAM 2.6 at 10 dB, BICM-SP 16QAM 3.0
225 /// at 15 dB), and the text: Gray-labelled BICM is "very close" to CM (within 0.1
226 /// bit from -5 to 20 dB), set partitioning "several decibels worse" (2.3 dB more
227 /// SNR for 2 bits with 8PSK, 3.8 dB with 16QAM).
228 #[test]
229 fn caire_1998_figs_4_5_capacity() {
230 let (p8, _) = pair(ConstellationKind::Psk, 8);
231 let (q16, sp16) = pair(ConstellationKind::Qam, 16);
232 for (got, want) in [
233 (constellation_mi(&p8, 10.0, None, RateChannel::Awgn, 12).unwrap(), 2.7),
234 (constellation_mi(&q16, 10.0, None, RateChannel::Awgn, 12).unwrap(), 3.15),
235 (constellation_gmi(&sp16, 10.0, None, RateChannel::Awgn, 12).unwrap(), 2.4),
236 (constellation_mi(&q16, 10.0, None, RateChannel::Rayleigh, 8).unwrap(), 2.6),
237 (constellation_gmi(&sp16, 15.0, None, RateChannel::Rayleigh, 8).unwrap(), 3.0),
238 ] {
239 assert!((got - want).abs() < 0.1, "{got} vs {want}");
240 }
241 for (kind, m) in [(ConstellationKind::Psk, 4), (ConstellationKind::Psk, 8), (ConstellationKind::Qam, 16)] {
242 let (gray, sp) = pair(kind, m);
243 for snr_db in [-5.0, 0.0, 5.0, 10.0, 20.0] {
244 let gap = constellation_mi(&gray, snr_db, None, RateChannel::Awgn, 12).unwrap()
245 - constellation_gmi(&gray, snr_db, None, RateChannel::Awgn, 12).unwrap();
246 assert!((-1e-12..0.1).contains(&gap), "{kind:?} {m}, {snr_db} dB: {gap}");
247 }
248 if m > 4 {
249 let need = |c: &Constellation| {
250 bisect(|s| constellation_gmi(c, s, None, RateChannel::Awgn, 12).unwrap() < 2.0, -10.0, 30.0, 30)
251 };
252 let loss = need(&sp) - need(&gray);
253 assert!((2.0..6.0).contains(&loss), "{kind:?} {m}: {loss} dB");
254 }
255 }
256 }
257}