use crate::common::types::PointOffsetType;
const MAX_ESTIMATED_POINTS: usize = 1000;
fn confidence_agresti_coull_interval(trials: usize, positive: usize, total: usize) -> (i64, i64) {
let z = 2.; let n_hat = trials as f64 + z * z;
let phat = (positive as f64 + z * z / 2.) / n_hat;
let interval = z * ((phat / n_hat) * (1. - phat)).sqrt();
let expected = (phat * total as f64) as i64;
let delta = (interval * total as f64) as i64;
(expected, delta)
}
pub fn sample_check_cardinality(
sample_points: impl Iterator<Item = PointOffsetType>,
checker: impl Fn(PointOffsetType) -> bool,
threshold: usize,
total_points: usize,
) -> bool {
let mut matched_points = 0;
let mut total_checked = 0;
let mut exp = 0;
let mut interval;
for idx in sample_points.take(MAX_ESTIMATED_POINTS) {
matched_points += usize::from(checker(idx));
total_checked += 1;
let estimation =
confidence_agresti_coull_interval(total_checked, matched_points, total_points);
exp = estimation.0;
interval = estimation.1;
if exp - interval > threshold as i64 {
return true;
}
if exp + interval < threshold as i64 {
return false;
}
}
exp > threshold as i64
}
#[cfg(test)]
mod tests {
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
use super::*;
#[test]
fn test_confidence_interval() {
let mut rng = StdRng::seed_from_u64(42);
let total = 100_000;
let true_p = 0.25;
let mut delta = 100_000;
let mut positive = 0;
for i in 1..=101 {
positive += usize::from(rng.random_bool(true_p));
if i % 20 == 1 {
let interval = confidence_agresti_coull_interval(i, positive, total);
assert!(interval.1 < delta);
delta = interval.1;
eprintln!(
"confidence_agresti_coull_interval({i}, {positive}, {total}) = {interval:#?}"
);
}
}
}
#[test]
fn test_sample_check_cardinality() {
let res = sample_check_cardinality(
vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].into_iter(),
|idx| idx % 2 == 0,
10_000,
100_000,
);
assert!(res)
}
}