1use super::Ofdm;
43use super::estimation::{FrequencyInterpolation, PortEstimation, estimate_port};
44use super::grid::ResourceGrid;
45use comnum_core::rng::Rng;
46use comnum_core::{Result, ensure, ensure_shape};
47use comnum_math::C64;
48use comnum_mimo::receiver::{Receiver, SoftOutput};
49use ndarray::Array2;
50
51#[derive(Clone, Debug, PartialEq)]
53pub struct OfdmFrame {
54 pub ofdm: Ofdm,
56 pub grid: ResourceGrid,
58 pub n_grids: usize,
60 pub n_ports: usize,
62 pub n_data_symbols: usize,
64 pub scale: f64,
66}
67
68pub fn transmit(
77 ofdm: &Ofdm,
78 grid: &ResourceGrid,
79 streams: Vec<Vec<C64>>,
80 rng: &mut Rng,
81) -> Result<(Vec<Vec<C64>>, OfdmFrame)> {
82 let n_ports = streams.len();
83 ensure(n_ports >= 1, || "at least one antenna".into())?;
84 let n_data_symbols = streams[0].len();
85 ensure_shape(streams.iter().all(|x| x.len() == n_data_symbols), || {
86 format!("the antennas carry different lengths: {:?}", streams.iter().map(Vec::len).collect::<Vec<_>>())
87 })?;
88 ofdm.check()?;
89 ensure(grid.n_subcarriers == ofdm.n_active, || {
90 format!(
91 "a grid of {} subcarriers for an OFDM modulator of {} active subcarriers",
92 grid.n_subcarriers, ofdm.n_active
93 )
94 })?;
95 ensure(!ofdm.dft_spread, || "the OFDM link maps a resource grid: DFT spreading is not supported".into())?;
98 grid.check()?;
99 let per_grid = grid.n_data();
100 ensure(per_grid > 0, || "the grid has no data resource element".into())?;
101 let n_grids = n_data_symbols.div_ceil(per_grid).max(1);
102 let scale = (ofdm.n_active as f64 / ofdm.fft_len() as f64).sqrt();
103 let pad_amp = std::f64::consts::FRAC_1_SQRT_2 / (n_ports as f64).sqrt();
105 let mut out = Vec::with_capacity(n_ports);
106 for (port, x) in streams.into_iter().enumerate() {
107 let mut data = x;
108 while data.len() < n_grids * per_grid {
109 data.push(C64::new(
110 if rng.bit() == 0 { pad_amp } else { -pad_amp },
111 if rng.bit() == 0 { pad_amp } else { -pad_amp },
112 ));
113 }
114 let mut time = Vec::with_capacity(n_grids * grid.n_symbols * ofdm.block_len());
115 for chunk in data.chunks(per_grid) {
116 let g = grid.map_port(chunk, port, n_ports)?;
117 time.extend(ofdm.modulate(&g)?.into_iter().map(|v| v * scale));
118 }
119 out.push(time);
120 }
121 let frame = OfdmFrame { ofdm: ofdm.clone(), grid: grid.clone(), n_grids, n_ports, n_data_symbols, scale };
122 Ok((out, frame))
123}
124
125#[derive(Clone, Copy, Debug, PartialEq)]
127pub struct FrameDetection {
128 pub gain: f64,
130 pub alamouti: bool,
133 pub noise_var: f64,
135}
136
137impl Default for FrameDetection {
138 fn default() -> FrameDetection {
140 FrameDetection { gain: 1.0, alamouti: false, noise_var: 0.0 }
141 }
142}
143
144impl FrameDetection {
145 pub fn check(&self) -> Result<()> {
150 let (g, v) = (self.gain, self.noise_var);
151 ensure(g.is_finite() && v.is_finite(), || {
152 format!("the gain and the noise variance must be finite, got {g} and {v}")
153 })
154 }
155}
156
157impl OfdmFrame {
158 pub fn data_res(&self) -> Vec<usize> {
161 self.grid.data_indices()
162 }
163
164 pub fn symbol_middles(&self, g: usize) -> Vec<usize> {
167 let (bl, cp, ln) = (self.ofdm.block_len(), self.ofdm.oversampling * self.ofdm.n_cp, self.ofdm.fft_len());
168 (0..self.grid.n_symbols).map(|l| (g * self.grid.n_symbols + l) * bl + cp + ln / 2).collect()
169 }
170
171 pub fn active_bins(&self) -> Vec<usize> {
173 (0..self.ofdm.n_active).map(|i| self.ofdm.bin(i)).collect()
174 }
175
176 pub fn received_grid(&self, streams: &[Vec<C64>], g: usize) -> Result<Vec<Vec<C64>>> {
181 let len = self.grid.n_symbols * self.ofdm.block_len();
182 let (start, end) = (g * len, (g + 1) * len);
183 streams
184 .iter()
185 .map(|x| {
186 ensure_shape(end <= x.len(), || {
187 format!("the signal of {} samples is shorter than the OFDM frame ({end} samples)", x.len())
188 })?;
189 let samples: Vec<C64> = x[start..end].iter().map(|v| v / self.scale).collect();
191 self.ofdm.demodulate(&samples)
192 })
193 .collect()
194 }
195
196 pub fn estimate(&self, rx: &[Vec<C64>], noise_var: f64, method: &FrequencyInterpolation) -> Result<Vec<C64>> {
202 let (nr, nt) = (rx.len(), self.n_ports);
203 let mut h = vec![C64::new(0.0, 0.0); self.grid.n_subcarriers * self.grid.n_symbols * nr * nt];
204 let mut settings = PortEstimation { method: method.clone(), noise_var, port: 0, n_ports: nt };
205 for (r, y) in rx.iter().enumerate() {
206 for t in 0..nt {
207 settings.port = t;
208 let est = estimate_port(&self.grid, y, &settings)?;
209 for (re, v) in est.into_iter().enumerate() {
210 h[(re * nr + r) * nt + t] = v;
211 }
212 }
213 }
214 Ok(h)
215 }
216
217 pub fn detect<F>(
227 &self,
228 streams: &[Vec<C64>],
229 mut channel: F,
230 receiver: &Receiver,
231 settings: &FrameDetection,
232 ) -> Result<SoftOutput>
233 where
234 F: FnMut(usize, &[Vec<C64>]) -> Result<Vec<C64>>,
235 {
236 settings.check()?;
237 let FrameDetection { gain, alamouti, noise_var } = *settings;
238 let (nr, nt) = (streams.len(), self.n_ports);
239 let data_res = self.data_res();
240 let mut out = SoftOutput::default();
241 for g in 0..self.n_grids {
242 let rx = self.received_grid(streams, g)?;
243 let h = channel(g, &rx)?;
244 let used = self.n_data_symbols.saturating_sub(g * data_res.len()).min(data_res.len());
245 receiver
246 .detect_uses(
247 data_res[..used].iter().copied(),
248 alamouti,
249 noise_var,
250 |re| Ok(Array2::from_shape_fn((nr, nt), |(r, t)| h[(re * nr + r) * nt + t] * gain)),
251 |re| rx.iter().map(|v| v[re]).collect(),
252 &mut out,
253 )
254 .map_err(|e| e.context("the data resource elements"))?;
255 }
256 Ok(out)
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::ofdm::grid::PilotPattern;
264
265 #[test]
269 fn the_link_on_the_modem_is_the_former_link() {
270 let ofdm = Ofdm { n_cp: 6, oversampling: 2, dc_null: true, ..Ofdm::new(32, 24).unwrap() };
271 let grid = ResourceGrid {
272 n_subcarriers: 24,
273 n_symbols: 5,
274 pattern: PilotPattern::Lattice { freq: 3, time: 2 },
275 seed: 4,
276 };
277 let mut rng = Rng::new(7);
278 let streams: Vec<Vec<C64>> = (0..2).map(|_| (0..250).map(|_| rng.complex_gaussian()).collect()).collect();
279 let (tx, frame) = transmit(&ofdm, &grid, streams.clone(), &mut Rng::new(1)).unwrap();
280 let mut pad_rng = Rng::new(1);
282 let (k, ln) = (ofdm.n_active, ofdm.fft_len());
283 let scale = (k as f64 / ln as f64).sqrt();
284 let amp = std::f64::consts::FRAC_1_SQRT_2 / 2f64.sqrt();
285 for (port, x) in streams.into_iter().enumerate() {
286 let mut data = x;
287 while data.len() < frame.n_grids * grid.n_data() {
288 data.push(C64::new(
289 if pad_rng.bit() == 0 { amp } else { -amp },
290 if pad_rng.bit() == 0 { amp } else { -amp },
291 ));
292 }
293 let mut old = Vec::new();
294 for chunk in data.chunks(grid.n_data()) {
295 for sym in grid.map_port(chunk, port, 2).unwrap().chunks(k) {
296 old.extend(ofdm.with_prefix(&ofdm.to_time(sym).unwrap()).unwrap().into_iter().map(|v| v * scale));
297 }
298 }
299 assert_eq!(tx[port], old);
300 }
301 let (bl, cp) = (ofdm.block_len(), ofdm.oversampling * ofdm.n_cp);
303 for g in 0..frame.n_grids {
304 let rx = frame.received_grid(&tx, g).unwrap();
305 for (x, got) in tx.iter().zip(&rx) {
306 let mut old = Vec::new();
307 for l in 0..grid.n_symbols {
308 let start = (g * grid.n_symbols + l) * bl + cp;
309 let block: Vec<C64> = x[start..start + ln].iter().map(|v| v / frame.scale).collect();
310 old.extend(ofdm.to_active(&block).unwrap());
311 }
312 assert_eq!(got, &old);
313 }
314 }
315 assert!(frame.received_grid(&[tx[0][..tx[0].len() - 1].to_vec()], frame.n_grids - 1).is_err());
316 }
317}