use super::Ofdm;
use super::estimation::{FrequencyInterpolation, PortEstimation, estimate_port};
use super::grid::ResourceGrid;
use comnum_core::rng::Rng;
use comnum_core::{Result, ensure, ensure_shape};
use comnum_math::C64;
use comnum_mimo::receiver::{Receiver, SoftOutput};
use ndarray::Array2;
#[derive(Clone, Debug, PartialEq)]
pub struct OfdmFrame {
pub ofdm: Ofdm,
pub grid: ResourceGrid,
pub n_grids: usize,
pub n_ports: usize,
pub n_data_symbols: usize,
pub scale: f64,
}
pub fn transmit(
ofdm: &Ofdm,
grid: &ResourceGrid,
streams: Vec<Vec<C64>>,
rng: &mut Rng,
) -> Result<(Vec<Vec<C64>>, OfdmFrame)> {
let n_ports = streams.len();
ensure(n_ports >= 1, || "at least one antenna".into())?;
let n_data_symbols = streams[0].len();
ensure_shape(streams.iter().all(|x| x.len() == n_data_symbols), || {
format!("the antennas carry different lengths: {:?}", streams.iter().map(Vec::len).collect::<Vec<_>>())
})?;
ofdm.check()?;
ensure(grid.n_subcarriers == ofdm.n_active, || {
format!(
"a grid of {} subcarriers for an OFDM modulator of {} active subcarriers",
grid.n_subcarriers, ofdm.n_active
)
})?;
ensure(!ofdm.dft_spread, || "the OFDM link maps a resource grid: DFT spreading is not supported".into())?;
grid.check()?;
let per_grid = grid.n_data();
ensure(per_grid > 0, || "the grid has no data resource element".into())?;
let n_grids = n_data_symbols.div_ceil(per_grid).max(1);
let scale = (ofdm.n_active as f64 / ofdm.fft_len() as f64).sqrt();
let pad_amp = std::f64::consts::FRAC_1_SQRT_2 / (n_ports as f64).sqrt();
let mut out = Vec::with_capacity(n_ports);
for (port, x) in streams.into_iter().enumerate() {
let mut data = x;
while data.len() < n_grids * per_grid {
data.push(C64::new(
if rng.bit() == 0 { pad_amp } else { -pad_amp },
if rng.bit() == 0 { pad_amp } else { -pad_amp },
));
}
let mut time = Vec::with_capacity(n_grids * grid.n_symbols * ofdm.block_len());
for chunk in data.chunks(per_grid) {
let g = grid.map_port(chunk, port, n_ports)?;
time.extend(ofdm.modulate(&g)?.into_iter().map(|v| v * scale));
}
out.push(time);
}
let frame = OfdmFrame { ofdm: ofdm.clone(), grid: grid.clone(), n_grids, n_ports, n_data_symbols, scale };
Ok((out, frame))
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FrameDetection {
pub gain: f64,
pub alamouti: bool,
pub noise_var: f64,
}
impl Default for FrameDetection {
fn default() -> FrameDetection {
FrameDetection { gain: 1.0, alamouti: false, noise_var: 0.0 }
}
}
impl FrameDetection {
pub fn check(&self) -> Result<()> {
let (g, v) = (self.gain, self.noise_var);
ensure(g.is_finite() && v.is_finite(), || {
format!("the gain and the noise variance must be finite, got {g} and {v}")
})
}
}
impl OfdmFrame {
pub fn data_res(&self) -> Vec<usize> {
self.grid.data_indices()
}
pub fn symbol_middles(&self, g: usize) -> Vec<usize> {
let (bl, cp, ln) = (self.ofdm.block_len(), self.ofdm.oversampling * self.ofdm.n_cp, self.ofdm.fft_len());
(0..self.grid.n_symbols).map(|l| (g * self.grid.n_symbols + l) * bl + cp + ln / 2).collect()
}
pub fn active_bins(&self) -> Vec<usize> {
(0..self.ofdm.n_active).map(|i| self.ofdm.bin(i)).collect()
}
pub fn received_grid(&self, streams: &[Vec<C64>], g: usize) -> Result<Vec<Vec<C64>>> {
let len = self.grid.n_symbols * self.ofdm.block_len();
let (start, end) = (g * len, (g + 1) * len);
streams
.iter()
.map(|x| {
ensure_shape(end <= x.len(), || {
format!("the signal of {} samples is shorter than the OFDM frame ({end} samples)", x.len())
})?;
let samples: Vec<C64> = x[start..end].iter().map(|v| v / self.scale).collect();
self.ofdm.demodulate(&samples)
})
.collect()
}
pub fn estimate(&self, rx: &[Vec<C64>], noise_var: f64, method: &FrequencyInterpolation) -> Result<Vec<C64>> {
let (nr, nt) = (rx.len(), self.n_ports);
let mut h = vec![C64::new(0.0, 0.0); self.grid.n_subcarriers * self.grid.n_symbols * nr * nt];
let mut settings = PortEstimation { method: method.clone(), noise_var, port: 0, n_ports: nt };
for (r, y) in rx.iter().enumerate() {
for t in 0..nt {
settings.port = t;
let est = estimate_port(&self.grid, y, &settings)?;
for (re, v) in est.into_iter().enumerate() {
h[(re * nr + r) * nt + t] = v;
}
}
}
Ok(h)
}
pub fn detect<F>(
&self,
streams: &[Vec<C64>],
mut channel: F,
receiver: &Receiver,
settings: &FrameDetection,
) -> Result<SoftOutput>
where
F: FnMut(usize, &[Vec<C64>]) -> Result<Vec<C64>>,
{
settings.check()?;
let FrameDetection { gain, alamouti, noise_var } = *settings;
let (nr, nt) = (streams.len(), self.n_ports);
let data_res = self.data_res();
let mut out = SoftOutput::default();
for g in 0..self.n_grids {
let rx = self.received_grid(streams, g)?;
let h = channel(g, &rx)?;
let used = self.n_data_symbols.saturating_sub(g * data_res.len()).min(data_res.len());
receiver
.detect_uses(
data_res[..used].iter().copied(),
alamouti,
noise_var,
|re| Ok(Array2::from_shape_fn((nr, nt), |(r, t)| h[(re * nr + r) * nt + t] * gain)),
|re| rx.iter().map(|v| v[re]).collect(),
&mut out,
)
.map_err(|e| e.context("the data resource elements"))?;
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ofdm::grid::PilotPattern;
#[test]
fn the_link_on_the_modem_is_the_former_link() {
let ofdm = Ofdm { n_cp: 6, oversampling: 2, dc_null: true, ..Ofdm::new(32, 24).unwrap() };
let grid = ResourceGrid {
n_subcarriers: 24,
n_symbols: 5,
pattern: PilotPattern::Lattice { freq: 3, time: 2 },
seed: 4,
};
let mut rng = Rng::new(7);
let streams: Vec<Vec<C64>> = (0..2).map(|_| (0..250).map(|_| rng.complex_gaussian()).collect()).collect();
let (tx, frame) = transmit(&ofdm, &grid, streams.clone(), &mut Rng::new(1)).unwrap();
let mut pad_rng = Rng::new(1);
let (k, ln) = (ofdm.n_active, ofdm.fft_len());
let scale = (k as f64 / ln as f64).sqrt();
let amp = std::f64::consts::FRAC_1_SQRT_2 / 2f64.sqrt();
for (port, x) in streams.into_iter().enumerate() {
let mut data = x;
while data.len() < frame.n_grids * grid.n_data() {
data.push(C64::new(
if pad_rng.bit() == 0 { amp } else { -amp },
if pad_rng.bit() == 0 { amp } else { -amp },
));
}
let mut old = Vec::new();
for chunk in data.chunks(grid.n_data()) {
for sym in grid.map_port(chunk, port, 2).unwrap().chunks(k) {
old.extend(ofdm.with_prefix(&ofdm.to_time(sym).unwrap()).unwrap().into_iter().map(|v| v * scale));
}
}
assert_eq!(tx[port], old);
}
let (bl, cp) = (ofdm.block_len(), ofdm.oversampling * ofdm.n_cp);
for g in 0..frame.n_grids {
let rx = frame.received_grid(&tx, g).unwrap();
for (x, got) in tx.iter().zip(&rx) {
let mut old = Vec::new();
for l in 0..grid.n_symbols {
let start = (g * grid.n_symbols + l) * bl + cp;
let block: Vec<C64> = x[start..start + ln].iter().map(|v| v / frame.scale).collect();
old.extend(ofdm.to_active(&block).unwrap());
}
assert_eq!(got, &old);
}
}
assert!(frame.received_grid(&[tx[0][..tx[0].len() - 1].to_vec()], frame.n_grids - 1).is_err());
}
}