use super::complex::Complex64;
use core::f64::consts::TAU;
pub const ANCHOR_INTERVAL: u64 = 1024;
#[derive(Debug, Clone)]
pub struct RunningDft {
freqs: Vec<f64>,
t0: f64,
dt: f64,
n: u64,
acc: Vec<Complex64>,
phasor: Vec<Complex64>,
step: Vec<Complex64>,
}
impl RunningDft {
pub fn new(freqs: impl Into<Vec<f64>>, t0: f64, dt: f64) -> Self {
let freqs = freqs.into();
let step = freqs
.iter()
.map(|&f| Complex64::cis(-TAU * f * dt))
.collect();
let mut s = RunningDft {
acc: vec![Complex64::ZERO; freqs.len()],
phasor: vec![Complex64::ZERO; freqs.len()],
step,
freqs,
t0,
dt,
n: 0,
};
s.anchor();
s
}
fn anchor(&mut self) {
let n = self.n as f64;
for (p, &f) in self.phasor.iter_mut().zip(&self.freqs) {
*p = Complex64::cis(-TAU * phase_cycles(f, self.t0, self.dt, n));
}
}
pub fn push(&mut self, x: f64) {
for ((a, p), s) in self.acc.iter_mut().zip(&mut self.phasor).zip(&self.step) {
*a += *p * x;
*p *= *s;
}
self.n += 1;
if self.n.is_multiple_of(ANCHOR_INTERVAL) {
self.anchor();
}
}
pub fn extend(&mut self, xs: impl IntoIterator<Item = f64>) {
for x in xs {
self.push(x);
}
}
pub fn next_time(&self) -> f64 {
self.time_of(self.n)
}
pub fn time_of(&self, n: u64) -> f64 {
self.t0 + n as f64 * self.dt
}
pub fn len(&self) -> u64 {
self.n
}
pub fn is_empty(&self) -> bool {
self.n == 0
}
pub fn freqs(&self) -> &[f64] {
&self.freqs
}
pub fn t0(&self) -> f64 {
self.t0
}
pub fn dt(&self) -> f64 {
self.dt
}
pub fn get(&self, k: usize) -> Complex64 {
self.acc[k] * self.dt
}
pub fn result(&self) -> Vec<Complex64> {
self.acc.iter().map(|&a| a * self.dt).collect()
}
pub fn reset(&mut self) {
self.n = 0;
self.acc.fill(Complex64::ZERO);
self.anchor();
}
}
pub(crate) fn phase_cycles(f: f64, t0: f64, dt: f64, n: f64) -> f64 {
let a = f * dt;
let p = a * n;
let e = a.mul_add(n, -p);
let c = f * t0;
(p.fract() + e + c.fract()).fract()
}
pub fn dft(t0: f64, dt: f64, samples: &[f64], freqs: &[f64]) -> Vec<Complex64> {
let mut d = RunningDft::new(freqs, t0, dt);
d.extend(samples.iter().copied());
d.result()
}
#[cfg(test)]
mod tests {
use super::*;
fn naive(t0: f64, dt: f64, xs: &[f64], f: f64) -> Complex64 {
xs.iter()
.enumerate()
.map(|(n, &x)| Complex64::cis(-TAU * f * (t0 + n as f64 * dt)) * x)
.sum::<Complex64>()
* dt
}
#[test]
fn matches_naive_sum() {
let dt = 1.7e-12;
let t0 = 0.5 * dt;
let xs: Vec<f64> = (0..5000)
.map(|n| ((n * 7919) % 1000) as f64 / 1000.0 - 0.5)
.collect();
let freqs = [0.0, 1.3e9, 7.77e9, 41e9];
let got = dft(t0, dt, &xs, &freqs);
for (g, &f) in got.iter().zip(&freqs) {
let r = naive(t0, dt, &xs, f);
assert!(
(*g - r).norm() < 1e-12 * xs.len() as f64 * dt,
"{f}: {g} vs {r}"
);
}
}
#[test]
fn dc_and_impulse() {
let d = dft(0.0, 2.0, &[1.0, 1.0, 1.0], &[0.0]);
assert_eq!(d[0], Complex64::new(6.0, 0.0));
let t0 = 0.125;
let d = dft(t0, 1.0, &[1.0], &[1.0]);
let expect = Complex64::cis(-TAU * t0);
assert!((d[0] - expect).norm() < 1e-15);
}
#[test]
fn running_matches_batch_and_reset() {
let xs: Vec<f64> = (0..3000).map(|n| (n as f64 * 0.01).sin()).collect();
let freqs = vec![1.0, 2.5, 10.0];
let mut r = RunningDft::new(freqs.clone(), 0.0, 0.01);
assert!(r.is_empty());
r.extend(xs.iter().copied());
assert_eq!(r.len(), 3000);
assert_eq!(r.result(), dft(0.0, 0.01, &xs, &freqs));
assert_eq!(r.get(1), r.result()[1]);
assert_eq!(r.freqs(), &freqs[..]);
r.reset();
assert!(r.is_empty());
assert_eq!(r.result(), vec![Complex64::ZERO; 3]);
r.extend(xs.iter().copied());
assert_eq!(r.result(), dft(0.0, 0.01, &xs, &freqs));
}
#[test]
fn time_axis() {
let r = RunningDft::new(vec![1.0], 0.5, 2.0);
assert_eq!(r.next_time(), 0.5);
assert_eq!(r.time_of(3), 6.5);
assert_eq!((r.t0(), r.dt()), (0.5, 2.0));
}
#[test]
fn no_drift_over_many_samples() {
let dt = 1e-12;
let f = 0.125 / dt; let n_samples: u64 = 2_001_000;
let phi = 0.3;
for t0 in [0.0, 0.5 * dt] {
let c0 = f * t0; let mut r = RunningDft::new(vec![f], t0, dt);
for n in 0..n_samples {
r.push((TAU * (c0 + (n % 8) as f64 / 8.0) + phi).cos());
}
let exact = Complex64::cis(-TAU * (c0 + (n_samples % 8) as f64 / 8.0));
let perr = (r.phasor[0] - exact).norm();
assert!(perr < 1e-13, "t0={t0}: phasor error {perr}");
let expect = Complex64::cis(phi) * (0.5 * n_samples as f64 * dt);
let err = (r.get(0) - expect).norm() / expect.norm();
assert!(err < 1e-11, "t0={t0}: relative error {err}");
}
}
#[test]
fn phase_reduction_is_accurate_for_large_n() {
let a = 0.1_f64;
let n: u64 = 123_456_789_013;
let bits = a.to_bits();
let m = (bits & ((1 << 52) - 1)) | (1 << 52);
let k = 1075 - ((bits >> 52) & 0x7ff); let prod = m as u128 * n as u128;
let exact = (prod % (1u128 << k)) as f64 / (1u128 << k) as f64;
let c = phase_cycles(1.0, 0.0, a, n as f64);
let d = (c - exact).abs();
let d = d.min(1.0 - d);
assert!(d < 1e-14, "{c} vs {exact}");
let naive = (a * n as f64).fract();
assert!((naive - exact).abs() > 1e-9);
}
}