comnum_mimo/massive.rs
1//! The uplink of multi-user massive MIMO: $K$ single-antenna users to a base station of
2//! $M$ antennas, with linear receivers (Ngo, Larsson and Marzetta 2013).
3//!
4//! # Model
5//!
6//! The base station receives $y = \sqrt{p_{u}}\\, G x + n$, with $G = H D^{1/2}$ the
7//! $M \times K$ channel: $H$ the small-scale fading (i.i.d. $\mathcal{CN}(0, 1)$, drawn
8//! by `comnum_channel::fading::Mimo`), $D = \mathrm{diag}(\beta_{k})$ the large-scale fading
9//! of the users, $x$ the symbols of unit energy, $n \sim \mathcal{CN}(0, I_{M})$ and
10//! $p_{u}$ the normalised transmit SNR of every user. With perfect channel state
11//! information (CSI), the receiver knows $G$. With estimated CSI, the users send
12//! orthogonal pilots of $\tau \ge K$ symbols at the power $p_{u}$: the MMSE estimate
13//! $\hat G$ has independent columns of variance $\tau p_{u} \beta_{k}^{2} / (\tau p_{u}
14//! \beta_{k} + 1)$ per entry ([`Uplink::estimate_variance`]), and its error, independent
15//! of $\hat G$, the variance $\varepsilon_{k} = \beta_{k} / (\tau p_{u} \beta_{k} + 1)$
16//! ([`Uplink::error_variance`]). Then $y = \sqrt{p_{u}} \hat G x + n'$ with the noise and
17//! estimation error $n'$ of variance $1 + p_{u} \sum_{i} \varepsilon_{i}$
18//! ([`Uplink::effective_noise_variance`]). The achievable rate of user $k$ is
19//! $\mathbb{E}\\, \log_{2}(1 + \mathrm{SINR}_{k})$ (eqs. (13), (37)): that of the
20//! unbiased estimates of `crate::detection::Linear` on the channel $\sqrt{p_{u}} \hat G$
21//! with the noise variance $1 + p_{u} \sum_{i} \varepsilon_{i}$.
22//!
23//! # Lower bounds
24//!
25//! [`Uplink::rate_bound`] gives the closed-form lower bounds on the achievable rate of
26//! user $k$ (Propositions 2, 3, 6 and 7), with $p_{p} = \tau p_{u}$:
27//!
28//! | Receiver | Perfect CSI | Estimated CSI |
29//! |---|---|---|
30//! | MR | $\log_{2}\Bigl(1 + \frac{p_{u} (M - 1) \beta_{k}}{p_{u} \sum_{i \ne k} \beta_{i} + 1}\Bigr)$ | $\log_{2}\Bigl(1 + \frac{\tau p_{u}^{2} (M - 1) \beta_{k}^{2}}{p_{u} (\tau p_{u} \beta_{k} + 1) \sum_{i \ne k} \beta_{i} + (\tau + 1) p_{u} \beta_{k} + 1}\Bigr)$ |
31//! | ZF | $\log_{2}(1 + p_{u} (M - K) \beta_{k})$ | $\log_{2}\Bigl(1 + \frac{\tau p_{u}^{2} (M - K) \beta_{k}^{2}}{(\tau p_{u} \beta_{k} + 1) \sum_{i} \frac{p_{u} \beta_{i}}{\tau p_{u} \beta_{i} + 1} + \tau p_{u} \beta_{k} + 1}\Bigr)$ |
32//!
33//! The bound of MR needs $M \ge 2$, that of ZF $M \ge K + 1$. As $M \to \infty$, with
34//! $p_{u} = E_{u} / M$ and perfect CSI the rate tends to $\log_{2}(1 + \beta_{k} E_{u})$,
35//! and with $p_{u} = E_{u} / \sqrt{M}$ and estimated CSI to $\log_{2}(1 + \tau
36//! \beta_{k}^{2} E_{u}^{2})$ (Propositions 1 and 5): doubling $M$ saves 3 dB of transmit
37//! power with perfect CSI and 1.5 dB with estimated CSI.
38//!
39//! # Examples
40//! ```
41//! use comnum_mimo::detection::Criterion;
42//! use comnum_mimo::massive::Uplink;
43//! // one user, perfect CSI: the MR and ZF bounds are log2(1 + p_u (M - 1) β)
44//! let up = Uplink::new(8, vec![0.5], 2.0)?;
45//! assert!((up.rate_bound(Criterion::Mr, 0)? - 8f64.log2()).abs() < 1e-12);
46//! assert!((up.rate_bound(Criterion::Zf, 0)? - 8f64.log2()).abs() < 1e-12);
47//! # Ok::<(), comnum_core::Error>(())
48//! ```
49//!
50//! # References
51//!
52//! 1. H. Q. Ngo, E. G. Larsson and T. L. Marzetta, "Energy and spectral efficiency of
53//! very large multiuser MIMO systems," *IEEE Trans. Commun.*, 2013 (arXiv 1112.3810).
54
55use comnum_core::{Error, Result, ensure};
56
57use crate::detection::Criterion;
58
59/// The uplink of $K$ users of large-scale fading `large_scale` to `antennas` antennas,
60/// at the normalised transmit SNR `snr_lin` of every user (see the module).
61#[derive(Clone, Debug, PartialEq)]
62pub struct Uplink {
63 /// The number $M$ of base-station antennas.
64 pub antennas: usize,
65 /// The large-scale fading $\beta_{k}$ of every user.
66 pub large_scale: Vec<f64>,
67 /// The normalised transmit SNR $p_{u}$ of every user (linear).
68 pub snr_lin: f64,
69 /// The length $\tau$ of the pilots of the estimated CSI; perfect CSI if `None`.
70 pub pilots: Option<usize>,
71}
72
73impl Uplink {
74 /// The uplink with perfect CSI.
75 ///
76 /// # Errors
77 /// [`Error::Config`] for no antenna or user, a large-scale fading that is not
78 /// positive and finite, or an SNR that is not.
79 pub fn new(antennas: usize, large_scale: Vec<f64>, snr_lin: f64) -> Result<Uplink> {
80 ensure(antennas >= 1 && !large_scale.is_empty(), || {
81 format!("the uplink needs antennas and users, got {antennas} and {}", large_scale.len())
82 })?;
83 ensure(large_scale.iter().all(|b| *b > 0.0 && b.is_finite()), || {
84 "the large-scale fading of every user must be positive and finite".into()
85 })?;
86 ensure(snr_lin > 0.0 && snr_lin.is_finite(), || format!("snr_lin must be positive, got {snr_lin}"))?;
87 Ok(Uplink { antennas, large_scale, snr_lin, pilots: None })
88 }
89
90 /// The uplink with the CSI estimated from orthogonal pilots of `tau` symbols.
91 ///
92 /// # Errors
93 /// [`Error::Config`] for fewer pilot symbols than users.
94 pub fn with_pilots(self, tau: usize) -> Result<Uplink> {
95 ensure(tau >= self.large_scale.len(), || {
96 format!("orthogonal pilots need tau >= K = {}, got {tau}", self.large_scale.len())
97 })?;
98 Ok(Uplink { pilots: Some(tau), ..self })
99 }
100
101 /// The variance $\varepsilon_{k}$ of every entry of the error of the channel
102 /// estimate of every user, 0 with perfect CSI.
103 pub fn error_variance(&self) -> Vec<f64> {
104 match self.pilots {
105 None => vec![0.0; self.large_scale.len()],
106 Some(tau) => self.large_scale.iter().map(|b| b / (tau as f64 * self.snr_lin * b + 1.0)).collect(),
107 }
108 }
109
110 /// The variance of every entry of the channel estimate $\hat G$ of every user:
111 /// $\beta_{k} - \varepsilon_{k}$ ($\beta_{k}$ with perfect CSI).
112 pub fn estimate_variance(&self) -> Vec<f64> {
113 self.large_scale.iter().zip(self.error_variance()).map(|(b, e)| b - e).collect()
114 }
115
116 /// The variance $1 + p_{u} \sum_{i} \varepsilon_{i}$ of the noise and of the
117 /// estimation error at every antenna.
118 pub fn effective_noise_variance(&self) -> f64 {
119 1.0 + self.snr_lin * self.error_variance().iter().sum::<f64>()
120 }
121
122 /// The closed-form lower bound on the achievable rate of user `k` (bit/s/Hz) with
123 /// the receiver `receiver` (see the module).
124 ///
125 /// # Errors
126 /// [`Error::Config`] for a user out of range, MMSE (whose bound is an approximation,
127 /// not implemented), MR with one antenna or ZF with $M \le K$.
128 pub fn rate_bound(&self, receiver: Criterion, k: usize) -> Result<f64> {
129 let n_users = self.large_scale.len();
130 ensure(k < n_users, || format!("user {k} of {n_users}"))?;
131 let (m, p, bk) = (self.antennas as f64, self.snr_lin, self.large_scale[k]);
132 let others: f64 = self.large_scale.iter().enumerate().filter(|(i, _)| *i != k).map(|(_, b)| b).sum();
133 let sinr = match receiver {
134 Criterion::Mr => {
135 ensure(self.antennas >= 2, || "the MR bound needs M >= 2".into())?;
136 match self.pilots {
137 None => p * (m - 1.0) * bk / (p * others + 1.0),
138 Some(tau) => {
139 let t = tau as f64;
140 t * p * p * (m - 1.0) * bk * bk / (p * (t * p * bk + 1.0) * others + (t + 1.0) * p * bk + 1.0)
141 }
142 }
143 }
144 Criterion::Zf => {
145 ensure(self.antennas > n_users, || format!("the ZF bound needs M > K = {n_users}"))?;
146 let dof = m - n_users as f64;
147 match self.pilots {
148 None => p * dof * bk,
149 Some(tau) => {
150 let t = tau as f64;
151 let errors: f64 = self.large_scale.iter().map(|b| p * b / (t * p * b + 1.0)).sum();
152 t * p * p * dof * bk * bk / ((t * p * bk + 1.0) * errors + t * p * bk + 1.0)
153 }
154 }
155 }
156 Criterion::Mmse => {
157 return Err(Error::Config("the MMSE bound of Ngo et al. is an approximation: not implemented".into()));
158 }
159 };
160 Ok((1.0 + sinr).log2())
161 }
162
163 /// The sum over the users of [`Uplink::rate_bound`] (bit/s/Hz).
164 ///
165 /// # Errors
166 /// As [`Uplink::rate_bound`].
167 pub fn sum_rate_bound(&self, receiver: Criterion) -> Result<f64> {
168 (0..self.large_scale.len()).map(|k| self.rate_bound(receiver, k)).sum()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn the_bounds_of_one_user_with_perfect_csi_are_those_of_maximum_ratio_combining() {
178 let up = Uplink::new(16, vec![0.3], 4.0).unwrap();
179 let expected = (1.0 + 4.0 * 15.0 * 0.3f64).log2();
180 for r in [Criterion::Mr, Criterion::Zf] {
181 assert!((up.rate_bound(r, 0).unwrap() - expected).abs() < 1e-12);
182 }
183 }
184
185 #[test]
186 fn estimated_csi_splits_the_large_scale_fading() {
187 let up = Uplink::new(32, vec![1.0, 0.25], 2.0).unwrap().with_pilots(2).unwrap();
188 let (e, g) = (up.error_variance(), up.estimate_variance());
189 assert!((e[0] - 1.0 / 5.0).abs() < 1e-15 && (e[1] - 0.25 / 2.0).abs() < 1e-15);
190 assert!((g[0] + e[0] - 1.0).abs() < 1e-15 && (g[1] + e[1] - 0.25).abs() < 1e-15);
191 assert!((up.effective_noise_variance() - (1.0 + 2.0 * (0.2 + 0.125))).abs() < 1e-15);
192 // estimation costs rate
193 for r in [Criterion::Mr, Criterion::Zf] {
194 let perfect = Uplink { pilots: None, ..up.clone() };
195 assert!(up.sum_rate_bound(r).unwrap() < perfect.sum_rate_bound(r).unwrap());
196 }
197 }
198
199 #[test]
200 fn the_power_scaling_laws_hold() {
201 // Propositions 1 and 5: p_u = E_u / M (perfect CSI), E_u / √M (estimated CSI)
202 let (eu, beta, k) = (10.0, vec![1.0, 0.5, 0.2], 3);
203 let m = 1_000_000usize;
204 let perfect = Uplink::new(m, beta.clone(), eu / m as f64).unwrap();
205 // the estimated CSI converges as 1/√M: 0.06 bit away at 10⁶ antennas, 0.006 at 10⁸
206 let m2 = 100_000_000usize;
207 let estimated = Uplink::new(m2, beta.clone(), eu / (m2 as f64).sqrt()).unwrap().with_pilots(k).unwrap();
208 for r in [Criterion::Mr, Criterion::Zf] {
209 for (i, b) in beta.iter().enumerate() {
210 let p = perfect.rate_bound(r, i).unwrap();
211 let e = estimated.rate_bound(r, i).unwrap();
212 assert!((p - (1.0 + b * eu).log2()).abs() < 1e-3, "{r:?} {i}: {p}");
213 assert!((e - (1.0 + k as f64 * b * b * eu * eu).log2()).abs() < 1e-2, "{r:?} {i}: {e}");
214 }
215 }
216 }
217
218 #[test]
219 fn invalid_uplinks_are_errors() {
220 assert!(Uplink::new(0, vec![1.0], 1.0).is_err());
221 assert!(Uplink::new(4, vec![], 1.0).is_err());
222 assert!(Uplink::new(4, vec![0.0], 1.0).is_err());
223 assert!(Uplink::new(4, vec![1.0], -1.0).is_err());
224 assert!(Uplink::new(4, vec![1.0, 1.0], 1.0).unwrap().with_pilots(1).is_err());
225 let up = Uplink::new(2, vec![1.0, 1.0], 1.0).unwrap();
226 assert!(up.rate_bound(Criterion::Zf, 0).is_err() && up.rate_bound(Criterion::Mmse, 0).is_err());
227 assert!(up.rate_bound(Criterion::Mr, 2).is_err() && up.rate_bound(Criterion::Mr, 0).is_ok());
228 }
229}