1use crate::detection::{Estimates, KBest, Linear, MimoDetector, Ml, Sic, Sphere};
34use crate::message_passing::{Amp, Oamp};
35use crate::stbc::Ostbc;
36use comnum_core::{Error, Result, ensure};
37use comnum_mapping::constellation::Constellation;
38use comnum_math::C64;
39use comnum_math::Jacobian;
40use comnum_math::linalg::CMat;
41use ndarray::{Array2, ArrayView2};
42
43#[non_exhaustive]
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub enum DetectorKind {
47 Mr,
49 Zf,
51 Mmse,
53 Sic,
55 ZfSic,
57 Ml,
59 Sphere,
61 KBest(usize),
63 Amp(usize),
65 Oamp(usize),
67}
68
69impl std::str::FromStr for DetectorKind {
70 type Err = Error;
71
72 fn from_str(name: &str) -> Result<DetectorKind> {
80 let name = name.to_ascii_lowercase();
81 let canonical = match name.as_str() {
82 "mmse_sic" => "sic",
83 "lama" => "amp",
84 other => other,
85 };
86 DetectorKind::ALL
87 .into_iter()
88 .find(|d| d.name() == canonical)
89 .ok_or_else(|| Error::Config(format!("unknown detector {name:?}: {}", DetectorKind::NAMES.join(", "))))
90 }
91}
92
93impl DetectorKind {
94 pub const DEFAULT_PATHS: usize = 8;
96
97 pub const DEFAULT_ITERATIONS: usize = 10;
99
100 pub const ALL: [DetectorKind; 10] = [
103 DetectorKind::Mr,
104 DetectorKind::Zf,
105 DetectorKind::Mmse,
106 DetectorKind::Sic,
107 DetectorKind::ZfSic,
108 DetectorKind::Ml,
109 DetectorKind::Sphere,
110 DetectorKind::KBest(DetectorKind::DEFAULT_PATHS),
111 DetectorKind::Amp(DetectorKind::DEFAULT_ITERATIONS),
112 DetectorKind::Oamp(DetectorKind::DEFAULT_ITERATIONS),
113 ];
114
115 pub const NAMES: [&'static str; 10] = {
128 let mut names = [""; 10];
129 let mut i = 0;
130 while i < names.len() {
131 names[i] = DetectorKind::ALL[i].name();
132 i += 1;
133 }
134 names
135 };
136
137 pub const fn name(self) -> &'static str {
140 match self {
141 DetectorKind::Mr => "mr",
142 DetectorKind::Zf => "zf",
143 DetectorKind::Mmse => "mmse",
144 DetectorKind::Sic => "sic",
145 DetectorKind::ZfSic => "zf_sic",
146 DetectorKind::Ml => "ml",
147 DetectorKind::Sphere => "sphere",
148 DetectorKind::KBest(_) => "kbest",
149 DetectorKind::Amp(_) => "amp",
150 DetectorKind::Oamp(_) => "oamp",
151 }
152 }
153
154 pub fn with_iterations(self, iterations: usize) -> Result<DetectorKind> {
160 let needs = matches!(self, DetectorKind::Amp(_) | DetectorKind::Oamp(_));
161 ensure(!needs || iterations >= 1, || format!("{self:?} needs at least one iteration"))?;
162 Ok(match self {
163 DetectorKind::Amp(_) => DetectorKind::Amp(iterations),
164 DetectorKind::Oamp(_) => DetectorKind::Oamp(iterations),
165 other => other,
166 })
167 }
168
169 pub fn from_name(name: &str, k: usize) -> Result<DetectorKind> {
175 Ok(match name.parse()? {
176 DetectorKind::KBest(_) => {
177 ensure(k >= 1, || format!("kbest needs at least one path, got k = {k}"))?;
178 DetectorKind::KBest(k)
179 }
180 other => other,
181 })
182 }
183
184 pub fn has_soft_output(self) -> bool {
187 Constellation::psk(2).and_then(|c| self.detector(&c)).is_ok_and(|d| d.has_soft_output())
188 }
189
190 pub fn detector(self, c: &Constellation) -> Result<Box<dyn MimoDetector + Send + Sync>> {
195 self.build(c.clone())
196 }
197
198 fn build(self, c: Constellation) -> Result<Box<dyn MimoDetector + Send + Sync>> {
200 Ok(match self {
201 DetectorKind::Mr => Box::new(Linear::mr(c)),
202 DetectorKind::Zf => Box::new(Linear::zf(c)),
203 DetectorKind::Mmse => Box::new(Linear::mmse(c)),
204 DetectorKind::Sic => Box::new(Sic::mmse(c)),
205 DetectorKind::ZfSic => Box::new(Sic::zf(c)),
206 DetectorKind::Ml => Box::new(Ml::new(c)),
207 DetectorKind::Sphere => Box::new(Sphere::new(c)),
208 DetectorKind::KBest(k) => Box::new(KBest::new(c, k)?),
209 DetectorKind::Amp(n) => Box::new(Amp::new(c, n)?),
210 DetectorKind::Oamp(n) => Box::new(Oamp::new(c, n)?),
211 })
212 }
213}
214
215#[derive(Clone, Debug, Default, PartialEq)]
218pub struct SoftOutput {
219 pub symbols: Vec<C64>,
221 pub noise_var: Vec<f64>,
223 pub llr: Vec<f64>,
225}
226
227impl SoftOutput {
228 pub fn append(&mut self, e: Estimates) {
230 self.symbols.extend(e.symbols);
231 self.noise_var.extend(e.noise_var);
232 }
233}
234
235pub struct Receiver {
237 kind: DetectorKind,
238 detector: Box<dyn MimoDetector + Send + Sync>,
239 alamouti: Ostbc,
241 llr: bool,
242}
243
244impl std::fmt::Debug for Receiver {
245 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
246 f.debug_struct("Receiver").field("kind", &self.kind).field("llr", &self.llr).finish_non_exhaustive()
247 }
248}
249
250impl Receiver {
251 pub fn new(kind: DetectorKind, c: Constellation, llr: bool) -> Result<Receiver> {
257 let detector = kind.build(c)?;
258 ensure(!llr || detector.has_soft_output(), || {
259 format!("the {} detector has no soft output: demap its decisions instead", kind.name())
260 })?;
261 Ok(Receiver { kind, detector, alamouti: Ostbc::alamouti(), llr })
262 }
263
264 pub fn constellation(&self) -> &Constellation {
266 self.detector.constellation()
267 }
268
269 fn append_llr(&self, e: &Estimates, out: &mut SoftOutput) -> Result<()> {
271 let start = out.llr.len();
272 out.llr.resize(start + e.symbols.len() * self.constellation().bits_per_symbol(), 0.0);
273 self.constellation().llr_with_variances_into(
274 &e.symbols,
275 &e.noise_var,
276 None,
277 Jacobian::Exact,
278 &mut out.llr[start..],
279 )
280 }
281
282 pub fn layers(&self, h: &ArrayView2<'_, C64>, y: &[C64], noise_var: f64, out: &mut SoftOutput) -> Result<()> {
287 if let Some(e) = self.detector.soft_estimate(h, y, noise_var)? {
288 if self.llr {
290 self.append_llr(&e, out)?;
291 }
292 out.append(e);
293 } else {
294 let labels = if self.llr {
296 let (labels, llr) = self.detector.detect_llr(h, y, noise_var)?;
297 out.llr.extend(llr);
298 labels
299 } else {
300 self.detector.detect(h, y, noise_var)?
301 };
302 let points = self.constellation().points();
303 out.symbols.extend(labels.iter().map(|&i| points[i]));
304 out.noise_var.extend(std::iter::repeat_n(noise_var, labels.len()));
305 }
306 Ok(())
307 }
308
309 pub fn detect_uses<I, H, Y>(
334 &self,
335 uses: I,
336 alamouti: bool,
337 noise_var: f64,
338 mut channel: H,
339 mut received: Y,
340 out: &mut SoftOutput,
341 ) -> Result<()>
342 where
343 I: IntoIterator<Item = usize>,
344 H: FnMut(usize) -> Result<CMat>,
345 Y: FnMut(usize) -> Vec<C64>,
346 {
347 let mut uses = uses.into_iter();
348 if !alamouti {
349 for i in uses {
350 self.layers(&channel(i)?.view(), &received(i), noise_var, out)?;
351 }
352 return Ok(());
353 }
354 while let Some(i0) = uses.next() {
355 let Some(i1) = uses.next() else {
356 return Err(Error::Shape("Alamouti's code needs an even number of channel uses".into()));
357 };
358 let (y0, y1) = (received(i0), received(i1));
359 let y = Array2::from_shape_fn((y0.len(), 2), |(r, j)| if j == 0 { y0[r] } else { y1[r] });
360 self.decode_alamouti(&channel(i0)?.view(), &y.view(), noise_var, out)?;
361 }
362 Ok(())
363 }
364
365 pub fn decode_alamouti(
371 &self,
372 h: &ArrayView2<'_, C64>,
373 y: &ArrayView2<'_, C64>,
374 noise_var: f64,
375 out: &mut SoftOutput,
376 ) -> Result<()> {
377 let e = self.alamouti.decode(h, y, noise_var)?;
378 if self.llr {
379 self.append_llr(&e, out)?;
380 }
381 out.append(e);
382 Ok(())
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use comnum_core::rng::Rng;
390 use comnum_math::linalg::matvec;
391
392 #[test]
393 fn the_soft_outputs_are_those_of_the_detectors() {
394 for kind in DetectorKind::ALL {
396 let former = !matches!(kind, DetectorKind::Sic | DetectorKind::ZfSic | DetectorKind::Sphere);
397 assert_eq!(kind.has_soft_output(), former, "{kind:?}");
398 }
399 assert!(!DetectorKind::KBest(0).has_soft_output() && DetectorKind::KBest(0).detector(&qpsk()).is_err());
400 assert!(Receiver::new(DetectorKind::Sphere, qpsk(), true).is_err());
401 }
402
403 fn qpsk() -> Constellation {
404 Constellation::qam(4).unwrap().normalized()
405 }
406
407 #[test]
408 fn amp_and_oamp_give_their_decoupled_estimates_and_their_llrs() {
409 let c = qpsk();
410 let mut rng = Rng::new(31);
411 let h = Array2::from_shape_fn((16, 8), |_| rng.complex_gaussian() * 0.25);
412 let x: Vec<C64> = (0..8).map(|_| c.points()[rng.below(4)]).collect();
413 let y: Vec<C64> = matvec(&h.view(), &x).iter().map(|v| v + rng.complex_gaussian() * 0.1).collect();
414 for kind in [DetectorKind::Amp(10), DetectorKind::Oamp(10)] {
415 let rx = Receiver::new(kind, c.clone(), true).unwrap();
416 let mut out = SoftOutput::default();
417 rx.layers(&h.view(), &y, 0.01, &mut out).unwrap();
418 let d = kind.detector(&c).unwrap();
419 let e = d.soft_estimate(&h.view(), &y, 0.01).unwrap().unwrap();
420 assert_eq!((out.symbols, out.noise_var), (e.symbols, e.noise_var));
421 let (_, llr) = d.detect_llr(&h.view(), &y, 0.01).unwrap();
423 assert!(out.llr.iter().zip(&llr).all(|(a, b)| a.to_bits() == b.to_bits()) && out.llr.len() == 16);
424 }
425 }
426
427 #[test]
428 fn the_stored_alamouti_code_decodes_as_a_new_one() {
429 let c = qpsk();
430 let mut rng = Rng::new(32);
431 let rx = Receiver::new(DetectorKind::Mmse, c.clone(), true).unwrap();
432 let code = Ostbc::alamouti();
433 let s: Vec<C64> = (0..2).map(|_| c.points()[rng.below(4)]).collect();
434 let h = Array2::from_shape_fn((2, 2), |_| rng.complex_gaussian());
435 let y = h.dot(&code.encode(&s).unwrap()).mapv(|v| v + rng.complex_gaussian() * 0.1);
436 let mut out = SoftOutput::default();
437 rx.decode_alamouti(&h.view(), &y.view(), 0.01, &mut out).unwrap();
438 let e = Ostbc::alamouti().decode_blocks(&[h], &y.view(), 0.01).unwrap();
439 assert_eq!((out.symbols.clone(), out.noise_var.clone()), (e.symbols.clone(), e.noise_var.clone()));
440 assert_eq!(out.llr, c.llr_with_variances(&e.symbols, &e.noise_var, None, Jacobian::Exact).unwrap());
441 }
442}