use crate::common::NumStdDev;
use crate::hll::composite_interpolation;
use crate::hll::cubic_interpolation;
use crate::hll::harmonic_numbers;
#[derive(Debug, Clone, PartialEq)]
pub struct HipEstimator {
hip_accum: f64,
kxq0: f64,
kxq1: f64,
out_of_order: bool,
}
impl HipEstimator {
pub fn new(lg_config_k: u8) -> Self {
let k = 1 << lg_config_k;
Self {
hip_accum: 0.0,
kxq0: k as f64, kxq1: 0.0,
out_of_order: false,
}
}
pub fn update(&mut self, lg_config_k: u8, old_value: u8, new_value: u8) {
let k = (1 << lg_config_k) as f64;
if !self.out_of_order {
self.hip_accum += k / (self.kxq0 + self.kxq1);
}
self.update_kxq(old_value, new_value);
}
fn update_kxq(&mut self, old_value: u8, new_value: u8) {
if old_value < 32 {
self.kxq0 -= inv_pow2(old_value);
} else {
self.kxq1 -= inv_pow2(old_value);
}
if new_value < 32 {
self.kxq0 += inv_pow2(new_value);
} else {
self.kxq1 += inv_pow2(new_value);
}
}
pub fn estimate(&self, lg_config_k: u8, cur_min: u8, num_at_cur_min: u32) -> f64 {
if self.out_of_order {
self.get_composite_estimate(lg_config_k, cur_min, num_at_cur_min)
} else {
self.hip_accum
}
}
pub fn upper_bound(
&self,
lg_config_k: u8,
cur_min: u8,
num_at_cur_min: u32,
num_std_dev: NumStdDev,
) -> f64 {
let estimate = self.estimate(lg_config_k, cur_min, num_at_cur_min);
let rse = get_rel_err(lg_config_k, true, self.out_of_order, num_std_dev);
estimate / (1.0 + rse)
}
pub fn lower_bound(
&self,
lg_config_k: u8,
cur_min: u8,
num_at_cur_min: u32,
num_std_dev: NumStdDev,
) -> f64 {
let estimate = self.estimate(lg_config_k, cur_min, num_at_cur_min);
let rse = get_rel_err(lg_config_k, false, self.out_of_order, num_std_dev);
estimate / (1.0 + rse)
}
fn get_raw_estimate(&self, lg_config_k: u8) -> f64 {
let k = (1 << lg_config_k) as f64;
let correction_factor = match lg_config_k {
4 => 0.673,
5 => 0.697,
6 => 0.709,
_ => 0.7213 / (1.0 + 1.079 / k),
};
(correction_factor * k * k) / (self.kxq0 + self.kxq1)
}
fn get_bitmap_estimate(&self, lg_config_k: u8, cur_min: u8, num_at_cur_min: u32) -> f64 {
let k = 1 << lg_config_k;
let num_unhit = if cur_min == 0 { num_at_cur_min } else { 0 };
if num_unhit == 0 {
return (k as f64) * (k as f64 / 0.5).ln();
}
let num_hit = k - num_unhit;
harmonic_numbers::bitmap_estimate(k, num_hit)
}
fn get_composite_estimate(&self, lg_config_k: u8, cur_min: u8, num_at_cur_min: u32) -> f64 {
let raw_est = self.get_raw_estimate(lg_config_k);
let x_arr = composite_interpolation::get_x_arr(lg_config_k);
let x_arr_len = composite_interpolation::get_x_arr_length();
let y_stride = composite_interpolation::get_y_stride(lg_config_k) as f64;
if raw_est < x_arr[0] {
return 0.0;
}
let x_arr_len_m1 = x_arr_len - 1;
if raw_est > x_arr[x_arr_len_m1] {
let final_y = y_stride * (x_arr_len_m1 as f64);
let factor = final_y / x_arr[x_arr_len_m1];
return raw_est * factor;
}
let adj_est = cubic_interpolation::using_x_arr_and_y_stride(x_arr, y_stride, raw_est);
let k = 1 << lg_config_k;
if adj_est > (3 * k) as f64 {
return adj_est;
}
let lin_est = self.get_bitmap_estimate(lg_config_k, cur_min, num_at_cur_min);
let avg_est = (adj_est + lin_est) / 2.0;
let crossover = match lg_config_k {
4 => 0.718,
5 => 0.672,
_ => 0.64,
};
let threshold = crossover * (k as f64);
if avg_est > threshold {
adj_est
} else {
lin_est
}
}
pub fn hip_accum(&self) -> f64 {
self.hip_accum
}
pub fn kxq0(&self) -> f64 {
self.kxq0
}
pub fn kxq1(&self) -> f64 {
self.kxq1
}
pub fn is_out_of_order(&self) -> bool {
self.out_of_order
}
pub fn set_out_of_order(&mut self, ooo: bool) {
self.out_of_order = ooo;
if ooo {
self.hip_accum = 0.0;
}
}
pub fn set_hip_accum(&mut self, value: f64) {
self.hip_accum = value;
}
pub fn set_kxq0(&mut self, value: f64) {
self.kxq0 = value;
}
pub fn set_kxq1(&mut self, value: f64) {
self.kxq1 = value;
}
}
#[inline]
fn inv_pow2(value: u8) -> f64 {
if value == 0 {
1.0
} else if value <= 63 {
1.0 / (1u64 << value) as f64
} else {
f64::exp2(-(value as f64))
}
}
fn get_rel_err(lg_config_k: u8, upper_bound: bool, ooo: bool, num_std_dev: NumStdDev) -> f64 {
if lg_config_k > 12 {
let rse_factor = if ooo {
1.03896 } else {
0.8325546 };
let k = (1 << lg_config_k) as f64;
let sign = if upper_bound { -1.0 } else { 1.0 };
return sign * (num_std_dev as u8 as f64) * rse_factor / k.sqrt();
}
let idx = ((lg_config_k as usize) - 4) * 3 + ((num_std_dev as usize) - 1);
match (ooo, upper_bound) {
(false, false) => HIP_LB[idx], (false, true) => HIP_UB[idx], (true, false) => NON_HIP_LB[idx], (true, true) => NON_HIP_UB[idx], }
}
const HIP_LB: [f64; 27] = [
0.207316195,
0.502865572,
0.882303765, 0.146981579,
0.335426881,
0.557052, 0.104026721,
0.227683872,
0.365888317, 0.073614601,
0.156781585,
0.245740374, 0.05205248,
0.108783763,
0.168030442, 0.036770852,
0.075727545,
0.11593785, 0.025990219,
0.053145536,
0.080772263, 0.018373987,
0.037266176,
0.056271814, 0.012936253,
0.02613829,
0.039387631, ];
const HIP_UB: [f64; 27] = [
-0.207805347,
-0.355574279,
-0.475535095, -0.146988328,
-0.262390832,
-0.360864026, -0.103877775,
-0.191503663,
-0.269311582, -0.073452978,
-0.138513438,
-0.198487447, -0.051982806,
-0.099703123,
-0.144128618, -0.036768609,
-0.07138158,
-0.104430324, -0.025991325,
-0.050854296,
-0.0748143, -0.01834533,
-0.036121138,
-0.05327616, -0.012920332,
-0.025572893,
-0.037896952, ];
const NON_HIP_LB: [f64; 27] = [
0.254409839,
0.682266712,
1.304022158, 0.181817353,
0.443389054,
0.778776219, 0.129432281,
0.295782195,
0.49252279, 0.091640655,
0.201175925,
0.323664385, 0.064858051,
0.138523393,
0.218805328, 0.045851855,
0.095925072,
0.148635751, 0.032454144,
0.067009668,
0.102660669, 0.022921382,
0.046868565,
0.071307398, 0.016155679,
0.032825719,
0.049677541, ];
const NON_HIP_UB: [f64; 27] = [
-0.256980172,
-0.411905944,
-0.52651057, -0.182332109,
-0.310275547,
-0.412660505, -0.129314228,
-0.230142294,
-0.315636197, -0.091584836,
-0.16834013,
-0.236346847, -0.06487411,
-0.122045231,
-0.174112107, -0.04591465,
-0.08784505,
-0.126917615, -0.032433119,
-0.062897613,
-0.091862929, -0.022960633,
-0.044875401,
-0.065736049, -0.016186662,
-0.031827816,
-0.046973459, ];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_estimator_initialization() {
let est = HipEstimator::new(10);
assert_eq!(est.hip_accum(), 0.0);
assert_eq!(est.kxq0(), 1024.0); assert_eq!(est.kxq1(), 0.0);
assert!(!est.is_out_of_order());
}
#[test]
fn test_estimator_update() {
let mut est = HipEstimator::new(8);
est.update(8, 0, 10);
assert!(est.hip_accum() > 0.0);
assert!(est.kxq0() < 256.0);
assert_eq!(est.kxq1(), 0.0); }
#[test]
fn test_kxq_split() {
let mut est = HipEstimator::new(8);
est.update(8, 0, 10);
let kxq0_after_10 = est.kxq0();
let kxq1_after_10 = est.kxq1();
assert!(kxq0_after_10 < 256.0);
assert_eq!(kxq1_after_10, 0.0);
est.update(8, 10, 50);
let kxq0_after_50 = est.kxq0();
let kxq1_after_50 = est.kxq1();
assert!(kxq0_after_50 < kxq0_after_10); assert!(kxq1_after_50 > 0.0); }
#[test]
fn test_out_of_order_flag() {
let mut est = HipEstimator::new(10);
est.update(8, 0, 5);
let hip_normal = est.hip_accum();
assert!(hip_normal > 0.0);
est.set_out_of_order(true);
assert!(est.is_out_of_order());
assert_eq!(est.hip_accum(), 0.0);
let kxq0_before = est.kxq0();
est.update(8, 5, 10);
assert_eq!(est.hip_accum(), 0.0); assert_ne!(est.kxq0(), kxq0_before); }
#[test]
fn test_setters() {
let mut est = HipEstimator::new(10);
est.set_hip_accum(123.45);
est.set_kxq0(678.9);
est.set_kxq1(0.0012);
assert_eq!(est.hip_accum(), 123.45);
assert_eq!(est.kxq0(), 678.9);
assert_eq!(est.kxq1(), 0.0012);
}
}