use super::nr;
use comnum_core::{Error, Result, ensure, ensure_shape};
use comnum_math::C64;
use comnum_math::sequences::{gold_3gpp, lfsr, zadoff_chu};
use comnum_mimo::stbc::Ostbc;
use std::f64::consts::FRAC_1_SQRT_2;
pub const CELL_IDS: u32 = 504;
pub const MAX_RB: usize = 110;
pub const SUBCARRIERS_PER_RB: usize = nr::SUBCARRIERS_PER_RB;
pub const SYMBOLS_PER_SLOT: usize = 7;
pub const SLOTS_PER_FRAME: usize = 20;
pub const SYNC_LENGTH: usize = 62;
pub const PSS_SYMBOL: usize = 6;
pub const SSS_SYMBOL: usize = 5;
pub const CENTRAL_SUBCARRIERS: usize = 72;
pub const PBCH_ELEMENTS: usize = 240;
const PSS_ROOTS: [u32; 3] = [25, 29, 34];
pub const BANDWIDTHS: [(usize, usize); 6] = [(6, 128), (15, 256), (25, 512), (50, 1024), (75, 1536), (100, 2048)];
pub fn fft_len(n_rb: usize) -> Result<usize> {
BANDWIDTHS
.iter()
.find(|(n, _)| *n == n_rb)
.map(|(_, f)| *f)
.ok_or_else(|| Error::Config(format!("LTE carriers have 6, 15, 25, 50, 75 or 100 resource blocks, got {n_rb}")))
}
fn check_cell(n_cell_id: u32) -> Result<()> {
ensure(n_cell_id < CELL_IDS, || format!("the cell identities are 0 to 503, got {n_cell_id}"))
}
fn check_rb(n_rb: usize) -> Result<()> {
ensure((6..=MAX_RB).contains(&n_rb), || format!("a downlink carrier has 6 to 110 resource blocks, got {n_rb}"))
}
pub fn pss(n_id2: u32) -> Result<Vec<C64>> {
ensure(n_id2 < 3, || format!("N_ID2 is 0, 1 or 2, got {n_id2}"))?;
let mut x = zadoff_chu(PSS_ROOTS[n_id2 as usize], 63)?;
x.remove(31);
Ok(x)
}
pub fn sss_indices(n_id1: u32) -> Result<(usize, usize)> {
ensure(n_id1 < 168, || format!("N_ID1 is 0 to 167, got {n_id1}"))?;
let n = n_id1 as usize;
let qp = n / 30;
let q = (n + qp * (qp + 1) / 2) / 30;
let mp = n + q * (q + 1) / 2;
let m0 = mp % 31;
Ok((m0, (m0 + mp / 31 + 1) % 31))
}
pub fn sss(n_id1: u32, n_id2: u32, second_half: bool) -> Result<Vec<f64>> {
ensure(n_id2 < 3, || format!("N_ID2 is 0, 1 or 2, got {n_id2}"))?;
let (m0, m1) = sss_indices(n_id1)?;
let seq = |taps: &[usize]| -> Vec<f64> {
lfsr(taps, 5, &[0, 0, 0, 0, 1], 31).iter().map(|&b| 1.0 - 2.0 * f64::from(b)).collect()
};
let (s, c, z) = (seq(&[0, 2]), seq(&[0, 3]), seq(&[0, 1, 2, 4]));
let n2 = n_id2 as usize;
let (a, b) = if second_half { (m1, m0) } else { (m0, m1) };
let mut d = vec![0.0; SYNC_LENGTH];
for n in 0..31 {
d[2 * n] = s[(n + a) % 31] * c[(n + n2) % 31];
d[2 * n + 1] = s[(n + b) % 31] * c[(n + n2 + 3) % 31] * z[(n + a % 8) % 31];
}
Ok(d)
}
pub fn crs(n_cell_id: u32, ns: usize, l: usize, n_rb: usize) -> Result<Vec<C64>> {
check_cell(n_cell_id)?;
check_rb(n_rb)?;
ensure(ns < SLOTS_PER_FRAME && l < SYMBOLS_PER_SLOT, || format!("slot {ns} and symbol {l}: 0 to 19 and 0 to 6"))?;
let id = n_cell_id as u64;
let c_init = (1024 * (7 * (ns as u64 + 1) + l as u64 + 1) * (2 * id + 1) + 2 * id + 1) % (1 << 31);
let start = MAX_RB - n_rb;
let c = gold_3gpp(c_init as u32, 4 * MAX_RB);
Ok((start..start + 2 * n_rb)
.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)
.collect())
}
pub fn crs_symbols(port: usize) -> Result<&'static [usize]> {
match port {
0 | 1 => Ok(&[0, 4]),
2 | 3 => Ok(&[1]),
_ => Err(Error::Config(format!("the CRS are sent on ports 0 to 3, got {port}"))),
}
}
pub fn crs_subcarriers(n_cell_id: u32, port: usize, ns: usize, l: usize, n_rb: usize) -> Result<Vec<usize>> {
check_cell(n_cell_id)?;
check_rb(n_rb)?;
ensure(crs_symbols(port)?.contains(&l), || format!("port {port} has no CRS in symbol {l}"))?;
let v = match (port, l) {
(0, 0) | (1, 4) => 0,
(0 | 1, _) => 3,
(2, _) => 3 * (ns % 2),
_ => 3 + 3 * (ns % 2),
};
let shift = (v + n_cell_id as usize % 6) % 6;
Ok((0..2 * n_rb).map(|m| 6 * m + shift).collect())
}
pub fn sync_first_subcarrier(n_rb: usize) -> usize {
6 * n_rb - 31
}
pub fn pbch_elements(n_cell_id: u32, n_rb: usize) -> Result<Vec<(usize, usize)>> {
check_cell(n_cell_id)?;
check_rb(n_rb)?;
let first = 6 * n_rb - 36;
let v_shift = n_cell_id as usize % 3;
Ok((0..4)
.flat_map(|l| (first..first + CENTRAL_SUBCARRIERS).map(move |k| (k, l)))
.filter(|&(k, l)| l >= 2 || k % 3 != v_shift)
.collect())
}
pub fn sfbc(ports: usize) -> Result<Ostbc> {
match ports {
2 => Ostbc::from_generator("SFBC, 2 ports", 2, 2, 2, |s| {
ndarray::array![[s[0], -s[1].conj()], [s[1], s[0].conj()]]
}),
4 => {
let z = C64::new(0.0, 0.0);
Ostbc::from_generator("SFBC-FSTD, 4 ports", 4, 4, 4, move |s| {
ndarray::array![
[s[0], z, -s[1].conj(), z],
[s[1], z, s[0].conj(), z],
[z, s[2], z, -s[3].conj()],
[z, s[3], z, s[2].conj()]
]
})
}
_ => Err(Error::Config(format!("transmit diversity uses 2 or 4 ports, got {ports}"))),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Carrier {
pub n_rb: usize,
pub n_fft: usize,
pub first_symbol: usize,
}
impl Carrier {
pub fn new(n_rb: usize, n_fft: usize) -> Result<Carrier> {
let c = Carrier { n_rb, n_fft, first_symbol: 0 };
c.nr()?;
Ok(c)
}
pub fn n_subcarriers(&self) -> usize {
SUBCARRIERS_PER_RB * self.n_rb
}
fn nr(&self) -> Result<nr::Carrier> {
check_rb(self.n_rb)?;
ensure(self.first_symbol < 2 * SYMBOLS_PER_SLOT, || {
format!("the first symbol is 0 to 13 in the subframe, got {}", self.first_symbol)
})?;
let c = nr::Carrier {
numerology: nr::Numerology::new(0, false)?,
n_subcarriers: self.n_subcarriers() + 2,
n_fft: self.n_fft,
first_symbol: self.first_symbol,
};
c.check()?;
Ok(c)
}
fn position(&self, k: usize) -> usize {
if k < self.n_subcarriers() / 2 { k + 1 } else { k + 2 }
}
pub fn modulate(&self, grid: &[C64]) -> Result<Vec<C64>> {
let c = self.nr()?;
let k = self.n_subcarriers();
ensure_shape(grid.len() % k == 0, || format!("{} values is not a whole number of symbols of {k}", grid.len()))?;
let mut wide = vec![C64::new(0.0, 0.0); grid.len() / k * (k + 2)];
for (sym, row) in grid.chunks_exact(k).zip(wide.chunks_exact_mut(k + 2)) {
for (i, v) in sym.iter().enumerate() {
row[self.position(i)] = *v;
}
}
c.modulate(&wide)
}
pub fn demodulate(&self, samples: &[C64]) -> Result<Vec<C64>> {
let wide = self.nr()?.demodulate(samples)?;
let k = self.n_subcarriers();
Ok(wide.chunks_exact(k + 2).flat_map(|row| (0..k).map(move |i| row[self.position(i)])).collect())
}
pub fn prefix(&self, l: usize) -> usize {
if l % SYMBOLS_PER_SLOT == 0 { 160 * self.n_fft / 2048 } else { 144 * self.n_fft / 2048 }
}
pub fn slot_len(&self) -> usize {
15_360 * self.n_fft / 2048
}
pub fn sample_rate(&self) -> f64 {
self.n_fft as f64 * 15e3
}
}
#[cfg(test)]
mod tests {
use super::*;
use comnum_math::fft;
#[test]
fn the_pss_are_the_punctured_zadoff_chu_sequences() {
for n2 in 0..3 {
let d = pss(n2).unwrap();
assert!(d.iter().all(|v| (v.norm() - 1.0).abs() < 1e-12));
let u = PSS_ROOTS[n2 as usize] as f64;
let n = 40.0;
let want = C64::from_polar(1.0, -std::f64::consts::PI * u * (n + 1.0) * (n + 2.0) / 63.0);
assert!((d[40] - want).norm() < 1e-9);
}
let (a, b) = (pss(0).unwrap(), pss(1).unwrap());
let r: C64 = a.iter().zip(&b).map(|(x, y)| x * y.conj()).sum();
assert!(r.norm() / 62.0 < 0.2, "{}", r.norm() / 62.0);
assert!(pss(3).is_err());
}
const TABLE_6_11_2_1_1: [(usize, usize); 168] = [
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 5),
(5, 6),
(6, 7),
(7, 8),
(8, 9),
(9, 10),
(10, 11),
(11, 12),
(12, 13),
(13, 14),
(14, 15),
(15, 16),
(16, 17),
(17, 18),
(18, 19),
(19, 20),
(20, 21),
(21, 22),
(22, 23),
(23, 24),
(24, 25),
(25, 26),
(26, 27),
(27, 28),
(28, 29),
(29, 30),
(0, 2),
(1, 3),
(2, 4),
(3, 5),
(4, 6),
(5, 7),
(6, 8),
(7, 9),
(8, 10),
(9, 11),
(10, 12),
(11, 13),
(12, 14),
(13, 15),
(14, 16),
(15, 17),
(16, 18),
(17, 19),
(18, 20),
(19, 21),
(20, 22),
(21, 23),
(22, 24),
(23, 25),
(24, 26),
(25, 27),
(26, 28),
(27, 29),
(28, 30),
(0, 3),
(1, 4),
(2, 5),
(3, 6),
(4, 7),
(5, 8),
(6, 9),
(7, 10),
(8, 11),
(9, 12),
(10, 13),
(11, 14),
(12, 15),
(13, 16),
(14, 17),
(15, 18),
(16, 19),
(17, 20),
(18, 21),
(19, 22),
(20, 23),
(21, 24),
(22, 25),
(23, 26),
(24, 27),
(25, 28),
(26, 29),
(27, 30),
(0, 4),
(1, 5),
(2, 6),
(3, 7),
(4, 8),
(5, 9),
(6, 10),
(7, 11),
(8, 12),
(9, 13),
(10, 14),
(11, 15),
(12, 16),
(13, 17),
(14, 18),
(15, 19),
(16, 20),
(17, 21),
(18, 22),
(19, 23),
(20, 24),
(21, 25),
(22, 26),
(23, 27),
(24, 28),
(25, 29),
(26, 30),
(0, 5),
(1, 6),
(2, 7),
(3, 8),
(4, 9),
(5, 10),
(6, 11),
(7, 12),
(8, 13),
(9, 14),
(10, 15),
(11, 16),
(12, 17),
(13, 18),
(14, 19),
(15, 20),
(16, 21),
(17, 22),
(18, 23),
(19, 24),
(20, 25),
(21, 26),
(22, 27),
(23, 28),
(24, 29),
(25, 30),
(0, 6),
(1, 7),
(2, 8),
(3, 9),
(4, 10),
(5, 11),
(6, 12),
(7, 13),
(8, 14),
(9, 15),
(10, 16),
(11, 17),
(12, 18),
(13, 19),
(14, 20),
(15, 21),
(16, 22),
(17, 23),
(18, 24),
(19, 25),
(20, 26),
(21, 27),
(22, 28),
(23, 29),
(24, 30),
(0, 7),
(1, 8),
(2, 9),
];
#[test]
fn the_sss_indices_are_table_6_11_2_1_1() {
for (n1, m) in TABLE_6_11_2_1_1.iter().enumerate() {
assert_eq!(sss_indices(n1 as u32).unwrap(), *m, "N_ID1 = {n1}");
}
}
#[test]
fn the_sss_tell_the_group_and_the_half_frame() {
let a = sss(57, 2, false).unwrap();
let b = sss(57, 2, true).unwrap();
assert!(a.iter().all(|v| v.abs() == 1.0));
let r: f64 = a.iter().zip(&b).map(|(x, y)| x * y).sum();
assert!(r.abs() < 20.0, "{r}");
let mut best = (0.0, 0);
for n1 in 0..168 {
let c: f64 = sss(n1, 2, false).unwrap().iter().zip(&a).map(|(x, y)| x * y).sum();
if c > best.0 {
best = (c, n1);
}
}
assert_eq!(best, (62.0, 57));
assert!(sss(168, 0, false).is_err() && sss(0, 3, false).is_err());
}
#[test]
fn the_crs_and_the_pbch_share_the_central_blocks_without_overlap() {
let (id, n_rb) = (301, 25);
let r = crs(id, 1, 0, n_rb).unwrap();
assert_eq!(r.len(), 50);
let wide = crs(id, 1, 0, 100).unwrap();
assert_eq!(r[..], wide[75..125]);
let k0 = crs_subcarriers(id, 0, 1, 0, n_rb).unwrap();
let k1 = crs_subcarriers(id, 1, 1, 0, n_rb).unwrap();
assert_eq!((k0[0], k1[0]), (1, 4)); let pbch = pbch_elements(id, n_rb).unwrap();
assert_eq!(pbch.len(), PBCH_ELEMENTS);
for (port, l) in [(0, 0), (1, 0), (2, 1), (3, 1)] {
let k = crs_subcarriers(id, port, 1, l, n_rb).unwrap();
assert!(pbch.iter().all(|e| !(e.1 == l && k.contains(&e.0))));
}
assert!(crs_subcarriers(id, 2, 1, 0, n_rb).is_err() && crs_symbols(4).is_err());
assert!(crs(504, 0, 0, 6).is_err() && crs(0, 20, 0, 6).is_err() && crs(0, 0, 0, 5).is_err());
}
#[test]
fn the_carrier_leaves_the_dc_subcarrier_empty() {
let c = Carrier::new(6, 128).unwrap();
let k = c.n_subcarriers();
let mut grid = vec![C64::new(0.0, 0.0); k];
grid[k / 2] = C64::new(1.0, 0.0);
let x = c.modulate(&grid).unwrap();
assert_eq!(x.len(), 128 + 10);
let spectrum = fft::fft(&x[10..]);
let peak = (0..128).max_by(|a, b| spectrum[*a].norm().total_cmp(&spectrum[*b].norm())).unwrap();
assert_eq!(peak, 1); let grid: Vec<C64> = (0..7 * k).map(|i| C64::new((i % 5) as f64, (i % 3) as f64)).collect();
let x = c.modulate(&grid).unwrap();
assert_eq!(x.len(), c.slot_len());
let back = c.demodulate(&x).unwrap();
assert!(back.iter().zip(&grid).all(|(a, b)| (a - b).norm() < 1e-9));
assert!(Carrier::new(100, 1024).is_err() && Carrier::new(5, 128).is_err());
assert!(fft_len(25).unwrap() == 512 && fft_len(10).is_err());
}
#[test]
fn transmit_diversity_is_undone_by_the_orthogonal_decoder() {
for ports in [2, 4] {
let code = sfbc(ports).unwrap();
let s: Vec<C64> = (0..8).map(|i| C64::new((i as f64).cos(), (i as f64).sin())).collect();
let x = code.encode(&s).unwrap();
for t in 0..x.ncols() {
let p: f64 = x.column(t).iter().map(C64::norm_sqr).sum();
assert!((p - 1.0).abs() < 1e-12, "{p}");
}
let h = ndarray::Array2::from_shape_fn((1, ports), |(_, a)| C64::from_polar(1.0 + a as f64, a as f64));
let y = h.dot(&x);
let est = code.decode(&h.view(), &y.view(), 1e-3).unwrap();
assert!(est.symbols.iter().zip(&s).all(|(a, b)| (a - b).norm() < 1e-9));
}
let x = sfbc(2).unwrap().encode(&[C64::new(1.0, 0.0), C64::new(0.0, 1.0)]).unwrap();
assert!((x[[1, 0]] - C64::new(0.0, 1.0) * FRAC_1_SQRT_2).norm() < 1e-12);
assert!(sfbc(3).is_err());
}
}