use super::super::Cf64;
use crate::portable_math::PortableFloat;
pub fn nwpr_power_ratio(window: &[Cf64]) -> Option<f64> {
let wbp: f64 = window.iter().map(|p| p.re * p.re + p.im * p.im).sum();
if window.is_empty() || wbp <= 0.0 {
return None;
}
let s = window.iter().fold(Cf64::default(), |a, &p| a + p);
Some((s.re * s.re + s.im * s.im) / wbp)
}
pub fn nwpr_cn0_from_ratio(mu: f64, k: usize, t_s: f64) -> Option<f64> {
let k = k as f64;
if !(mu > 1.0 && mu < k && t_s > 0.0) {
return None;
}
Some(10.0 * ((mu - 1.0) / ((k - mu) * t_s)).plog10())
}
pub fn nwpr_cn0_dbhz(prompts: &[Cf64], k: usize, t_s: f64) -> Option<f64> {
if k < 2 {
return None;
}
let ratios: Vec<f64> = prompts
.chunks_exact(k)
.filter_map(nwpr_power_ratio)
.collect();
if ratios.is_empty() {
return None;
}
let mu = ratios.iter().sum::<f64>() / ratios.len() as f64;
nwpr_cn0_from_ratio(mu, k, t_s)
}
pub fn beaulieu_term(prev: Cf64, cur: Cf64) -> Option<f64> {
let den = 0.5 * (prev.re * prev.re + cur.re * cur.re);
if den <= 0.0 {
return None;
}
let d = cur.re.abs() - prev.re.abs();
Some(d * d / den)
}
pub fn beaulieu_cn0_from_term(mean_term: f64, t_s: f64) -> Option<f64> {
if !(mean_term > 0.0 && t_s > 0.0) {
return None;
}
Some(10.0 * (1.0 / (mean_term * t_s)).plog10())
}
pub fn beaulieu_cn0_dbhz(prompts: &[Cf64], t_s: f64) -> Option<f64> {
let terms: Vec<f64> = prompts
.windows(2)
.filter_map(|w| beaulieu_term(w[0], w[1]))
.collect();
if terms.is_empty() {
return None;
}
beaulieu_cn0_from_term(terms.iter().sum::<f64>() / terms.len() as f64, t_s)
}
pub fn phase_lock_indicator(window: &[Cf64]) -> f64 {
let s = window.iter().fold(Cf64::default(), |a, &p| a + p);
let nbp = s.re * s.re + s.im * s.im;
if nbp == 0.0 {
0.0
} else {
(s.re * s.re - s.im * s.im) / nbp
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BitSyncConfig {
pub min_votes: u32,
pub ratio: f64,
}
impl Default for BitSyncConfig {
fn default() -> Self {
Self {
min_votes: 12,
ratio: 3.0,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BitSync {
cfg: BitSyncConfig,
hist: Vec<u32>,
prev_sign: Option<bool>,
edge: Option<usize>,
}
impl BitSync {
pub fn new(periods_per_bit: usize, cfg: BitSyncConfig) -> Self {
Self {
cfg,
hist: vec![0; periods_per_bit.max(2)],
prev_sign: None,
edge: None,
}
}
pub fn push(&mut self, period_index: u64, prompt: Cf64) -> Option<usize> {
if self.edge.is_some() {
return self.edge;
}
let s = prompt.re >= 0.0;
if let Some(prev) = self.prev_sign {
if prev != s {
let n = self.hist.len() as u64;
self.hist[(period_index % n) as usize] += 1;
}
}
self.prev_sign = Some(s);
let (mut best, mut bi, mut second) = (0u32, 0usize, 0u32);
for (i, &v) in self.hist.iter().enumerate() {
if v > best {
second = best;
best = v;
bi = i;
} else if v > second {
second = v;
}
}
if best >= self.cfg.min_votes && best as f64 >= self.cfg.ratio * second as f64 {
self.edge = Some(bi);
}
self.edge
}
pub fn edge(&self) -> Option<usize> {
self.edge
}
pub fn histogram(&self) -> &[u32] {
&self.hist
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noiseless_windows_hit_the_limits() {
let w = vec![Cf64::new(2.0, 0.0); 20];
assert!((nwpr_power_ratio(&w).unwrap() - 20.0).abs() < 1e-12);
assert!(nwpr_cn0_from_ratio(20.0, 20, 1e-3).is_none());
assert!((phase_lock_indicator(&w) - 1.0).abs() < 1e-12);
let q = vec![Cf64::new(0.0, 2.0); 20];
assert!((phase_lock_indicator(&q) + 1.0).abs() < 1e-12);
}
#[test]
fn bit_sync_finds_a_clean_edge() {
let mut bs = BitSync::new(20, BitSyncConfig::default());
let mut edge = None;
for k in 0..2000u64 {
let bit = if ((k + 20 - 7) / 20) % 3 == 1 {
-1.0
} else {
1.0
};
edge = bs.push(k, Cf64::new(bit, 0.0));
}
assert_eq!(edge, Some(7));
}
}