comnum_waveform/ofdm/lte.rs
1//! The downlink signals of LTE that a terminal reads before anything else: the
2//! synchronisation signals, the cell-specific reference signals, the resource elements
3//! of the broadcast channel, the OFDM grid with its unused DC subcarrier, and the
4//! space-frequency block code of transmit diversity (frame structure type 1, FDD, normal
5//! cyclic prefix).
6//!
7//! # Grid and numerology
8//!
9//! A downlink carrier of $N_{\mathrm{RB}}$ resource blocks has $K = 12 N_{\mathrm{RB}}$
10//! subcarriers of 15 kHz, numbered $k = 0, \dots, K - 1$ from the lowest; the subcarrier
11//! $k$ sits at $(k - K/2)\Delta f$ for $k < K/2$ and $(k - K/2 + 1)\Delta f$ above, the DC
12//! subcarrier being left empty (TS 36.211, Sec. 6.12). A slot holds 7 OFDM symbols, with
13//! the prefixes of 160 then 144 samples at 30.72 MS/s, the numerology $\mu = 0$ of
14//! [`super::nr::Numerology`]; [`Carrier`] modulates the grid with the prefixes of
15//! [`super::nr::Carrier`], the empty DC subcarrier inserted. The six central resource
16//! blocks, $k = 6 N_{\mathrm{RB}} - 36, \dots, 6 N_{\mathrm{RB}} + 35$, carry the same
17//! signals in every bandwidth: a receiver reads them at 1.92 MS/s with the grid of
18//! $N_{\mathrm{RB}} = 6$ and an FFT of 128.
19//!
20//! # Synchronisation signals (Sec. 6.11)
21//!
22//! The 504 cell identities are $N_{\mathrm{ID}}^{\mathrm{cell}} = 3 N_{\mathrm{ID}}^{(1)} +
23//! N_{\mathrm{ID}}^{(2)}$, $N_{\mathrm{ID}}^{(1)} < 168$, $N_{\mathrm{ID}}^{(2)} < 3$. Both
24//! signals occupy the 62 subcarriers $k = n - 31 + 6 N_{\mathrm{RB}}$, $n = 0, \dots, 61$
25//! (five empty ones on each side), in slots 0 and 10: the PSS in the last symbol of the
26//! slot ($l = 6$), the SSS in the symbol before ($l = 5$).
27//!
28//! - PSS ([`pss`]): the Zadoff–Chu sequence of length 63 and root $u = 25, 29, 34$ for
29//! $N_{\mathrm{ID}}^{(2)} = 0, 1, 2$, its middle element punctured,
30//! $d_{u}(n) = e^{-j\pi u n (n + 1)/63}$ for $n \le 30$ and $e^{-j\pi u (n + 1)(n + 2)/63}$
31//! for $31 \le n \le 61$ (eq. of Sec. 6.11.1.1, Table 6.11.1.1-1);
32//! - SSS ([`sss`]): the interleaving of two m-sequences of length 31,
33//! $d(2n) = s_{0}^{(m_{0})}(n) c_{0}(n)$ and $d(2n + 1) = s_{1}^{(m_{1})}(n) c_{1}(n)
34//! z_{1}^{(m_{0})}(n)$ in subframe 0, the roles of $m_{0}$ and $m_{1}$ swapped in subframe
35//! 5, so that one SSS tells the half frame; $m_{0} = m' \bmod 31$, $m_{1} = (m_{0} +
36//! \lfloor m'/31 \rfloor + 1) \bmod 31$, $m' = N_{\mathrm{ID}}^{(1)} + q(q + 1)/2$, $q =
37//! \lfloor (N_{\mathrm{ID}}^{(1)} + q'(q' + 1)/2)/30 \rfloor$, $q' = \lfloor
38//! N_{\mathrm{ID}}^{(1)}/30 \rfloor$ (Table 6.11.2.1-1); $\tilde{s}$, $\tilde{c}$ and
39//! $\tilde{z}$ are the m-sequences $x(i + 5) = x(i + 2) + x(i)$, $x(i + 3) + x(i)$ and
40//! $x(i + 4) + x(i + 2) + x(i + 1) + x(i)$, started at $\[0\,0\,0\,0\,1\]$, as $1 - 2x$;
41//! $c_{0}$, $c_{1}$ are $\tilde{c}$ shifted by $N_{\mathrm{ID}}^{(2)}$ and
42//! $N_{\mathrm{ID}}^{(2)} + 3$, $z_{1}^{(m)}$ is $\tilde{z}$ shifted by $m \bmod 8$.
43//!
44//! # Cell-specific reference signals (Sec. 6.10.1)
45//!
46//! In the symbol $l$ of the slot $n_{s}$, the CRS is the QPSK of the Gold sequence of
47//! 3GPP ([`comnum_math::sequences::gold_3gpp`]) with
48//! $c_{\mathrm{init}} = 2^{10}(7(n_{s} + 1) + l + 1)(2N_{\mathrm{ID}}^{\mathrm{cell}} + 1) +
49//! 2N_{\mathrm{ID}}^{\mathrm{cell}} + 1$,
50//! $r(m) = \[(1 - 2c(2m)) + j(1 - 2c(2m + 1))\]/\sqrt{2}$, of which the carrier uses
51//! $r(m + 110 - N_{\mathrm{RB}})$, $m < 2 N_{\mathrm{RB}}$ ([`crs`]), on the subcarriers
52//! $k = 6m + (v + v_{\mathrm{shift}}) \bmod 6$, $v_{\mathrm{shift}} =
53//! N_{\mathrm{ID}}^{\mathrm{cell}} \bmod 6$ ([`crs_subcarriers`]): ports 0 and 1 in the
54//! symbols $l = 0$ and 4 of every slot, $v = 0$ and 3 (port 0) or 3 and 0 (port 1);
55//! ports 2 and 3 in $l = 1$, $v = 3(n_{s} \bmod 2)$ and $3 + 3(n_{s} \bmod 2)$. A port
56//! sends nothing on the CRS elements of the others.
57//!
58//! # Broadcast channel (Sec. 6.6)
59//!
60//! The PBCH occupies the 72 central subcarriers of the first four symbols of slot 1, but
61//! for the elements of the CRS of four ports, whatever the number of ports: those $k$
62//! with $k \equiv v_{\mathrm{shift}} \pmod 3$ in $l = 0$ and 1; 240 elements per frame,
63//! frequency first ([`pbch_elements`]). With two or four ports, the QPSK symbols are
64//! sent by transmit diversity ([`sfbc`], Sec. 6.3.3.3 and 6.3.4.3): the pairs
65//! $(x_{0}, x_{1}) = (d(2i), d(2i + 1))$ go on two consecutive elements as
66//! $\frac{1}{\sqrt{2}}\[x_{0}, -x_{1}^{*}\]$ and $\frac{1}{\sqrt{2}}\[x_{1}, x_{0}^{*}\]$ on ports
67//! 0 and 1 (Alamouti's code across subcarriers); with four ports, the pairs alternate
68//! between the ports 0, 2 and 1, 3.
69//!
70//! # Examples
71//! ```
72//! use comnum_waveform::ofdm::lte::{pss, sss, pbch_elements, Carrier};
73//! assert_eq!(pss(1)?.len(), 62);
74//! assert_eq!(sss(100, 1, false)?.len(), 62);
75//! assert_eq!(pbch_elements(301, 6)?.len(), 240);
76//! let c = Carrier::new(6, 128)?; // the six central RBs, 1.92 MS/s
77//! assert_eq!(c.sample_rate(), 1.92e6);
78//! # Ok::<(), comnum_core::Error>(())
79//! ```
80//!
81//! # References
82//!
83//! 1. 3GPP TS 36.211 V18.0.2, *E-UTRA; Physical channels and modulation*, Sec. 6.3.3.3,
84//! 6.3.4.3, 6.6, 6.10.1, 6.11, 6.12, 7.1.2, 7.2.
85//! 2. D. Astély et al., "LTE: the evolution of mobile broadband," *IEEE Commun. Mag.*
86//! 47(4), 44–51, 2009.
87
88use super::nr;
89use comnum_core::{Error, Result, ensure, ensure_shape};
90use comnum_math::C64;
91use comnum_math::sequences::{gold_3gpp, lfsr, zadoff_chu};
92use comnum_mimo::stbc::Ostbc;
93use std::f64::consts::FRAC_1_SQRT_2;
94
95/// The cell identities $N_{\mathrm{ID}}^{\mathrm{cell}} < 504$.
96pub const CELL_IDS: u32 = 504;
97/// The largest downlink bandwidth, $N_{\mathrm{RB}}^{\max,\mathrm{DL}} = 110$ resource blocks.
98pub const MAX_RB: usize = 110;
99/// Subcarriers of a resource block.
100pub const SUBCARRIERS_PER_RB: usize = nr::SUBCARRIERS_PER_RB;
101/// OFDM symbols of a slot (normal cyclic prefix).
102pub const SYMBOLS_PER_SLOT: usize = 7;
103/// Slots of a 10 ms frame.
104pub const SLOTS_PER_FRAME: usize = 20;
105/// Length of the PSS and of the SSS.
106pub const SYNC_LENGTH: usize = 62;
107/// The symbol of the PSS in slots 0 and 10.
108pub const PSS_SYMBOL: usize = 6;
109/// The symbol of the SSS in slots 0 and 10.
110pub const SSS_SYMBOL: usize = 5;
111/// The subcarriers of the six central resource blocks (PSS, SSS, PBCH).
112pub const CENTRAL_SUBCARRIERS: usize = 72;
113/// The PBCH elements of a frame.
114pub const PBCH_ELEMENTS: usize = 240;
115
116/// The roots of the PSS, Table 6.11.1.1-1.
117const PSS_ROOTS: [u32; 3] = [25, 29, 34];
118
119/// The resource blocks of the six bandwidths of LTE and their FFT at 15 kHz
120/// ($N_{\mathrm{FFT}} \Delta f$ the sampling rate: 1.92 to 30.72 MS/s).
121pub const BANDWIDTHS: [(usize, usize); 6] = [(6, 128), (15, 256), (25, 512), (50, 1024), (75, 1536), (100, 2048)];
122
123/// The usual FFT size of a carrier of `n_rb` resource blocks ([`BANDWIDTHS`]).
124///
125/// # Errors
126/// [`Error::Config`] for another number of resource blocks.
127pub fn fft_len(n_rb: usize) -> Result<usize> {
128 BANDWIDTHS
129 .iter()
130 .find(|(n, _)| *n == n_rb)
131 .map(|(_, f)| *f)
132 .ok_or_else(|| Error::Config(format!("LTE carriers have 6, 15, 25, 50, 75 or 100 resource blocks, got {n_rb}")))
133}
134
135fn check_cell(n_cell_id: u32) -> Result<()> {
136 ensure(n_cell_id < CELL_IDS, || format!("the cell identities are 0 to 503, got {n_cell_id}"))
137}
138
139fn check_rb(n_rb: usize) -> Result<()> {
140 ensure((6..=MAX_RB).contains(&n_rb), || format!("a downlink carrier has 6 to 110 resource blocks, got {n_rb}"))
141}
142
143/// The PSS of $N_{\mathrm{ID}}^{(2)}$: 62 values of unit magnitude, on the subcarriers
144/// $k = n - 31 + 6 N_{\mathrm{RB}}$ (see the module).
145///
146/// # Errors
147/// [`Error::Config`] for $N_{\mathrm{ID}}^{(2)} > 2$.
148pub fn pss(n_id2: u32) -> Result<Vec<C64>> {
149 ensure(n_id2 < 3, || format!("N_ID2 is 0, 1 or 2, got {n_id2}"))?;
150 let mut x = zadoff_chu(PSS_ROOTS[n_id2 as usize], 63)?;
151 x.remove(31);
152 Ok(x)
153}
154
155/// The indices $(m_{0}, m_{1})$ of the SSS of the group $N_{\mathrm{ID}}^{(1)}$ (Table
156/// 6.11.2.1-1).
157///
158/// # Errors
159/// [`Error::Config`] for $N_{\mathrm{ID}}^{(1)} > 167$.
160pub fn sss_indices(n_id1: u32) -> Result<(usize, usize)> {
161 ensure(n_id1 < 168, || format!("N_ID1 is 0 to 167, got {n_id1}"))?;
162 let n = n_id1 as usize;
163 let qp = n / 30;
164 let q = (n + qp * (qp + 1) / 2) / 30;
165 let mp = n + q * (q + 1) / 2;
166 let m0 = mp % 31;
167 Ok((m0, (m0 + mp / 31 + 1) % 31))
168}
169
170/// The SSS of $(N_{\mathrm{ID}}^{(1)}, N_{\mathrm{ID}}^{(2)})$: 62 values $\pm 1$, those of
171/// subframe 0, or of subframe 5 when `second_half` (see the module).
172///
173/// # Errors
174/// [`Error::Config`] for $N_{\mathrm{ID}}^{(1)} > 167$ or $N_{\mathrm{ID}}^{(2)} > 2$.
175pub fn sss(n_id1: u32, n_id2: u32, second_half: bool) -> Result<Vec<f64>> {
176 ensure(n_id2 < 3, || format!("N_ID2 is 0, 1 or 2, got {n_id2}"))?;
177 let (m0, m1) = sss_indices(n_id1)?;
178 let seq = |taps: &[usize]| -> Vec<f64> {
179 lfsr(taps, 5, &[0, 0, 0, 0, 1], 31).iter().map(|&b| 1.0 - 2.0 * f64::from(b)).collect()
180 };
181 let (s, c, z) = (seq(&[0, 2]), seq(&[0, 3]), seq(&[0, 1, 2, 4]));
182 let n2 = n_id2 as usize;
183 let (a, b) = if second_half { (m1, m0) } else { (m0, m1) };
184 let mut d = vec![0.0; SYNC_LENGTH];
185 for n in 0..31 {
186 d[2 * n] = s[(n + a) % 31] * c[(n + n2) % 31];
187 d[2 * n + 1] = s[(n + b) % 31] * c[(n + n2 + 3) % 31] * z[(n + a % 8) % 31];
188 }
189 Ok(d)
190}
191
192/// The CRS of the symbol `l` of the slot `ns` on a carrier of `n_rb` resource blocks:
193/// the $2 N_{\mathrm{RB}}$ values $r(m + 110 - N_{\mathrm{RB}})$, in the order of
194/// [`crs_subcarriers`] (see the module).
195///
196/// # Errors
197/// [`Error::Config`] for a cell identity above 503, a slot above 19, a symbol above 6 or
198/// a number of resource blocks outside 6 to 110.
199pub fn crs(n_cell_id: u32, ns: usize, l: usize, n_rb: usize) -> Result<Vec<C64>> {
200 check_cell(n_cell_id)?;
201 check_rb(n_rb)?;
202 ensure(ns < SLOTS_PER_FRAME && l < SYMBOLS_PER_SLOT, || format!("slot {ns} and symbol {l}: 0 to 19 and 0 to 6"))?;
203 let id = n_cell_id as u64;
204 let c_init = (1024 * (7 * (ns as u64 + 1) + l as u64 + 1) * (2 * id + 1) + 2 * id + 1) % (1 << 31);
205 let start = MAX_RB - n_rb;
206 let c = gold_3gpp(c_init as u32, 4 * MAX_RB);
207 Ok((start..start + 2 * n_rb)
208 .map(|m| C64::new(1.0 - 2.0 * f64::from(c[2 * m]), 1.0 - 2.0 * f64::from(c[2 * m + 1])) * FRAC_1_SQRT_2)
209 .collect())
210}
211
212/// The symbols of a slot that carry the CRS of `port`: 0 and 4 for ports 0 and 1, 1 for
213/// ports 2 and 3.
214///
215/// # Errors
216/// [`Error::Config`] for a port above 3.
217pub fn crs_symbols(port: usize) -> Result<&'static [usize]> {
218 match port {
219 0 | 1 => Ok(&[0, 4]),
220 2 | 3 => Ok(&[1]),
221 _ => Err(Error::Config(format!("the CRS are sent on ports 0 to 3, got {port}"))),
222 }
223}
224
225/// The $2 N_{\mathrm{RB}}$ subcarriers $k = 6m + (v + v_{\mathrm{shift}}) \bmod 6$ of the CRS
226/// of `port` in the symbol `l` of the slot `ns` (see the module).
227///
228/// # Errors
229/// [`Error::Config`] for a cell identity above 503, a port above 3, a symbol without the
230/// CRS of the port ([`crs_symbols`]) or a number of resource blocks outside 6 to 110.
231pub fn crs_subcarriers(n_cell_id: u32, port: usize, ns: usize, l: usize, n_rb: usize) -> Result<Vec<usize>> {
232 check_cell(n_cell_id)?;
233 check_rb(n_rb)?;
234 ensure(crs_symbols(port)?.contains(&l), || format!("port {port} has no CRS in symbol {l}"))?;
235 let v = match (port, l) {
236 (0, 0) | (1, 4) => 0,
237 (0 | 1, _) => 3,
238 (2, _) => 3 * (ns % 2),
239 _ => 3 + 3 * (ns % 2),
240 };
241 let shift = (v + n_cell_id as usize % 6) % 6;
242 Ok((0..2 * n_rb).map(|m| 6 * m + shift).collect())
243}
244
245/// The first of the 62 subcarriers of the PSS and of the SSS, $6 N_{\mathrm{RB}} - 31$.
246pub fn sync_first_subcarrier(n_rb: usize) -> usize {
247 6 * n_rb - 31
248}
249
250/// The 240 elements $(k, l)$ of the PBCH in the first four symbols of slot 1, in the
251/// order of the mapping (frequency first; see the module).
252///
253/// # Errors
254/// [`Error::Config`] for a cell identity above 503 or a number of resource blocks outside
255/// 6 to 110.
256pub fn pbch_elements(n_cell_id: u32, n_rb: usize) -> Result<Vec<(usize, usize)>> {
257 check_cell(n_cell_id)?;
258 check_rb(n_rb)?;
259 let first = 6 * n_rb - 36;
260 let v_shift = n_cell_id as usize % 3;
261 Ok((0..4)
262 .flat_map(|l| (first..first + CENTRAL_SUBCARRIERS).map(move |k| (k, l)))
263 .filter(|&(k, l)| l >= 2 || k % 3 != v_shift)
264 .collect())
265}
266
267/// The space-frequency block code of transmit diversity on `ports` = 2 or 4 antenna ports
268/// (see the module), as an orthogonal code whose channel uses are consecutive resource
269/// elements: [`Ostbc::encode`] gives the precoded symbols of every port,
270/// [`Ostbc::decode_blocks`] combines them.
271///
272/// # Errors
273/// [`Error::Config`] for another number of ports.
274pub fn sfbc(ports: usize) -> Result<Ostbc> {
275 match ports {
276 2 => Ostbc::from_generator("SFBC, 2 ports", 2, 2, 2, |s| {
277 ndarray::array![[s[0], -s[1].conj()], [s[1], s[0].conj()]]
278 }),
279 4 => {
280 let z = C64::new(0.0, 0.0);
281 Ostbc::from_generator("SFBC-FSTD, 4 ports", 4, 4, 4, move |s| {
282 ndarray::array![
283 [s[0], z, -s[1].conj(), z],
284 [s[1], z, s[0].conj(), z],
285 [z, s[2], z, -s[3].conj()],
286 [z, s[3], z, s[2].conj()]
287 ]
288 })
289 }
290 _ => Err(Error::Config(format!("transmit diversity uses 2 or 4 ports, got {ports}"))),
291 }
292}
293
294/// A downlink carrier: the grid of $K = 12 N_{\mathrm{RB}}$ subcarriers and its OFDM
295/// modulator at $N_{\mathrm{FFT}} \cdot 15$ kHz, with the empty DC subcarrier and the
296/// normal prefixes of every symbol of the subframe (see the module).
297#[derive(Clone, Copy, Debug, PartialEq, Eq)]
298pub struct Carrier {
299 /// Resource blocks $N_{\mathrm{RB}}$.
300 pub n_rb: usize,
301 /// FFT size $N_{\mathrm{FFT}}$ (a multiple of 128, above $K$).
302 pub n_fft: usize,
303 /// The position in the subframe (0 to 13) of the first OFDM symbol of the grid.
304 pub first_symbol: usize,
305}
306
307impl Carrier {
308 /// The carrier of `n_rb` resource blocks and an FFT of `n_fft`, its grid starting
309 /// with the first symbol of a subframe.
310 ///
311 /// # Errors
312 /// [`Error::Config`] for a number of resource blocks outside 6 to 110 or an FFT size
313 /// that is not a multiple of 128 above $K + 1$.
314 pub fn new(n_rb: usize, n_fft: usize) -> Result<Carrier> {
315 let c = Carrier { n_rb, n_fft, first_symbol: 0 };
316 c.nr()?;
317 Ok(c)
318 }
319
320 /// Subcarriers $K$ of the grid.
321 pub fn n_subcarriers(&self) -> usize {
322 SUBCARRIERS_PER_RB * self.n_rb
323 }
324
325 /// The carrier of NR of the same numerology whose grid holds the $K$ subcarriers, the
326 /// DC one and an unused one below: $K + 2$ subcarriers, the LTE subcarrier $k$ at
327 /// $k + 1$ below the DC and $k + 2$ above.
328 fn nr(&self) -> Result<nr::Carrier> {
329 check_rb(self.n_rb)?;
330 ensure(self.first_symbol < 2 * SYMBOLS_PER_SLOT, || {
331 format!("the first symbol is 0 to 13 in the subframe, got {}", self.first_symbol)
332 })?;
333 let c = nr::Carrier {
334 numerology: nr::Numerology::new(0, false)?,
335 n_subcarriers: self.n_subcarriers() + 2,
336 n_fft: self.n_fft,
337 first_symbol: self.first_symbol,
338 };
339 c.check()?;
340 Ok(c)
341 }
342
343 /// The position in the grid of [`Carrier::nr`] of the subcarrier `k`.
344 fn position(&self, k: usize) -> usize {
345 if k < self.n_subcarriers() / 2 { k + 1 } else { k + 2 }
346 }
347
348 /// OFDM-modulate a grid (`grid[l * K + k]` for subcarrier `k` of symbol `l`), every
349 /// symbol with the prefix of its position in the subframe.
350 ///
351 /// # Errors
352 /// The errors of [`Carrier::new`]; [`Error::Shape`] for a grid that is not a whole
353 /// number of symbols.
354 pub fn modulate(&self, grid: &[C64]) -> Result<Vec<C64>> {
355 let c = self.nr()?;
356 let k = self.n_subcarriers();
357 ensure_shape(grid.len() % k == 0, || format!("{} values is not a whole number of symbols of {k}", grid.len()))?;
358 let mut wide = vec![C64::new(0.0, 0.0); grid.len() / k * (k + 2)];
359 for (sym, row) in grid.chunks_exact(k).zip(wide.chunks_exact_mut(k + 2)) {
360 for (i, v) in sym.iter().enumerate() {
361 row[self.position(i)] = *v;
362 }
363 }
364 c.modulate(&wide)
365 }
366
367 /// The grid of the samples of whole OFDM symbols, their prefixes removed: the
368 /// inverse of [`Carrier::modulate`].
369 ///
370 /// # Errors
371 /// The errors of [`Carrier::new`]; [`Error::Shape`] if the samples do not end with a
372 /// whole symbol.
373 pub fn demodulate(&self, samples: &[C64]) -> Result<Vec<C64>> {
374 let wide = self.nr()?.demodulate(samples)?;
375 let k = self.n_subcarriers();
376 Ok(wide.chunks_exact(k + 2).flat_map(|row| (0..k).map(move |i| row[self.position(i)])).collect())
377 }
378
379 /// The prefix of the symbol `l` of the subframe (0 to 13), in samples.
380 pub fn prefix(&self, l: usize) -> usize {
381 // 160 then 144 samples at N_FFT = 2048 (Table 6.12-1)
382 if l % SYMBOLS_PER_SLOT == 0 { 160 * self.n_fft / 2048 } else { 144 * self.n_fft / 2048 }
383 }
384
385 /// Samples of a slot, $15360 N_{\mathrm{FFT}}/2048$ (0.5 ms).
386 pub fn slot_len(&self) -> usize {
387 15_360 * self.n_fft / 2048
388 }
389
390 /// The sampling rate $N_{\mathrm{FFT}} \cdot 15$ kHz (Hz).
391 pub fn sample_rate(&self) -> f64 {
392 self.n_fft as f64 * 15e3
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399 use comnum_math::fft;
400
401 #[test]
402 fn the_pss_are_the_punctured_zadoff_chu_sequences() {
403 for n2 in 0..3 {
404 let d = pss(n2).unwrap();
405 assert!(d.iter().all(|v| (v.norm() - 1.0).abs() < 1e-12));
406 // the elements 31.. are those of the sequence after the punctured one
407 let u = PSS_ROOTS[n2 as usize] as f64;
408 let n = 40.0;
409 let want = C64::from_polar(1.0, -std::f64::consts::PI * u * (n + 1.0) * (n + 2.0) / 63.0);
410 assert!((d[40] - want).norm() < 1e-9);
411 }
412 // the PSS of the three identities are nearly orthogonal
413 let (a, b) = (pss(0).unwrap(), pss(1).unwrap());
414 let r: C64 = a.iter().zip(&b).map(|(x, y)| x * y.conj()).sum();
415 assert!(r.norm() / 62.0 < 0.2, "{}", r.norm() / 62.0);
416 assert!(pss(3).is_err());
417 }
418
419 /// Table 6.11.2.1-1 of TS 36.211 V18.0.2, the 168 rows $(m_{0}, m_{1})$ verbatim.
420 const TABLE_6_11_2_1_1: [(usize, usize); 168] = [
421 (0, 1),
422 (1, 2),
423 (2, 3),
424 (3, 4),
425 (4, 5),
426 (5, 6),
427 (6, 7),
428 (7, 8),
429 (8, 9),
430 (9, 10),
431 (10, 11),
432 (11, 12),
433 (12, 13),
434 (13, 14),
435 (14, 15),
436 (15, 16),
437 (16, 17),
438 (17, 18),
439 (18, 19),
440 (19, 20),
441 (20, 21),
442 (21, 22),
443 (22, 23),
444 (23, 24),
445 (24, 25),
446 (25, 26),
447 (26, 27),
448 (27, 28),
449 (28, 29),
450 (29, 30),
451 (0, 2),
452 (1, 3),
453 (2, 4),
454 (3, 5),
455 (4, 6),
456 (5, 7),
457 (6, 8),
458 (7, 9),
459 (8, 10),
460 (9, 11),
461 (10, 12),
462 (11, 13),
463 (12, 14),
464 (13, 15),
465 (14, 16),
466 (15, 17),
467 (16, 18),
468 (17, 19),
469 (18, 20),
470 (19, 21),
471 (20, 22),
472 (21, 23),
473 (22, 24),
474 (23, 25),
475 (24, 26),
476 (25, 27),
477 (26, 28),
478 (27, 29),
479 (28, 30),
480 (0, 3),
481 (1, 4),
482 (2, 5),
483 (3, 6),
484 (4, 7),
485 (5, 8),
486 (6, 9),
487 (7, 10),
488 (8, 11),
489 (9, 12),
490 (10, 13),
491 (11, 14),
492 (12, 15),
493 (13, 16),
494 (14, 17),
495 (15, 18),
496 (16, 19),
497 (17, 20),
498 (18, 21),
499 (19, 22),
500 (20, 23),
501 (21, 24),
502 (22, 25),
503 (23, 26),
504 (24, 27),
505 (25, 28),
506 (26, 29),
507 (27, 30),
508 (0, 4),
509 (1, 5),
510 (2, 6),
511 (3, 7),
512 (4, 8),
513 (5, 9),
514 (6, 10),
515 (7, 11),
516 (8, 12),
517 (9, 13),
518 (10, 14),
519 (11, 15),
520 (12, 16),
521 (13, 17),
522 (14, 18),
523 (15, 19),
524 (16, 20),
525 (17, 21),
526 (18, 22),
527 (19, 23),
528 (20, 24),
529 (21, 25),
530 (22, 26),
531 (23, 27),
532 (24, 28),
533 (25, 29),
534 (26, 30),
535 (0, 5),
536 (1, 6),
537 (2, 7),
538 (3, 8),
539 (4, 9),
540 (5, 10),
541 (6, 11),
542 (7, 12),
543 (8, 13),
544 (9, 14),
545 (10, 15),
546 (11, 16),
547 (12, 17),
548 (13, 18),
549 (14, 19),
550 (15, 20),
551 (16, 21),
552 (17, 22),
553 (18, 23),
554 (19, 24),
555 (20, 25),
556 (21, 26),
557 (22, 27),
558 (23, 28),
559 (24, 29),
560 (25, 30),
561 (0, 6),
562 (1, 7),
563 (2, 8),
564 (3, 9),
565 (4, 10),
566 (5, 11),
567 (6, 12),
568 (7, 13),
569 (8, 14),
570 (9, 15),
571 (10, 16),
572 (11, 17),
573 (12, 18),
574 (13, 19),
575 (14, 20),
576 (15, 21),
577 (16, 22),
578 (17, 23),
579 (18, 24),
580 (19, 25),
581 (20, 26),
582 (21, 27),
583 (22, 28),
584 (23, 29),
585 (24, 30),
586 (0, 7),
587 (1, 8),
588 (2, 9),
589 ];
590
591 #[test]
592 fn the_sss_indices_are_table_6_11_2_1_1() {
593 for (n1, m) in TABLE_6_11_2_1_1.iter().enumerate() {
594 assert_eq!(sss_indices(n1 as u32).unwrap(), *m, "N_ID1 = {n1}");
595 }
596 }
597
598 #[test]
599 fn the_sss_tell_the_group_and_the_half_frame() {
600 let a = sss(57, 2, false).unwrap();
601 let b = sss(57, 2, true).unwrap();
602 assert!(a.iter().all(|v| v.abs() == 1.0));
603 let r: f64 = a.iter().zip(&b).map(|(x, y)| x * y).sum();
604 assert!(r.abs() < 20.0, "{r}");
605 let mut best = (0.0, 0);
606 for n1 in 0..168 {
607 let c: f64 = sss(n1, 2, false).unwrap().iter().zip(&a).map(|(x, y)| x * y).sum();
608 if c > best.0 {
609 best = (c, n1);
610 }
611 }
612 assert_eq!(best, (62.0, 57));
613 assert!(sss(168, 0, false).is_err() && sss(0, 3, false).is_err());
614 }
615
616 #[test]
617 fn the_crs_and_the_pbch_share_the_central_blocks_without_overlap() {
618 let (id, n_rb) = (301, 25);
619 let r = crs(id, 1, 0, n_rb).unwrap();
620 assert_eq!(r.len(), 50);
621 // the values of a narrower carrier are the central ones of a wider one
622 let wide = crs(id, 1, 0, 100).unwrap();
623 assert_eq!(r[..], wide[75..125]);
624 let k0 = crs_subcarriers(id, 0, 1, 0, n_rb).unwrap();
625 let k1 = crs_subcarriers(id, 1, 1, 0, n_rb).unwrap();
626 assert_eq!((k0[0], k1[0]), (1, 4)); // v_shift = 301 mod 6 = 1
627 let pbch = pbch_elements(id, n_rb).unwrap();
628 assert_eq!(pbch.len(), PBCH_ELEMENTS);
629 for (port, l) in [(0, 0), (1, 0), (2, 1), (3, 1)] {
630 let k = crs_subcarriers(id, port, 1, l, n_rb).unwrap();
631 assert!(pbch.iter().all(|e| !(e.1 == l && k.contains(&e.0))));
632 }
633 assert!(crs_subcarriers(id, 2, 1, 0, n_rb).is_err() && crs_symbols(4).is_err());
634 assert!(crs(504, 0, 0, 6).is_err() && crs(0, 20, 0, 6).is_err() && crs(0, 0, 0, 5).is_err());
635 }
636
637 #[test]
638 fn the_carrier_leaves_the_dc_subcarrier_empty() {
639 let c = Carrier::new(6, 128).unwrap();
640 let k = c.n_subcarriers();
641 // one symbol (the first of a subframe: prefix 10) with only the subcarrier K/2
642 let mut grid = vec![C64::new(0.0, 0.0); k];
643 grid[k / 2] = C64::new(1.0, 0.0);
644 let x = c.modulate(&grid).unwrap();
645 assert_eq!(x.len(), 128 + 10);
646 let spectrum = fft::fft(&x[10..]);
647 let peak = (0..128).max_by(|a, b| spectrum[*a].norm().total_cmp(&spectrum[*b].norm())).unwrap();
648 assert_eq!(peak, 1); // +15 kHz, not the DC bin
649 // a slot round trip
650 let grid: Vec<C64> = (0..7 * k).map(|i| C64::new((i % 5) as f64, (i % 3) as f64)).collect();
651 let x = c.modulate(&grid).unwrap();
652 assert_eq!(x.len(), c.slot_len());
653 let back = c.demodulate(&x).unwrap();
654 assert!(back.iter().zip(&grid).all(|(a, b)| (a - b).norm() < 1e-9));
655 assert!(Carrier::new(100, 1024).is_err() && Carrier::new(5, 128).is_err());
656 assert!(fft_len(25).unwrap() == 512 && fft_len(10).is_err());
657 }
658
659 #[test]
660 fn transmit_diversity_is_undone_by_the_orthogonal_decoder() {
661 for ports in [2, 4] {
662 let code = sfbc(ports).unwrap();
663 let s: Vec<C64> = (0..8).map(|i| C64::new((i as f64).cos(), (i as f64).sin())).collect();
664 let x = code.encode(&s).unwrap();
665 // unit total power on every element
666 for t in 0..x.ncols() {
667 let p: f64 = x.column(t).iter().map(C64::norm_sqr).sum();
668 assert!((p - 1.0).abs() < 1e-12, "{p}");
669 }
670 let h = ndarray::Array2::from_shape_fn((1, ports), |(_, a)| C64::from_polar(1.0 + a as f64, a as f64));
671 let y = h.dot(&x);
672 let est = code.decode(&h.view(), &y.view(), 1e-3).unwrap();
673 assert!(est.symbols.iter().zip(&s).all(|(a, b)| (a - b).norm() < 1e-9));
674 }
675 // ports 0 and 1 of the first pair: [x0, -x1*] then [x1, x0*], over sqrt 2
676 let x = sfbc(2).unwrap().encode(&[C64::new(1.0, 0.0), C64::new(0.0, 1.0)]).unwrap();
677 assert!((x[[1, 0]] - C64::new(0.0, 1.0) * FRAC_1_SQRT_2).norm() < 1e-12);
678 assert!(sfbc(3).is_err());
679 }
680}