comnum_mapping/link_adaptation.rs
1//! Variable-rate variable-power MQAM over Rayleigh fading (Goldsmith & Chua 1997): the
2//! Shannon capacity with power adapted in time, the spectral efficiency of MQAM whose
3//! size and power follow the channel gain at a target bit error rate, with a continuum or
4//! a finite set of constellations, and the average time spent in every fading region.
5//!
6//! The bit error rate of MQAM at the SNR $\gamma$ is bounded by eq. (17),
7//! $\mathrm{BER} \le 0.2\, e^{-1.5\gamma/(M-1)}$ ([`mqam_ber_bound`]), which fixes the
8//! constellation of a target BER at $M(\gamma) = 1 + K\gamma S(\gamma)/\bar S$, with the
9//! power gap $K = -1.5/\ln(5\,\mathrm{BER})$ ([`mqam_power_gap`], eqs. (20), (24)).
10//! Over Rayleigh fading of mean SNR $\bar\gamma$, $p(\gamma) = e^{-\gamma/\bar\gamma}/\bar\gamma$,
11//! every integral is an exponential integral $E_{1}$:
12//!
13//! - capacity with water-filling in time, eqs. (5)–(7): the cutoff $\gamma_{0} = x\bar\gamma$
14//! solves $e^{-x}/x - E_{1}(x) = \bar\gamma$ and $C/B = E_{1}(x)/\ln 2$
15//! ([`rayleigh_water_filling_cutoff`], [`rayleigh_capacity_water_filling`]);
16//! - continuous-rate adaptive MQAM, eqs. (23)–(25): the same water-filling at the mean
17//! SNR $K\bar\gamma$ ([`rayleigh_adaptive_mqam_efficiency`]);
18//! - discrete rates, eqs. (28)–(32): the constellations $M_{1} < \dots < M_{N}$ (none in
19//! region 0) are used for $M_{j} \le \gamma/\gamma_{K}^{*} < M_{j+1}$ with the power
20//! $(M_{j} - 1)/(\gamma K)$; the power constraint (32) fixes $\gamma_{K}^{*}$,
21//! $$\sum_{j=1}^{N} \frac{M_{j} - 1}{K\bar\gamma}\left[E_{1}\!\left(\frac{\gamma_{K}^{*}M_{j}}{\bar\gamma}\right) - E_{1}\!\left(\frac{\gamma_{K}^{*}M_{j+1}}{\bar\gamma}\right)\right] = 1,$$
22//! and $R/B = \sum_{j} \log_{2}M_{j}\, p(M_{j} \le \gamma/\gamma_{K}^{*} < M_{j+1})$
23//! ([`rayleigh_discrete_mqam`]);
24//! - the average fade region duration, eqs. (34)–(35): $\bar\tau_{j} = \pi_{j}/(N_{j+1} + N_{j})$
25//! with $\pi_{j}$ the probability of region $j$ and $N_{j}$ the level-crossing rate of
26//! its lower edge ([`rayleigh_region_durations`]);
27//! - a constant error $\epsilon = \hat\gamma/\gamma$ of the SNR estimate raises the BER of
28//! the target $\mathrm{BER}_{0}$ to $0.2\,(5\,\mathrm{BER}_{0})^{1/\epsilon}$, eq. (42)
29//! ([`estimation_error_ber`]).
30//!
31//! # Examples
32//! ```
33//! use comnum_mapping::link_adaptation::{rayleigh_adaptive_mqam_efficiency, rayleigh_capacity_water_filling};
34//! // 20 dB of mean SNR: 5.90 bit/s/Hz of capacity, 4.22 for adaptive MQAM at 1e-3 (Fig. 3)
35//! let c = rayleigh_capacity_water_filling(100.0)?;
36//! let r = rayleigh_adaptive_mqam_efficiency(100.0, 1e-3)?;
37//! assert!((c - 5.90).abs() < 0.01 && (r - 4.22).abs() < 0.01, "{c} {r}");
38//! # Ok::<(), comnum_core::Error>(())
39//! ```
40//!
41//! # References
42//! 1. A. J. Goldsmith, S.-G. Chua, "Variable-rate variable-power MQAM for fading
43//! channels," *IEEE Trans. Commun.* 45(10), 1218–1230, 1997: eqs. (5)–(7), (17),
44//! (20)–(25), (28)–(35), (42), Figs. 3, 5, 10, Table II.
45
46use comnum_core::{Error, Result};
47use comnum_math::{bisect, expint_e1};
48use std::f64::consts::{LN_2, PI};
49
50/// The bound (17) of the bit error rate of MQAM of size `m` at the SNR `snr` (linear):
51/// $0.2\, e^{-1.5\,\gamma/(M-1)}$, within 1 dB for $M \ge 4$ and SNRs of 0 to 30 dB.
52pub fn mqam_ber_bound(snr: f64, m: f64) -> f64 {
53 0.2 * (-1.5 * snr / (m - 1.0)).exp()
54}
55
56/// The power gap $K = -1.5/\ln(5\,\mathrm{BER})$ of MQAM at the target bit error rate
57/// `ber` (eq. (24)): $-5.48$ dB at $10^{-3}$.
58///
59/// # Errors
60/// [`Error::Config`] unless $0 <$ `ber` $< 0.2$.
61pub fn mqam_power_gap(ber: f64) -> Result<f64> {
62 if !(ber > 0.0 && ber < 0.2) {
63 return Err(Error::Config(format!("the target bit error rate must be in (0, 0.2), got {ber}")));
64 }
65 Ok(-1.5 / (5.0 * ber).ln())
66}
67
68fn check_snr(mean_snr: f64) -> Result<()> {
69 if mean_snr > 0.0 && mean_snr.is_finite() {
70 Ok(())
71 } else {
72 Err(Error::Config(format!("the mean SNR must be positive and finite, got {mean_snr}")))
73 }
74}
75
76/// The cutoff $\gamma_{0}$ (linear) of water-filling in time over Rayleigh fading of mean
77/// SNR `mean_snr` (linear), eq. (6): $x = \gamma_{0}/\bar\gamma$ solves
78/// $e^{-x}/x - E_{1}(x) = \bar\gamma$ (bisection on $\ln x$ to $10^{-15}$).
79///
80/// # Errors
81/// [`Error::Config`] for a mean SNR that is not positive and finite.
82pub fn rayleigh_water_filling_cutoff(mean_snr: f64) -> Result<f64> {
83 check_snr(mean_snr)?;
84 // f(x) = e^{-x}/x - E1(x) falls from infinity to 0: the predicate holds below the root
85 let ln_x = bisect(|l| (-l.exp()).exp() / l.exp() - expint_e1(l.exp()) > mean_snr, -50.0, 5.0, 200);
86 Ok(ln_x.exp() * mean_snr)
87}
88
89/// The capacity (bit/s/Hz) of Rayleigh fading of mean SNR `mean_snr` (linear) with the
90/// power adapted by water-filling in time, eq. (7): $E_{1}(\gamma_{0}/\bar\gamma)/\ln 2$.
91///
92/// # Errors
93/// As [`rayleigh_water_filling_cutoff`].
94pub fn rayleigh_capacity_water_filling(mean_snr: f64) -> Result<f64> {
95 let g0 = rayleigh_water_filling_cutoff(mean_snr)?;
96 Ok(expint_e1(g0 / mean_snr) / LN_2)
97}
98
99/// The spectral efficiency (bit/s/Hz) of MQAM with rate and power adapted continuously
100/// to Rayleigh fading of mean SNR `mean_snr` (linear) at the target bit error rate `ber`,
101/// eq. (25): the water-filling capacity at the mean SNR $K\bar\gamma$.
102///
103/// # Errors
104/// As [`mqam_power_gap`] and [`rayleigh_water_filling_cutoff`].
105pub fn rayleigh_adaptive_mqam_efficiency(mean_snr: f64, ber: f64) -> Result<f64> {
106 rayleigh_capacity_water_filling(mqam_power_gap(ber)? * mean_snr)
107}
108
109/// MQAM with a finite set of constellations adapted to Rayleigh fading (eqs. (28)–(32)).
110#[derive(Clone, Debug, PartialEq)]
111pub struct DiscreteRate {
112 /// The threshold $\gamma_{K}^{*}$ (linear): constellation $M_{j}$ is used for
113 /// $M_{j} \le \gamma/\gamma_{K}^{*} < M_{j+1}$.
114 pub gamma_k: f64,
115 /// The spectral efficiency (bit/s/Hz), eq. (31).
116 pub efficiency: f64,
117 /// The lower edges $A_{j} = \gamma_{K}^{*}M_{j}$ (linear SNR) of the regions $1..N$.
118 pub edges: Vec<f64>,
119}
120
121/// The discrete-rate adaptive MQAM of the constellation sizes `sizes` ($M_{1} < \dots <
122/// M_{N}$, no transmission below $M_{1}$) over Rayleigh fading of mean SNR `mean_snr`
123/// (linear) at the target BER `ber`: $\gamma_{K}^{*}$ from the power constraint (32), by
124/// bisection on its logarithm to $10^{-15}$, and the efficiency (31).
125///
126/// # Errors
127/// [`Error::Config`] for an empty or unsorted set, a size below 2, or as
128/// [`mqam_power_gap`] and [`rayleigh_water_filling_cutoff`].
129pub fn rayleigh_discrete_mqam(mean_snr: f64, ber: f64, sizes: &[usize]) -> Result<DiscreteRate> {
130 check_snr(mean_snr)?;
131 let k = mqam_power_gap(ber)?;
132 if sizes.is_empty() || sizes[0] < 2 || sizes.windows(2).any(|w| w[0] >= w[1]) {
133 return Err(Error::Config(format!("the constellation sizes must be increasing and at least 2, got {sizes:?}")));
134 }
135 let m: Vec<f64> = sizes.iter().map(|&s| s as f64).collect();
136 let e1 = |a: f64| if a.is_infinite() { 0.0 } else { expint_e1(a / mean_snr) };
137 // the average power of eq. (32) for the threshold g: it decreases as g grows
138 let power = |g: f64| -> f64 {
139 (0..m.len())
140 .map(|j| {
141 let next = m.get(j + 1).map_or(f64::INFINITY, |&n| g * n);
142 (m[j] - 1.0) / (k * mean_snr) * (e1(g * m[j]) - e1(next))
143 })
144 .sum()
145 };
146 let ln_g = bisect(|l| power(l.exp()) > 1.0, -60.0, 20.0, 200);
147 let g = ln_g.exp();
148 let edges: Vec<f64> = m.iter().map(|&mj| g * mj).collect();
149 let surv = |a: f64| (-a / mean_snr).exp();
150 let efficiency = (0..m.len())
151 .map(|j| {
152 let next = edges.get(j + 1).map_or(0.0, |&a| surv(a));
153 m[j].log2() * (surv(edges[j]) - next)
154 })
155 .sum();
156 Ok(DiscreteRate { gamma_k: g, efficiency, edges })
157}
158
159/// The average time (s) spent in every region $0..N$ of a [`DiscreteRate`] policy over
160/// Rayleigh fading of mean SNR `mean_snr` (linear) and maximum Doppler shift `doppler_hz`
161/// (eqs. (34)–(35)): region 0 below $A_{1}$ (no transmission), region $j$ on
162/// $[A_{j}, A_{j+1})$; $\bar\tau_{j} = \pi_{j}/(N_{j} + N_{j+1})$ with
163/// $N_{j} = \sqrt{2\pi A_{j}/\bar\gamma}\, f_{D}\, e^{-A_{j}/\bar\gamma}$ ($N_{0} = 0$,
164/// $N_{N+1} = 0$).
165///
166/// # Errors
167/// [`Error::Config`] for a mean SNR or a Doppler shift that is not positive and finite.
168pub fn rayleigh_region_durations(policy: &DiscreteRate, mean_snr: f64, doppler_hz: f64) -> Result<Vec<f64>> {
169 check_snr(mean_snr)?;
170 if !(doppler_hz > 0.0 && doppler_hz.is_finite()) {
171 return Err(Error::Config(format!("the Doppler shift must be positive and finite, got {doppler_hz}")));
172 }
173 let a: Vec<f64> = std::iter::once(0.0).chain(policy.edges.iter().copied()).collect();
174 let rate = |a: f64| (2.0 * PI * a / mean_snr).sqrt() * doppler_hz * (-a / mean_snr).exp();
175 let surv = |a: f64| (-a / mean_snr).exp();
176 Ok((0..a.len())
177 .map(|j| {
178 let (lo, hi) = (a[j], a.get(j + 1).copied());
179 let prob = surv(lo) - hi.map_or(0.0, surv);
180 let crossings = rate(lo) + hi.map_or(0.0, rate);
181 prob / crossings
182 })
183 .collect())
184}
185
186/// The bit error rate of adaptive MQAM designed for `ber0` when the SNR is estimated as
187/// $\epsilon$ = `epsilon` times its value (perfect AGC), eq. (42):
188/// $0.2\,(5\,\mathrm{BER}_{0})^{1/\epsilon}$.
189pub fn estimation_error_ber(ber0: f64, epsilon: f64) -> f64 {
190 0.2 * (5.0 * ber0).powf(1.0 / epsilon)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 /// The cutoff solves eq. (6) and lies below 1 (Sec. III); the adaptive MQAM loses the
198 /// power gap K against the capacity; the bound (17) gives 0.2 at zero SNR.
199 #[test]
200 fn the_cutoff_solves_eq_6_and_mqam_pays_the_gap() {
201 for snr_db in [0.0, 10.0, 20.0, 30.0] {
202 let s: f64 = 10f64.powf(snr_db / 10.0);
203 let g0 = rayleigh_water_filling_cutoff(s).unwrap();
204 let x = g0 / s;
205 assert!(((-x).exp() / g0 - expint_e1(x) / s - 1.0).abs() < 1e-9, "{snr_db} dB");
206 assert!(g0 < 1.0);
207 let c = rayleigh_capacity_water_filling(s).unwrap();
208 let r = rayleigh_adaptive_mqam_efficiency(s, 1e-3).unwrap();
209 assert!(r < c);
210 }
211 assert!((mqam_ber_bound(0.0, 4.0) - 0.2).abs() < 1e-15);
212 assert!((10.0 * mqam_power_gap(1e-3).unwrap().log10() + 5.48).abs() < 0.01);
213 assert!(mqam_power_gap(0.3).is_err() && rayleigh_capacity_water_filling(-1.0).is_err());
214 }
215
216 /// With many fine constellations the discrete rate approaches the continuous one, and
217 /// a single constellation of size 2 never exceeds 1 bit/s/Hz.
218 #[test]
219 fn discrete_rates_approach_the_continuous_one_and_saturate() {
220 let s = 100.0;
221 let sizes: Vec<usize> = (1..=14).map(|b| 1usize << b).collect();
222 let d = rayleigh_discrete_mqam(s, 1e-3, &sizes).unwrap();
223 let c = rayleigh_adaptive_mqam_efficiency(s, 1e-3).unwrap();
224 assert!(d.efficiency < c && c - d.efficiency < 0.6, "{} {c}", d.efficiency);
225 let one = rayleigh_discrete_mqam(s, 1e-3, &[2]).unwrap();
226 assert!(one.efficiency < 1.0 && one.efficiency > 0.9);
227 assert!(rayleigh_discrete_mqam(s, 1e-3, &[4, 2]).is_err());
228 let t = rayleigh_region_durations(&d, s, 100.0).unwrap();
229 assert_eq!(t.len(), sizes.len() + 1);
230 assert!(t.iter().all(|&x| x > 0.0 && x.is_finite()));
231 }
232}