use comnum_core::{Error, Result, ensure, ensure_shape};
use comnum_math::C64;
use std::sync::Arc;
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum PilotPattern {
Block {
period: usize,
},
Comb {
spacing: usize,
},
Lattice {
freq: usize,
time: usize,
},
Explicit(Arc<PilotTable>),
None,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PilotTable {
n_subcarriers: usize,
n_symbols: usize,
values: Vec<Option<C64>>,
}
impl PilotTable {
pub fn new(
n_subcarriers: usize,
n_symbols: usize,
pilots: impl IntoIterator<Item = ((usize, usize), C64)>,
) -> Result<PilotTable> {
ensure(n_subcarriers > 0 && n_symbols > 0, || {
format!("a pilot table needs at least one RE, got {n_subcarriers} × {n_symbols}")
})?;
let mut values = vec![None; n_subcarriers * n_symbols];
for ((k, l), v) in pilots {
ensure(k < n_subcarriers && l < n_symbols, || {
format!("pilot ({k}, {l}) outside the grid of {n_subcarriers} × {n_symbols}")
})?;
ensure(v.norm() > 0.0 && v.re.is_finite() && v.im.is_finite(), || {
format!("the pilot ({k}, {l}) must be finite and non-zero, got {v}")
})?;
values[l * n_subcarriers + k] = Some(v);
}
Ok(PilotTable { n_subcarriers, n_symbols, values })
}
pub fn n_subcarriers(&self) -> usize {
self.n_subcarriers
}
pub fn n_symbols(&self) -> usize {
self.n_symbols
}
pub fn get(&self, k: usize, l: usize) -> Option<C64> {
if k < self.n_subcarriers && l < self.n_symbols { self.values[l * self.n_subcarriers + k] } else { None }
}
pub fn len(&self) -> usize {
self.values.iter().filter(|v| v.is_some()).count()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl PilotPattern {
pub fn block(period: usize) -> Result<PilotPattern> {
Ok(PilotPattern::Block { period: positive(period, "period")? })
}
pub fn comb(spacing: usize) -> Result<PilotPattern> {
Ok(PilotPattern::Comb { spacing: positive(spacing, "spacing")? })
}
pub fn lattice(freq: usize, time: usize) -> Result<PilotPattern> {
Ok(PilotPattern::Lattice { freq: positive(freq, "freq")?, time: positive(time, "time")? })
}
}
fn positive(v: usize, what: &str) -> Result<usize> {
if v >= 1 { Ok(v) } else { Err(Error::Config(format!("the pilot {what} must be at least 1, got {v}"))) }
}
#[derive(Clone, Debug, PartialEq)]
pub struct ResourceGrid {
pub n_subcarriers: usize,
pub n_symbols: usize,
pub pattern: PilotPattern,
pub seed: u64,
}
impl ResourceGrid {
pub fn check(&self) -> Result<()> {
let (k, l) = (self.n_subcarriers, self.n_symbols);
ensure(k >= 1 && l >= 1, || format!("a resource grid needs at least one RE, got {k} × {l}"))?;
match &self.pattern {
PilotPattern::Block { period } => positive(*period, "period").map(drop),
PilotPattern::Comb { spacing } => positive(*spacing, "spacing").map(drop),
PilotPattern::Lattice { freq, time } => {
positive(*freq, "freq").and_then(|_| positive(*time, "time")).map(drop)
}
PilotPattern::Explicit(t) => ensure(t.n_subcarriers == k && t.n_symbols == l, || {
format!("a pilot table of {} × {} for a grid of {k} × {l}", t.n_subcarriers, t.n_symbols)
}),
PilotPattern::None => Ok(()),
}
}
pub fn from_table(table: PilotTable) -> ResourceGrid {
ResourceGrid {
n_subcarriers: table.n_subcarriers,
n_symbols: table.n_symbols,
pattern: PilotPattern::Explicit(Arc::new(table)),
seed: 0,
}
}
pub fn is_pilot(&self, k: usize, l: usize) -> bool {
match &self.pattern {
PilotPattern::Block { period } => l % (*period).max(1) == 0,
PilotPattern::Comb { spacing } => k % (*spacing).max(1) == 0,
PilotPattern::Lattice { freq, time } => k % (*freq).max(1) == 0 && l % (*time).max(1) == 0,
PilotPattern::Explicit(t) => t.get(k, l).is_some(),
PilotPattern::None => false,
}
}
pub fn port_pilots(&self, l: usize, port: usize, n_ports: usize) -> Vec<usize> {
(0..self.n_subcarriers)
.filter(|&k| self.is_pilot(k, l))
.enumerate()
.filter(|(j, _)| j % n_ports.max(1) == port)
.map(|(_, k)| k)
.collect()
}
pub fn map_port(&self, data: &[C64], port: usize, n_ports: usize) -> Result<Vec<C64>> {
let mut g = self.map(data)?;
if n_ports > 1 {
for l in 0..self.n_symbols {
let mine = self.port_pilots(l, port, n_ports);
for k in (0..self.n_subcarriers).filter(|&k| self.is_pilot(k, l) && !mine.contains(&k)) {
g[l * self.n_subcarriers + k] = C64::new(0.0, 0.0);
}
}
}
Ok(g)
}
pub fn pilot(&self, k: usize, l: usize) -> C64 {
if let PilotPattern::Explicit(t) = &self.pattern {
if let Some(v) = t.get(k, l) {
return v;
}
}
comnum_mapping::source::qpsk(&mut comnum_core::rng::member(self.seed, (l * self.n_subcarriers + k) as u64))
}
fn res(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
(0..self.n_symbols).flat_map(move |l| (0..self.n_subcarriers).map(move |k| (k, l)))
}
pub fn pilot_positions(&self) -> Vec<(usize, usize)> {
self.res().filter(|&(k, l)| self.is_pilot(k, l)).collect()
}
pub fn data_indices(&self) -> Vec<usize> {
self.res().filter(|&(k, l)| !self.is_pilot(k, l)).map(|(k, l)| l * self.n_subcarriers + k).collect()
}
pub fn pilot_symbols(&self) -> Vec<usize> {
(0..self.n_symbols).filter(|&l| (0..self.n_subcarriers).any(|k| self.is_pilot(k, l))).collect()
}
pub fn n_data(&self) -> usize {
self.res().filter(|&(k, l)| !self.is_pilot(k, l)).count()
}
pub fn map(&self, data: &[C64]) -> Result<Vec<C64>> {
self.check()?;
let n_data = self.n_data();
ensure_shape(data.len() == n_data, || format!("{} data symbols for a grid of {n_data} data REs", data.len()))?;
let mut it = data.iter();
Ok(self
.res()
.map(
|(k, l)| if self.is_pilot(k, l) { self.pilot(k, l) } else { *it.next().unwrap_or(&C64::new(0.0, 0.0)) },
)
.collect())
}
pub fn extract<T: Copy>(&self, grid: &[T]) -> Result<Vec<T>> {
let n = self.n_subcarriers * self.n_symbols;
ensure_shape(grid.len() == n, || format!("{} REs for a grid of {n}", grid.len()))?;
Ok(self.data_indices().into_iter().map(|i| grid[i]).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_data_indices_are_the_former_walks() {
let table = scattered(49, 8);
let patterns = [
PilotPattern::Block { period: 3 },
PilotPattern::Comb { spacing: 4 },
PilotPattern::Lattice { freq: 3, time: 2 },
PilotPattern::Explicit(Arc::new(table)),
PilotPattern::None,
];
for pattern in patterns {
let g = ResourceGrid { n_subcarriers: 49, n_symbols: 8, pattern, seed: 5 };
let former: Vec<usize> = (0..8)
.flat_map(|l| (0..49).map(move |k| (k, l)))
.filter(|&(k, l)| !g.is_pilot(k, l))
.map(|(k, l)| l * 49 + k)
.collect();
assert_eq!(g.data_indices(), former);
assert_eq!(g.n_data(), 49 * 8 - g.pilot_positions().len());
let data: Vec<C64> = (0..g.n_data()).map(|i| C64::new(i as f64, -1.0)).collect();
let mut it = data.iter();
let mut old = Vec::new();
for l in 0..8 {
for k in 0..49 {
old.push(if g.is_pilot(k, l) { g.pilot(k, l) } else { *it.next().unwrap() });
}
}
assert_eq!(g.map(&data).unwrap(), old);
}
let bad = ResourceGrid {
n_subcarriers: 12,
n_symbols: 7,
pattern: PilotPattern::Lattice { freq: 3, time: 0 },
seed: 0,
};
assert!(bad.check().is_err());
let wrong_size = ResourceGrid { n_subcarriers: 48, ..ResourceGrid::from_table(scattered(49, 8)) };
assert!(wrong_size.check().is_err());
}
#[test]
fn patterns_place_their_pilots() {
let block =
ResourceGrid { n_subcarriers: 8, n_symbols: 6, pattern: PilotPattern::Block { period: 3 }, seed: 0 };
assert_eq!(block.pilot_symbols(), vec![0, 3]);
assert_eq!(block.n_data(), 8 * 4);
let comb = ResourceGrid { pattern: PilotPattern::Comb { spacing: 4 }, ..block.clone() };
assert_eq!(comb.pilot_positions().len(), 2 * 6);
assert!((comb.pilot(4, 2).norm() - 1.0).abs() < 1e-12);
assert!(block.map(&[C64::new(0.0, 0.0); 3]).is_err());
}
fn scattered(k_n: usize, l_n: usize) -> PilotTable {
let pilots = (0..l_n).flat_map(move |l| {
(0..k_n).filter(move |k| k % 12 == 3 * (l % 4)).map(move |k| {
let sign = if (k / 12) % 2 == 0 { 1.0 } else { -1.0 };
((k, l), C64::new(sign * 4.0 / 3.0, 0.0))
})
});
PilotTable::new(k_n, l_n, pilots).unwrap()
}
#[test]
fn explicit_tables_carry_their_positions_and_values() {
let g = ResourceGrid::from_table(scattered(49, 8));
assert_eq!(g.pilot_positions().len(), 34);
assert_eq!(g.pilot_symbols(), (0..8).collect::<Vec<_>>());
assert!(g.is_pilot(15, 1) && !g.is_pilot(15, 0));
assert_eq!(g.pilot(12, 0), C64::new(-4.0 / 3.0, 0.0));
let data: Vec<C64> = (0..g.n_data()).map(|i| C64::new(i as f64, 1.0)).collect();
let grid = g.map(&data).unwrap();
assert_eq!(grid[12], C64::new(-4.0 / 3.0, 0.0));
assert_eq!(g.extract(&grid).unwrap(), data);
assert!(g.extract(&grid[1..]).is_err());
let one = C64::new(1.0, 0.0);
assert!(PilotTable::new(4, 2, [((4, 0), one)]).is_err());
assert!(PilotTable::new(4, 2, [((0, 2), one)]).is_err());
assert!(PilotTable::new(4, 2, [((0, 0), C64::new(0.0, 0.0))]).is_err());
assert!(PilotTable::new(0, 2, []).is_err());
}
}