comnum_math/sequences.rs
1//! Binary pseudo-random sequences (linear-feedback shift registers, the length-31 Gold
2//! sequence of 3GPP) and the Zadoff–Chu sequences.
3//!
4//! # Model
5//!
6//! A Fibonacci register of order $r$ produces $x(i + r) = \sum_{t \in T} x(i + t) \bmod 2$
7//! from its first $r$ bits ([`lfsr`]): every m-sequence and Gold sequence of the
8//! standards is one or a sum of two. The pseudo-random sequence of 3GPP (TS 38.211,
9//! Sec. 5.2.1; TS 36.211, Sec. 7.2) is
10//!
11//! $$
12//! c(n) = \left(x_{1}(n + N_{c}) + x_{2}(n + N_{c})\right) \bmod 2, \qquad N_{c} = 1600,
13//! $$
14//!
15//! with $x_{1}(n+31) = (x_{1}(n+3) + x_{1}(n)) \bmod 2$, $x_{1}(0) = 1$,
16//! $x_{1}(1..30) = 0$, and $x_{2}(n+31) = (x_{2}(n+3) + x_{2}(n+2) + x_{2}(n+1) +
17//! x_{2}(n)) \bmod 2$ initialised by $c_{\mathrm{init}} = \sum_{i} x_{2}(i) 2^{i}$
18//! ([`gold_3gpp`]): the scrambling of the coded bits and the sequences of the reference
19//! signals.
20//!
21//! The Zadoff–Chu sequence of root $u$ and odd length $N_{\mathrm{ZC}}$ ([`zadoff_chu`]),
22//!
23//! $$
24//! x_{u}(n) = e^{-j\pi u n (n + 1) / N_{\mathrm{ZC}}}, \qquad 0 \le n < N_{\mathrm{ZC}},
25//! \quad \gcd(u, N_{\mathrm{ZC}}) = 1,
26//! $$
27//!
28//! has a constant amplitude and a zero periodic autocorrelation at every nonzero lag
29//! (a CAZAC sequence; Chu 1972), and two roots $u_{1} \ne u_{2}$ with $u_{1} - u_{2}$
30//! prime to a prime $N_{\mathrm{ZC}}$ cross-correlate with the constant magnitude
31//! $1/\sqrt{N_{\mathrm{ZC}}}$: the primary synchronisation signal of LTE (TS 36.211,
32//! Sec. 6.11.1.1, $N_{\mathrm{ZC}} = 63$, its middle element punctured) and the
33//! preambles of the random access.
34//!
35//! # Examples
36//! ```
37//! use comnum_math::sequences::{gold_3gpp, lfsr};
38//! // x³ + x + 1: the m-sequence of period 7
39//! assert_eq!(lfsr(&[0, 1], 3, &[1, 0, 0], 10), vec![1, 0, 0, 1, 0, 1, 1, 1, 0, 0]);
40//! assert_eq!(gold_3gpp(0, 8).len(), 8);
41//! let x = comnum_math::sequences::zadoff_chu(25, 63)?;
42//! assert!(x.iter().all(|v| (v.norm() - 1.0).abs() < 1e-12));
43//! # Ok::<(), comnum_core::Error>(())
44//! ```
45//!
46//! # References
47//!
48//! 1. 3GPP TS 38.211, *NR; Physical channels and modulation*, Sec. 5.2.1.
49//! 2. 3GPP TS 36.211 V18.0.2, *E-UTRA; Physical channels and modulation*, Sec. 6.11.1.1, 7.2.
50//! 3. D. Chu, "Polyphase codes with good periodic correlation properties," *IEEE Trans.
51//! Inf. Theory* 18(4), 531–532, 1972.
52
53use crate::C64;
54use comnum_core::{Result, ensure};
55
56/// The sequence of a linear-feedback shift register of order `order`: the first
57/// `order` bits are `init` (zero-padded), then $x_{i + \mathrm{order}} = \sum_{t} x_{i + t}
58/// \bmod 2$ over the `taps`, up to `len` bits in all (the Fibonacci form of every
59/// m-sequence, Gold sequence and PRBS of the standards).
60///
61/// # Examples
62/// ```
63/// use comnum_math::sequences::lfsr;
64/// // x³ + x + 1: the m-sequence of period 7
65/// let x = lfsr(&[0, 1], 3, &[1, 0, 0], 10);
66/// assert_eq!(x, vec![1, 0, 0, 1, 0, 1, 1, 1, 0, 0]);
67/// ```
68pub fn lfsr(taps: &[usize], order: usize, init: &[u8], len: usize) -> Vec<u8> {
69 let mut x = vec![0u8; len.max(order)];
70 for (v, &b) in x.iter_mut().zip(init.iter().take(order)) {
71 *v = b & 1;
72 }
73 for i in 0..len.saturating_sub(order) {
74 x[i + order] = taps.iter().fold(0, |acc, &t| acc ^ x[i + t]);
75 }
76 x.truncate(len);
77 x
78}
79
80/// The first `n` bits of the Gold sequence $c$ of 3GPP with the initial value `c_init`
81/// (31 bits; see the module).
82pub fn gold_3gpp(c_init: u32, n: usize) -> Vec<u8> {
83 const NC: usize = 1600;
84 let len = n + NC + 31;
85 let init2: Vec<u8> = (0..31).map(|i| ((c_init >> i) & 1) as u8).collect();
86 let x1 = lfsr(&[0, 3], 31, &[1], len);
87 let x2 = lfsr(&[0, 1, 2, 3], 31, &init2, len);
88 (0..n).map(|i| x1[i + NC] ^ x2[i + NC]).collect()
89}
90
91/// The Zadoff–Chu sequence $x_{u}(n) = e^{-j\pi u n (n + 1) / N_{\mathrm{ZC}}}$ of root `u`
92/// and odd length `n_zc` (see the module).
93///
94/// # Errors
95/// [`comnum_core::Error::Config`] for an even or zero length, or a root that is zero
96/// modulo $N_{\mathrm{ZC}}$ or not prime to it.
97///
98/// # Examples
99/// ```
100/// use comnum_math::sequences::zadoff_chu;
101/// let x = zadoff_chu(29, 63)?;
102/// // zero periodic autocorrelation at every nonzero lag
103/// let r: comnum_math::C64 = (0..63).map(|n| x[n] * x[(n + 5) % 63].conj()).sum();
104/// assert!(r.norm() < 1e-9);
105/// # Ok::<(), comnum_core::Error>(())
106/// ```
107pub fn zadoff_chu(u: u32, n_zc: u32) -> Result<Vec<C64>> {
108 ensure(n_zc % 2 == 1, || format!("a Zadoff–Chu sequence has an odd length, got {n_zc}"))?;
109 let gcd = |mut a: u64, mut b: u64| {
110 while b != 0 {
111 (a, b) = (b, a % b);
112 }
113 a
114 };
115 ensure(u % n_zc != 0 && gcd(u64::from(u), u64::from(n_zc)) == 1, || {
116 format!("the root {u} must be nonzero and prime to the length {n_zc}")
117 })?;
118 let n_zc = u64::from(n_zc);
119 // the phase u n (n + 1) / N reduced modulo 2N exactly, in integers
120 Ok((0..n_zc)
121 .map(|n| {
122 let k = (u64::from(u) * n % (2 * n_zc)) * (n + 1) % (2 * n_zc);
123 C64::from_polar(1.0, -std::f64::consts::PI * k as f64 / n_zc as f64)
124 })
125 .collect())
126}