Skip to main content

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}