use super::{Cpm, FrequencyPulse};
use comnum_core::{Error, Result, ensure, ensure_shape};
use comnum_dsp::filter::{LowPass, fir_filter, low_pass_fir};
use comnum_math::{C64, cis};
use std::f64::consts::{PI, TAU};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MlPath {
pub symbols: Vec<usize>,
pub phase_states: Vec<usize>,
}
impl Cpm {
pub const MAX_DIFFERENCE_SEQUENCES: usize = 1 << 20;
fn n_symbols(&self, r: &[C64]) -> Result<usize> {
let sps = self.cfg.sps;
ensure_shape(r.len() >= sps && r.len() % sps == 0, || {
format!("{} samples is not a positive multiple of {sps} samples per symbol", r.len())
})?;
Ok(r.len() / sps)
}
fn branch_waveforms(&self) -> Vec<Vec<C64>> {
let (m, l, sps) = (self.cfg.order, self.cfg.length, self.cfg.sps);
let mut window = vec![0usize; l];
(0..m * self.past_combos)
.map(|combo| {
let mut c = combo;
for w in &mut window {
*w = c % m;
c /= m;
}
(0..sps).map(|t| cis(-self.partial_phase(&window, t))).collect()
})
.collect()
}
pub fn ml_detect(&self, r: &[C64]) -> Result<Vec<usize>> {
Ok(self.ml_detect_path(r)?.symbols)
}
pub fn ml_detect_path(&self, r: &[C64]) -> Result<MlPath> {
let n_sym = self.n_symbols(r)?;
let (m, l, sps) = (self.cfg.order, self.cfg.length, self.cfg.sps);
let n_phase = self.n_phase_states();
let n_states = self.n_states();
let pc = self.past_combos;
let waves = self.branch_waveforms();
let rot: Vec<C64> = (0..n_phase).map(|s| cis(-PI * s as f64 / f64::from(self.cfg.h_den))).collect();
let past0 = (0..l.saturating_sub(1)).fold(0usize, |acc, _| acc * m + self.one());
let mut metric = vec![f64::NEG_INFINITY; n_states];
metric[past0] = 0.0;
let mut next = vec![f64::NEG_INFINITY; n_states];
let mut prev = vec![0u32; n_sym * n_states];
let mut sym = vec![0u8; n_sym * n_states];
let mut corr = vec![C64::new(0.0, 0.0); waves.len()];
for n in 0..n_sym {
let seg = &r[n * sps..(n + 1) * sps];
for (c, w) in corr.iter_mut().zip(&waves) {
*c = seg.iter().zip(w).map(|(a, b)| a * b).sum();
}
next.fill(f64::NEG_INFINITY);
for state in 0..n_states {
let acc = metric[state];
if acc == f64::NEG_INFINITY {
continue;
}
let (s, past) = (state / pc, state % pc);
for i in 0..m {
let combo = i + m * past;
let branch = acc + (corr[combo] * rot[s]).re;
let leaving = if l == 1 { i } else { past / m.pow((l - 2) as u32) };
let s_next = self.next_phase(s, leaving);
let past_next = if l == 1 { 0 } else { (i + m * past) % pc };
let to = s_next * pc + past_next;
if branch > next[to] {
next[to] = branch;
prev[n * n_states + to] = state as u32;
sym[n * n_states + to] = i as u8;
}
}
}
let best = next.iter().copied().fold(f64::NEG_INFINITY, f64::max);
for v in &mut next {
*v -= best;
}
std::mem::swap(&mut metric, &mut next);
}
let mut state = (0..n_states).max_by(|&a, &b| metric[a].total_cmp(&metric[b])).unwrap_or(0);
let mut symbols = vec![0usize; n_sym];
let mut phase_states = vec![0usize; n_sym + 1];
phase_states[n_sym] = state / pc;
for n in (0..n_sym).rev() {
symbols[n] = sym[n * n_states + state] as usize;
state = prev[n * n_states + state] as usize;
phase_states[n] = state / pc;
}
Ok(MlPath { symbols, phase_states })
}
fn ensure_msk(&self) -> Result<()> {
let c = &self.cfg;
ensure(
c.order == 2 && c.length == 1 && c.pulse == FrequencyPulse::Rectangular && c.h_num == 1 && c.h_den == 2,
|| "the quadrature detector needs MSK (binary 1REC, h = 1/2)".into(),
)
}
pub fn msk_detect(&self, r: &[C64]) -> Result<Vec<u8>> {
self.ensure_msk()?;
let n_sym = self.n_symbols(r)?;
let sps = self.cfg.sps;
let (mut rotate, step) = (C64::new(1.0, 0.0), C64::new(0.0, -1.0));
let mut bits = Vec::with_capacity(n_sym);
for n in 1..=n_sym {
rotate *= step;
let centre = n * sps;
let lo = centre - sps;
let hi = (centre + sps).min(r.len());
let z: C64 = (lo..hi).map(|t| r[t] * (PI * (t as f64 - centre as f64) / (2.0 * sps as f64)).cos()).sum();
bits.push(u8::from((z * rotate).re > 0.0));
}
Ok(bits)
}
fn ensure_binary(&self) -> Result<()> {
ensure(self.cfg.order == 2, || "the differential detector needs a binary signal".into())
}
pub fn differential_detect(&self, r: &[C64], rx_bt: Option<f64>) -> Result<Vec<usize>> {
self.ensure_binary()?;
let n_sym = self.n_symbols(r)?;
let (l, sps) = (self.cfg.length, self.cfg.sps);
let filtered;
let r = match rx_bt {
Some(bt) => {
ensure(bt > 0.0 && bt.is_finite(), || format!("the receive bandwidth must be positive, got {bt}"))?;
let taps = low_pass_fir(bt, sps as f64, 4 * sps + 1, LowPass::Gauss);
filtered = fir_filter(&taps, r);
&filtered[..]
}
None => r,
};
let offset = (l + 1) * sps / 2;
let at = |k: usize| r[k.min(r.len() - 1)];
let sample = |n: usize| at(n * sps + offset);
let mut prev = at(offset - sps);
let mut out = Vec::with_capacity(n_sym);
for n in 0..n_sym {
let cur = sample(n);
out.push(usize::from((cur * prev.conj()).im > 0.0));
prev = cur;
}
Ok(out)
}
pub fn min_distance2(&self, max_len: usize) -> Result<f64> {
let (m, l) = (self.cfg.order, self.cfg.length);
let n_diff = 2 * m - 1;
let res = 256usize;
let h = self.h();
let unit = i64::from(self.cfg.h_den) * 2;
let mut best = f64::INFINITY;
if max_len == 0 {
return Ok(best);
}
let n_codes = u32::try_from(max_len)
.ok()
.and_then(|e| n_diff.checked_pow(e))
.filter(|&n| n <= Cpm::MAX_DIFFERENCE_SEQUENCES)
.ok_or_else(|| {
Error::Config(format!(
"{n_diff}^{max_len} difference sequences exceed the {} of the search",
Cpm::MAX_DIFFERENCE_SEQUENCES
))
})?;
let mut gamma = vec![0i64; max_len];
for code in 0..n_codes {
let mut c = code;
for g in &mut gamma {
*g = (c % n_diff) as i64 - (m as i64 - 1);
c /= n_diff;
}
if gamma[0] == 0 {
continue;
}
let total: i64 = gamma.iter().sum();
if (i64::from(self.cfg.h_num) * 2 * total).rem_euclid(unit) != 0 {
continue;
}
let span = max_len + l - 1;
let integral = comnum_math::simpson(
|t| {
let dphi: f64 = gamma
.iter()
.enumerate()
.map(|(i, &g)| 2.0 * g as f64 * self.cfg.pulse.phase_response(t - i as f64, l))
.sum::<f64>()
* TAU
* h;
1.0 - dphi.cos()
},
0.0,
span as f64,
span * res,
);
best = best.min((m as f64).log2() * integral);
}
Ok(best)
}
}
pub fn msk_precode(bits: &[u8]) -> Vec<usize> {
let mut d_prev = 1i8;
bits.iter()
.map(|&b| {
let d = if b & 1 == 1 { 1i8 } else { -1 };
let a = d_prev * d;
d_prev = d;
usize::from(a > 0)
})
.collect()
}
pub fn msk_bits(phase_states: &[usize]) -> Vec<u8> {
phase_states.iter().enumerate().skip(1).map(|(n, &s)| u8::from((s + 4 - n % 4) % 4 == 0)).collect()
}