entrenar/optim/dp/gradient.rs
1//! Gradient operations for differential privacy.
2
3use rand::Rng;
4use std::f64::consts::PI;
5
6/// Clip gradient to max norm (per-sample)
7pub fn clip_gradient(grad: &[f64], max_norm: f64) -> Vec<f64> {
8 let norm: f64 = grad.iter().map(|x| x.powi(2)).sum::<f64>().sqrt();
9
10 if norm > max_norm {
11 let scale = max_norm / norm;
12 grad.iter().map(|x| x * scale).collect()
13 } else {
14 grad.to_vec()
15 }
16}
17
18/// Compute L2 norm of gradient
19pub fn grad_norm(grad: &[f64]) -> f64 {
20 grad.iter().map(|x| x.powi(2)).sum::<f64>().sqrt()
21}
22
23/// Add Gaussian noise to gradient
24pub fn add_gaussian_noise<R: Rng>(grad: &[f64], std_dev: f64, rng: &mut R) -> Vec<f64> {
25 grad.iter()
26 .map(|&x| {
27 // Box-Muller transform for Gaussian noise
28 let u1: f64 = rng.random::<f64>().max(1e-10);
29 let u2: f64 = rng.random::<f64>();
30 let noise = (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos() * std_dev;
31 x + noise
32 })
33 .collect()
34}