Skip to main content

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}