use super::{Block, Extension, Frame};
use comnum_core::Result;
use comnum_core::rng::Rng;
use comnum_waveform::ce_ofdm::CeOfdm;
use comnum_waveform::ce_ofdm::gamp::Gamp;
use comnum_waveform::ce_ofdm::theory as ce_theory;
use comnum_waveform::ce_ofdm::{Receiver, Unwrap};
pub(super) fn blocks() -> Vec<Extension> {
vec![
builtin!(
"ce_ofdm_tx",
"Constant-envelope OFDM modulator (Thompson et al. 2008): K symbols per block on the bins 1..K of an N = 2J(K + 1) DFT, exp(j2πh x).",
CeOfdmSpec,
needs [Samples],
gives [],
|q, _, _| Ok(Box::new(CeOfdmTx(q.link()))),
),
builtin!(
"ce_ofdm_rx",
"CE-OFDM receiver back to the symbols: the arctangent phase demodulator, the linear and enhanced receivers of Ahmed and Zeidler (2015) or GAMP (López Morales et al. 2022); records ser_theory and ber_theory when esn0_db is given.",
CeOfdmRxSpec,
needs [Samples, Constellation],
gives [],
|q, label, _| Ok(Box::new(CeOfdmRx::new(&q, label)?)),
),
]
}
block_params! {
struct CeOfdmSpec {
n_subcarriers: usize [64] >= 1, "data subcarriers K per block";
oversampling: usize [4] >= 1, "oversampling factor J";
h: f64 [required] > 0, "modulation index (phase standard deviation 2πh)";
n_cp: usize [0] "samples", "cyclic prefix";
}
}
impl CeOfdmSpec {
fn link(&self) -> CeOfdm {
CeOfdm {
n_subcarriers: self.n_subcarriers,
oversampling: self.oversampling,
h: self.h,
n_cp: self.n_cp,
receiver: Receiver::default(),
sample_rate: comnum_core::units::Hertz(1.0),
}
}
}
struct CeOfdmTx(CeOfdm);
impl Block for CeOfdmTx {
fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
s.require_one_sps("ce_ofdm_tx")?;
s.samples = self.0.modulate(&s.samples)?;
s.sps = self.0.sps();
Ok(())
}
}
block_params! {
struct CeOfdmRxSpec {
n_subcarriers: usize [64] >= 1, "data subcarriers K (as the modulator)";
oversampling: usize [4] >= 1, "oversampling factor J (as the modulator)";
h: f64 [required] > 0, "modulation index (as the modulator)";
n_cp: usize [0] "samples", "cyclic prefix (as the modulator)";
unwrap: String ["none"] one_of ["none", "stream"],
"none (principal phase) or stream (unwrapped along the stream, the phase unwrapper of the paper)";
receiver: String ["arctangent"] one_of ["arctangent", "linear", "enhanced", "gamp"],
"arctangent (phase demodulator), linear (Im r) or enhanced (Im r (2 - Re r)) of Ahmed and Zeidler 2015, or gamp (near-optimum message passing, needs esn0_db)";
esn0_db: Option<f64> [absent] "dB",
"E_s/N_0 of the channel per data symbol: records ser_theory and ber_theory (the linear regime for arctangent and gamp, where it is the optimum asymptotic rate of QPSK; Bussgang's analysis for linear and enhanced), and sets the noise variance of gamp";
}
}
struct CeOfdmRx {
ce: CeOfdm,
receiver: Option<Receiver>,
label: String,
esn0_db: Option<f64>,
}
impl CeOfdmRx {
fn new(p: &CeOfdmRxSpec, label: String) -> Result<CeOfdmRx> {
let ce =
CeOfdmSpec { n_subcarriers: p.n_subcarriers, oversampling: p.oversampling, h: p.h, n_cp: p.n_cp }.link();
let receiver = match p.receiver.as_str() {
"gamp" => {
if p.esn0_db.is_none() {
return Err(comnum_core::Error::Config("ce_ofdm_rx: the gamp receiver needs esn0_db".into()));
}
None
}
"arctangent" => Some(Receiver::Arctangent(p.unwrap.parse::<Unwrap>()?)),
other => Some(other.parse::<Receiver>()?),
};
Ok(CeOfdmRx { ce, receiver, label, esn0_db: p.esn0_db })
}
}
impl Block for CeOfdmRx {
fn apply(&self, s: &mut Frame, _: &mut Rng) -> Result<()> {
let c = s.constellation("ce_ofdm_rx")?.clone();
if let Some(es) = self.esn0_db {
let (m, kind) = (c.order(), c.kind());
let ce =
CeOfdm { receiver: self.receiver.unwrap_or(Receiver::Arctangent(Unwrap::None)), ..self.ce.clone() };
s.record(&self.label, "ser_theory", ce_theory::ser_receiver(&ce, kind, m, es)?);
s.record(&self.label, "ber_theory", ce_theory::ber_receiver(&ce, kind, m, es)?);
}
s.samples = match (self.receiver, self.esn0_db) {
(Some(r), _) => self.ce.demodulate_with_receiver(&s.samples, r)?,
(None, Some(es)) => Gamp::default().detect(&self.ce, &s.samples, &c, self.ce.noise_variance(es))?,
(None, None) => unreachable!("checked by new"),
};
s.sps = 1.0;
Ok(())
}
}