1use comnum_core::{Error, Result, ensure, ensure_shape};
39use comnum_math::C64;
40use std::sync::Arc;
41
42#[non_exhaustive]
44#[derive(Clone, Debug, PartialEq)]
45pub enum PilotPattern {
46 Block {
48 period: usize,
50 },
51 Comb {
53 spacing: usize,
55 },
56 Lattice {
58 freq: usize,
60 time: usize,
62 },
63 Explicit(Arc<PilotTable>),
65 None,
67}
68
69#[derive(Clone, Debug, PartialEq)]
72pub struct PilotTable {
73 n_subcarriers: usize,
74 n_symbols: usize,
75 values: Vec<Option<C64>>,
77}
78
79impl PilotTable {
80 pub fn new(
87 n_subcarriers: usize,
88 n_symbols: usize,
89 pilots: impl IntoIterator<Item = ((usize, usize), C64)>,
90 ) -> Result<PilotTable> {
91 ensure(n_subcarriers > 0 && n_symbols > 0, || {
92 format!("a pilot table needs at least one RE, got {n_subcarriers} × {n_symbols}")
93 })?;
94 let mut values = vec![None; n_subcarriers * n_symbols];
95 for ((k, l), v) in pilots {
96 ensure(k < n_subcarriers && l < n_symbols, || {
97 format!("pilot ({k}, {l}) outside the grid of {n_subcarriers} × {n_symbols}")
98 })?;
99 ensure(v.norm() > 0.0 && v.re.is_finite() && v.im.is_finite(), || {
100 format!("the pilot ({k}, {l}) must be finite and non-zero, got {v}")
101 })?;
102 values[l * n_subcarriers + k] = Some(v);
103 }
104 Ok(PilotTable { n_subcarriers, n_symbols, values })
105 }
106
107 pub fn n_subcarriers(&self) -> usize {
109 self.n_subcarriers
110 }
111
112 pub fn n_symbols(&self) -> usize {
114 self.n_symbols
115 }
116
117 pub fn get(&self, k: usize, l: usize) -> Option<C64> {
119 if k < self.n_subcarriers && l < self.n_symbols { self.values[l * self.n_subcarriers + k] } else { None }
120 }
121
122 pub fn len(&self) -> usize {
124 self.values.iter().filter(|v| v.is_some()).count()
125 }
126
127 pub fn is_empty(&self) -> bool {
129 self.len() == 0
130 }
131}
132
133impl PilotPattern {
134 pub fn block(period: usize) -> Result<PilotPattern> {
147 Ok(PilotPattern::Block { period: positive(period, "period")? })
148 }
149
150 pub fn comb(spacing: usize) -> Result<PilotPattern> {
155 Ok(PilotPattern::Comb { spacing: positive(spacing, "spacing")? })
156 }
157
158 pub fn lattice(freq: usize, time: usize) -> Result<PilotPattern> {
163 Ok(PilotPattern::Lattice { freq: positive(freq, "freq")?, time: positive(time, "time")? })
164 }
165}
166
167fn positive(v: usize, what: &str) -> Result<usize> {
169 if v >= 1 { Ok(v) } else { Err(Error::Config(format!("the pilot {what} must be at least 1, got {v}"))) }
170}
171
172#[derive(Clone, Debug, PartialEq)]
174pub struct ResourceGrid {
175 pub n_subcarriers: usize,
177 pub n_symbols: usize,
179 pub pattern: PilotPattern,
181 pub seed: u64,
183}
184
185impl ResourceGrid {
186 pub fn check(&self) -> Result<()> {
199 let (k, l) = (self.n_subcarriers, self.n_symbols);
200 ensure(k >= 1 && l >= 1, || format!("a resource grid needs at least one RE, got {k} × {l}"))?;
201 match &self.pattern {
202 PilotPattern::Block { period } => positive(*period, "period").map(drop),
203 PilotPattern::Comb { spacing } => positive(*spacing, "spacing").map(drop),
204 PilotPattern::Lattice { freq, time } => {
205 positive(*freq, "freq").and_then(|_| positive(*time, "time")).map(drop)
206 }
207 PilotPattern::Explicit(t) => ensure(t.n_subcarriers == k && t.n_symbols == l, || {
208 format!("a pilot table of {} × {} for a grid of {k} × {l}", t.n_subcarriers, t.n_symbols)
209 }),
210 PilotPattern::None => Ok(()),
211 }
212 }
213
214 pub fn from_table(table: PilotTable) -> ResourceGrid {
216 ResourceGrid {
217 n_subcarriers: table.n_subcarriers,
218 n_symbols: table.n_symbols,
219 pattern: PilotPattern::Explicit(Arc::new(table)),
220 seed: 0,
221 }
222 }
223
224 pub fn is_pilot(&self, k: usize, l: usize) -> bool {
227 match &self.pattern {
228 PilotPattern::Block { period } => l % (*period).max(1) == 0,
229 PilotPattern::Comb { spacing } => k % (*spacing).max(1) == 0,
230 PilotPattern::Lattice { freq, time } => k % (*freq).max(1) == 0 && l % (*time).max(1) == 0,
231 PilotPattern::Explicit(t) => t.get(k, l).is_some(),
232 PilotPattern::None => false,
233 }
234 }
235
236 pub fn port_pilots(&self, l: usize, port: usize, n_ports: usize) -> Vec<usize> {
240 (0..self.n_subcarriers)
241 .filter(|&k| self.is_pilot(k, l))
242 .enumerate()
243 .filter(|(j, _)| j % n_ports.max(1) == port)
244 .map(|(_, k)| k)
245 .collect()
246 }
247
248 pub fn map_port(&self, data: &[C64], port: usize, n_ports: usize) -> Result<Vec<C64>> {
254 let mut g = self.map(data)?;
255 if n_ports > 1 {
256 for l in 0..self.n_symbols {
257 let mine = self.port_pilots(l, port, n_ports);
258 for k in (0..self.n_subcarriers).filter(|&k| self.is_pilot(k, l) && !mine.contains(&k)) {
259 g[l * self.n_subcarriers + k] = C64::new(0.0, 0.0);
260 }
261 }
262 }
263 Ok(g)
264 }
265
266 pub fn pilot(&self, k: usize, l: usize) -> C64 {
269 if let PilotPattern::Explicit(t) = &self.pattern {
270 if let Some(v) = t.get(k, l) {
271 return v;
272 }
273 }
274 comnum_mapping::source::qpsk(&mut comnum_core::rng::member(self.seed, (l * self.n_subcarriers + k) as u64))
275 }
276
277 fn res(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
279 (0..self.n_symbols).flat_map(move |l| (0..self.n_subcarriers).map(move |k| (k, l)))
280 }
281
282 pub fn pilot_positions(&self) -> Vec<(usize, usize)> {
284 self.res().filter(|&(k, l)| self.is_pilot(k, l)).collect()
285 }
286
287 pub fn data_indices(&self) -> Vec<usize> {
297 self.res().filter(|&(k, l)| !self.is_pilot(k, l)).map(|(k, l)| l * self.n_subcarriers + k).collect()
298 }
299
300 pub fn pilot_symbols(&self) -> Vec<usize> {
302 (0..self.n_symbols).filter(|&l| (0..self.n_subcarriers).any(|k| self.is_pilot(k, l))).collect()
303 }
304
305 pub fn n_data(&self) -> usize {
307 self.res().filter(|&(k, l)| !self.is_pilot(k, l)).count()
308 }
309
310 pub fn map(&self, data: &[C64]) -> Result<Vec<C64>> {
317 self.check()?;
318 let n_data = self.n_data();
319 ensure_shape(data.len() == n_data, || format!("{} data symbols for a grid of {n_data} data REs", data.len()))?;
320 let mut it = data.iter();
321 Ok(self
322 .res()
323 .map(
324 |(k, l)| if self.is_pilot(k, l) { self.pilot(k, l) } else { *it.next().unwrap_or(&C64::new(0.0, 0.0)) },
325 )
326 .collect())
327 }
328
329 pub fn extract<T: Copy>(&self, grid: &[T]) -> Result<Vec<T>> {
334 let n = self.n_subcarriers * self.n_symbols;
335 ensure_shape(grid.len() == n, || format!("{} REs for a grid of {n}", grid.len()))?;
336 Ok(self.data_indices().into_iter().map(|i| grid[i]).collect())
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
347 fn the_data_indices_are_the_former_walks() {
348 let table = scattered(49, 8);
349 let patterns = [
350 PilotPattern::Block { period: 3 },
351 PilotPattern::Comb { spacing: 4 },
352 PilotPattern::Lattice { freq: 3, time: 2 },
353 PilotPattern::Explicit(Arc::new(table)),
354 PilotPattern::None,
355 ];
356 for pattern in patterns {
357 let g = ResourceGrid { n_subcarriers: 49, n_symbols: 8, pattern, seed: 5 };
358 let former: Vec<usize> = (0..8)
359 .flat_map(|l| (0..49).map(move |k| (k, l)))
360 .filter(|&(k, l)| !g.is_pilot(k, l))
361 .map(|(k, l)| l * 49 + k)
362 .collect();
363 assert_eq!(g.data_indices(), former);
364 assert_eq!(g.n_data(), 49 * 8 - g.pilot_positions().len());
365 let data: Vec<C64> = (0..g.n_data()).map(|i| C64::new(i as f64, -1.0)).collect();
366 let mut it = data.iter();
367 let mut old = Vec::new();
368 for l in 0..8 {
369 for k in 0..49 {
370 old.push(if g.is_pilot(k, l) { g.pilot(k, l) } else { *it.next().unwrap() });
371 }
372 }
373 assert_eq!(g.map(&data).unwrap(), old);
374 }
375 let bad = ResourceGrid {
376 n_subcarriers: 12,
377 n_symbols: 7,
378 pattern: PilotPattern::Lattice { freq: 3, time: 0 },
379 seed: 0,
380 };
381 assert!(bad.check().is_err());
382 let wrong_size = ResourceGrid { n_subcarriers: 48, ..ResourceGrid::from_table(scattered(49, 8)) };
383 assert!(wrong_size.check().is_err());
384 }
385
386 #[test]
387 fn patterns_place_their_pilots() {
388 let block =
389 ResourceGrid { n_subcarriers: 8, n_symbols: 6, pattern: PilotPattern::Block { period: 3 }, seed: 0 };
390 assert_eq!(block.pilot_symbols(), vec![0, 3]);
391 assert_eq!(block.n_data(), 8 * 4);
392 let comb = ResourceGrid { pattern: PilotPattern::Comb { spacing: 4 }, ..block.clone() };
393 assert_eq!(comb.pilot_positions().len(), 2 * 6);
394 assert!((comb.pilot(4, 2).norm() - 1.0).abs() < 1e-12);
395 assert!(block.map(&[C64::new(0.0, 0.0); 3]).is_err());
396 }
397
398 fn scattered(k_n: usize, l_n: usize) -> PilotTable {
401 let pilots = (0..l_n).flat_map(move |l| {
402 (0..k_n).filter(move |k| k % 12 == 3 * (l % 4)).map(move |k| {
403 let sign = if (k / 12) % 2 == 0 { 1.0 } else { -1.0 };
404 ((k, l), C64::new(sign * 4.0 / 3.0, 0.0))
405 })
406 });
407 PilotTable::new(k_n, l_n, pilots).unwrap()
408 }
409
410 #[test]
411 fn explicit_tables_carry_their_positions_and_values() {
412 let g = ResourceGrid::from_table(scattered(49, 8));
413 assert_eq!(g.pilot_positions().len(), 34);
415 assert_eq!(g.pilot_symbols(), (0..8).collect::<Vec<_>>());
416 assert!(g.is_pilot(15, 1) && !g.is_pilot(15, 0));
417 assert_eq!(g.pilot(12, 0), C64::new(-4.0 / 3.0, 0.0));
418 let data: Vec<C64> = (0..g.n_data()).map(|i| C64::new(i as f64, 1.0)).collect();
419 let grid = g.map(&data).unwrap();
420 assert_eq!(grid[12], C64::new(-4.0 / 3.0, 0.0));
421 assert_eq!(g.extract(&grid).unwrap(), data);
422 assert!(g.extract(&grid[1..]).is_err());
423 let one = C64::new(1.0, 0.0);
424 assert!(PilotTable::new(4, 2, [((4, 0), one)]).is_err());
425 assert!(PilotTable::new(4, 2, [((0, 2), one)]).is_err());
426 assert!(PilotTable::new(4, 2, [((0, 0), C64::new(0.0, 0.0))]).is_err());
427 assert!(PilotTable::new(0, 2, []).is_err());
428 }
429}