use statrs::function::erf;
pub(super) fn get_analytic_gaussian_sigma(sensitivity: f64, epsilon: f64, delta: f64) -> f64 {
let delta_0 = b_neg(epsilon, 0.);
let alpha = if delta == delta_0 {
1.
} else {
let (s_inf, s_sup) = doubling_trick(epsilon, delta, delta_0);
let tol: f64 = 1e-10f64;
let s_final = binary_search(s_inf, s_sup, epsilon, delta, delta_0, tol);
let sign = if delta > delta_0 { -1. } else { 1. };
(1. + s_final / 2.).sqrt() + sign * (s_final / 2.).sqrt()
};
alpha * sensitivity / (2. * epsilon).sqrt()
}
fn binary_search(
mut s_inf: f64, mut s_sup: f64, epsilon: f64, delta: f64, delta_0: f64, tol: f64,
) -> f64 {
let s_to_delta = |s: f64| if delta > delta_0 {
b_neg(epsilon, s)
} else {
b_pos(epsilon, s)
};
loop {
let s_mid = s_inf + (s_sup - s_inf) / 2.;
let delta_prime = s_to_delta(s_mid);
let diff = delta_prime - delta;
if (diff.abs() <= tol) && (diff <= 0.) { return s_mid }
let is_left = if delta > delta_0 {
delta_prime > delta
} else {
delta_prime < delta
};
if is_left {
s_sup = s_mid;
} else {
s_inf = s_mid;
}
}
}
fn doubling_trick(
epsilon: f64, delta: f64, delta_0: f64,
) -> (f64, f64) {
let mut s_inf: f64 = 0.;
let mut s_sup: f64 = 1.;
let predicate = |s: f64| if delta > delta_0 {
b_neg(epsilon, s) < delta
} else {
b_pos(epsilon, s) > delta
};
while predicate(s_sup) {
s_inf = s_sup;
s_sup = 2.0 * s_inf;
}
(s_inf, s_sup)
}
fn b_neg(epsilon: f64, s: f64) -> f64 {
phi((epsilon * s).sqrt()) - epsilon.exp() * phi(-(epsilon * (s + 2.)).sqrt())
}
fn b_pos(epsilon: f64, s: f64) -> f64 {
phi(-(epsilon * s).sqrt()) - epsilon.exp() * phi(-(epsilon * (s + 2.)).sqrt())
}
fn phi(t: f64) -> f64 {
0.5 * (1. + erf::erf(t / 2.0_f64.sqrt()))
}