use num_complex::Complex32 as C32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubcarrierRole {
Data,
Pilot,
Null,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum CarrierPlanError {
#[error("carrier index {0} is out of range for n_fft={1} (valid: -(n_fft/2)..=((n_fft-1)/2))")]
OutOfRange(i32, usize),
#[error("carrier index {0} is assigned more than one role (data/pilot overlap)")]
Overlap(i32),
#[error("no data carriers specified")]
EmptyDataSet,
#[error("carrier index {0} intrudes into the {1}-carrier edge-guard band")]
InGuardBand(i32, usize),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CarrierPlan {
n_fft: usize,
cp_len: usize,
data_carriers: Vec<i32>,
pilot_carriers: Vec<(i32, C32)>,
window_roll_off: usize,
}
impl CarrierPlan {
pub fn new(n_fft: usize, cp_len: usize) -> Self {
Self {
n_fft,
cp_len,
data_carriers: Vec::new(),
pilot_carriers: Vec::new(),
window_roll_off: 0,
}
}
pub fn with_data_carriers(mut self, carriers: impl IntoIterator<Item = i32>) -> Self {
self.data_carriers.extend(carriers);
self
}
pub fn with_pilot_carriers(mut self, carriers: impl IntoIterator<Item = (i32, C32)>) -> Self {
self.pilot_carriers.extend(carriers);
self
}
pub fn with_window_roll_off(mut self, roll_off: usize) -> Self {
self.window_roll_off = roll_off;
self
}
pub fn window_roll_off(&self) -> usize {
self.window_roll_off
}
pub fn with_contiguous_data(mut self, edge_guard: usize, include_dc: bool) -> Self {
let (lo, hi) = self.index_bounds();
let g = edge_guard as i32;
let start = lo + 1 + g;
let pilots: std::collections::HashSet<i32> =
self.pilot_carriers.iter().map(|&(idx, _)| idx).collect();
for idx in start..=(hi - g) {
if idx == 0 && !include_dc {
continue;
}
if pilots.contains(&idx) {
continue;
}
self.data_carriers.push(idx);
}
self
}
pub fn n_fft(&self) -> usize {
self.n_fft
}
pub fn cp_len(&self) -> usize {
self.cp_len
}
pub fn data_carriers(&self) -> &[i32] {
&self.data_carriers
}
pub fn occupied_half_carriers(&self) -> usize {
let data = self.data_carriers.iter().copied();
let pilots = self.pilot_carriers.iter().map(|&(idx, _)| idx);
data.chain(pilots)
.map(|idx| idx.unsigned_abs() as usize)
.max()
.unwrap_or(0)
}
pub fn pilot_carriers(&self) -> &[(i32, C32)] {
&self.pilot_carriers
}
pub fn index_bounds(&self) -> (i32, i32) {
let n = self.n_fft as i32;
(-(n / 2), (n - 1) / 2)
}
fn in_range(&self, idx: i32) -> bool {
let (lo, hi) = self.index_bounds();
idx >= lo && idx <= hi
}
pub fn validate(&self) -> Result<(), CarrierPlanError> {
if self.data_carriers.is_empty() {
return Err(CarrierPlanError::EmptyDataSet);
}
for &idx in &self.data_carriers {
if !self.in_range(idx) {
return Err(CarrierPlanError::OutOfRange(idx, self.n_fft));
}
}
for &(idx, _) in &self.pilot_carriers {
if !self.in_range(idx) {
return Err(CarrierPlanError::OutOfRange(idx, self.n_fft));
}
}
let mut seen = std::collections::HashSet::new();
for &idx in &self.data_carriers {
if !seen.insert(idx) {
return Err(CarrierPlanError::Overlap(idx));
}
}
for &(idx, _) in &self.pilot_carriers {
if !seen.insert(idx) {
return Err(CarrierPlanError::Overlap(idx));
}
}
Ok(())
}
pub fn validate_edge_guard(&self, edge_guard: usize) -> Result<(), CarrierPlanError> {
self.validate()?;
let (lo, hi) = self.index_bounds();
let g = edge_guard as i32;
let (glo, ghi) = (lo + g, hi - g);
for &idx in &self.data_carriers {
if idx < glo || idx > ghi {
return Err(CarrierPlanError::InGuardBand(idx, edge_guard));
}
}
for &(idx, _) in &self.pilot_carriers {
if idx < glo || idx > ghi {
return Err(CarrierPlanError::InGuardBand(idx, edge_guard));
}
}
Ok(())
}
}