use std::time::{SystemTime, UNIX_EPOCH};
pub struct LcgRng {
state: u64,
a: u64,
c: u64,
m: u64,
}
impl LcgRng {
pub fn new(seed: u64) -> Self {
LcgRng {
state: seed,
a: 6364136223846793005,
c: 1442695040888963407,
m: u64::MAX,
}
}
pub fn gen_range(&mut self, range: std::ops::Range<f64>) -> f64 {
let random_u64 = self.next_u64();
let random_f64 = random_u64 as f64 / u64::MAX as f64;
range.start + random_f64 * (range.end - range.start)
}
fn next_u64(&mut self) -> u64 {
self.state = (self.a.wrapping_mul(self.state).wrapping_add(self.c)) % self.m;
self.state
}
}
fn estimate_alpha_beta_from_owd<'a, I>(x: I) -> (f64, f64)
where
I: IntoIterator<Item = &'a f64>,
I::IntoIter: ExactSizeIterator + Clone,
{
let mut x_vec: Vec<_> = x.into_iter().cloned().collect();
let n = x_vec.len() as f64;
x_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median_x = if x_vec.len() % 2 == 0 {
let mid = x_vec.len() / 2;
(x_vec[mid - 1] + x_vec[mid]) / 2.0
} else {
x_vec[x_vec.len() / 2]
};
let sum_sq_diff = x_vec.iter().map(|&xi| (xi - median_x).powi(2)).sum::<f64>();
let var_x = sum_sq_diff / (n - 1.0);
let alpha = median_x.powi(2) / var_x;
let beta = var_x / median_x;
(alpha, beta)
}
fn sort_values<'a, I>(values: I) -> Vec<f64>
where
I: IntoIterator<Item = &'a f64>,
I::IntoIter: ExactSizeIterator + Clone,
{
let mut sorted_values: Vec<_> = values.into_iter().cloned().collect();
sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
sorted_values
}
fn marsaglia_polar_sample(rng: &mut LcgRng) -> f64 {
loop {
let u: f64 = rng.gen_range(-1.0..1.0);
let v: f64 = rng.gen_range(-1.0..1.0);
let s = u * u + v * v;
if s < 1.0 && s != 0.0 {
let z0 = u * (-2.0 * s.ln() / s).sqrt();
return z0;
}
}
}
fn generate_random_gamma_values(alpha: f64, beta: f64, num_samples: usize, seed: u64) -> Vec<f64> {
let mut rng = LcgRng::new(seed);
(0..num_samples)
.map(|_| {
let d = alpha - 1.0 / 3.0;
let c = (1.0 / 3.0) / d.sqrt();
loop {
let x = marsaglia_polar_sample(&mut rng);
let v = 1.0 + c * x;
if v <= 0.0 {
continue;
}
let v = v * v * v;
let u = rng.gen_range(0.0..1.0);
let x_squared = x * x;
if u < 1.0 - 0.0331 * x_squared * x_squared
|| u.ln() < 0.5 * x_squared + d * (1.0 - v + v.ln())
{
break d * v * beta;
}
}
})
.collect()
}
pub fn estimate<I>(time_values: I) -> f64
where
I: IntoIterator<Item = f64>,
{
let time_values_vec: Vec<f64> = time_values.into_iter().collect();
let n = time_values_vec.len();
let (mut alpha, beta) = estimate_alpha_beta_from_owd(&time_values_vec);
if alpha > 4.0 {
alpha = 4.0;
} else if alpha < 1.0 {
alpha = 1.0;
}
let random_values = generate_random_gamma_values(alpha, beta, n, get_time_based_seed());
let sorted = sort_values(&time_values_vec);
let mut random_sorted = random_values;
random_sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
estimate_offset(&sorted, &random_sorted)
}
fn estimate_offset(x_sort: &[f64], y: &[f64]) -> f64 {
let n = x_sort.len();
let mut y_regression = Vec::new();
let mut x_regression = Vec::new();
for i in 0..n {
let rank = (i + 1) as f64;
let p_value = (rank - 0.5) / n as f64;
y_regression.push(y[i]);
x_regression.push(x_sort[i] - p_value);
}
let beta = {
let x_mean = x_regression.iter().sum::<f64>() / x_regression.len() as f64;
let y_mean = y_regression.iter().sum::<f64>() / y_regression.len() as f64;
let numerator = x_regression
.iter()
.zip(y_regression.iter())
.map(|(x, y)| (x - x_mean) * (y - y_mean))
.sum::<f64>();
let denominator = x_regression
.iter()
.map(|x| (x - x_mean).powi(2))
.sum::<f64>();
numerator / denominator
};
let gamma = {
let y_mean = y_regression.iter().sum::<f64>() / y_regression.len() as f64;
let x_mean = x_regression.iter().sum::<f64>() / x_regression.len() as f64;
y_mean - beta * x_mean
};
-gamma / beta
}
fn get_time_based_seed() -> u64 {
let now = SystemTime::now();
let duration = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
duration.as_secs() ^ duration.subsec_nanos() as u64
}