use std::time::Duration;
fn phi_from_prob(x: f64) -> f64 {
-f64::log10(x)
}
pub struct PingWindow {
n: usize,
sum: f64,
sum2: f64,
}
impl PingWindow {
pub fn new(interval: Duration) -> Self {
let sum = interval.as_millis() as f64;
Self {
n: 1,
sum,
sum2: 0.,
}
}
pub fn add_ping(&mut self, du: Duration) {
if self.n == 10000 {
self.sum = self.sum / self.n as f64 * (self.n - 1) as f64;
self.sum2 = self.sum2 / self.n as f64 * (self.n - 1) as f64;
self.n -= 1;
}
let v = du.as_millis() as f64;
self.sum += v;
self.n += 1;
let mu = self.sum / self.n as f64;
self.sum2 += (v - mu) * (v - mu);
}
pub fn normal_dist(&self) -> NormalDist {
let n = self.n;
let mu = self.sum / n as f64;
let sigma = f64::sqrt(self.sum2 / n as f64);
NormalDist { mu, sigma }
}
}
pub struct NormalDist {
mu: f64,
sigma: f64,
}
impl NormalDist {
pub fn mu(&self) -> Duration {
Duration::from_millis(self.mu as u64)
}
pub fn sigma(&self) -> Duration {
Duration::from_millis(self.sigma as u64)
}
fn integral(&self, x: f64) -> f64 {
let sigma = if self.sigma < 1. { 1. } else { self.sigma };
let y = (x - self.mu) / sigma;
let e = f64::exp(-y * (1.5976 + 0.070566 * y * y));
let out = if x > self.mu {
e / (1. + e)
} else {
1. - 1. / (1. + e)
};
assert!(0. <= out && out <= 1.);
out
}
pub fn phi(&self, elapsed: Duration) -> f64 {
let x = elapsed.as_millis() as f64;
let y = self.integral(x);
phi_from_prob(y)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[tokio::test]
async fn test_phi_detector() {
let mut window = PingWindow::new(Duration::from_secs(5));
for _ in 0..100 {
window.add_ping(Duration::from_millis(100));
}
let last_ping = Instant::now();
loop {
let dist = window.normal_dist();
let phi = dist.phi(Instant::now() - last_ping);
dbg!(phi);
if phi > 10. {
break;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
}
#[test]
fn test_values() {
let window = PingWindow::new(Duration::from_secs(5));
let dist = window.normal_dist();
dbg!(dist.mu());
dbg!(dist.sigma());
}
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(100000))]
#[test]
fn test_integral(mu: f64, sigma: f64, x: f64) {
let dist = NormalDist { mu, sigma };
let y = dist.integral(x);
assert!(0. <= y && y <= 1.);
}
}
}