use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
pub struct LaplaceMechanism {
epsilon: f64,
rng: ChaCha8Rng,
}
impl LaplaceMechanism {
pub fn new(epsilon: f64) -> Self {
Self {
epsilon,
rng: rand::make_rng::<ChaCha8Rng>(),
}
}
pub fn with_seed(epsilon: f64, seed: u64) -> Self {
Self {
epsilon,
rng: ChaCha8Rng::seed_from_u64(seed),
}
}
pub fn add_noise(&mut self, value: f64, sensitivity: f64, epsilon: f64) -> f64 {
let scale = sensitivity / epsilon;
let noise = self.sample_laplace(scale);
value + noise
}
pub fn add_noise_to_count(&mut self, count: u64, epsilon: f64) -> u64 {
let noised = self.add_noise(count as f64, 1.0, epsilon);
noised.max(0.0).round() as u64
}
fn sample_laplace(&mut self, scale: f64) -> f64 {
let u: f64 = self.rng.random();
let sign = if u < 0.5 { -1.0 } else { 1.0 };
let abs_u = (u - 0.5).abs();
sign * scale * (1.0 - 2.0 * abs_u).ln()
}
pub fn epsilon(&self) -> f64 {
self.epsilon
}
}
pub struct GaussianMechanism {
epsilon: f64,
delta: f64,
rng: ChaCha8Rng,
}
impl GaussianMechanism {
pub fn new(epsilon: f64, delta: f64) -> Self {
Self {
epsilon,
delta,
rng: rand::make_rng::<ChaCha8Rng>(),
}
}
pub fn add_noise(&mut self, value: f64, sensitivity: f64, epsilon: f64) -> f64 {
let sigma = sensitivity * (2.0 * (1.25 / self.delta).ln()).sqrt() / epsilon;
let noise = self.sample_gaussian(sigma);
value + noise
}
pub fn epsilon(&self) -> f64 {
self.epsilon
}
fn sample_gaussian(&mut self, sigma: f64) -> f64 {
let u1: f64 = self.rng.random();
let u2: f64 = self.rng.random();
let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
z * sigma
}
}
pub mod sensitivity {
pub const COUNT: f64 = 1.0;
pub fn sum(min_value: f64, max_value: f64) -> f64 {
max_value - min_value
}
pub fn mean(min_value: f64, max_value: f64, n: usize) -> f64 {
(max_value - min_value) / n as f64
}
pub const HISTOGRAM: f64 = 2.0; }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_laplace_noise() {
let mut mechanism = LaplaceMechanism::with_seed(1.0, 42);
let value = 100.0;
let noised1 = mechanism.add_noise(value, 1.0, 1.0);
let mut mechanism2 = LaplaceMechanism::with_seed(1.0, 42);
let noised2 = mechanism2.add_noise(value, 1.0, 1.0);
assert_eq!(noised1, noised2);
}
#[test]
fn test_laplace_preserves_rough_magnitude() {
let mut mechanism = LaplaceMechanism::with_seed(1.0, 42);
let value = 1000.0;
let noised = mechanism.add_noise(value, 1.0, 1.0);
assert!((noised - value).abs() < 100.0);
}
}