Skip to main content

oxilean_std/differential_privacy/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4use super::functions::*;
5
6#[allow(dead_code)]
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum NoiseType {
9    Laplace,
10    Exponential,
11    Gumbel,
12}
13/// Full Laplace mechanism with sensitivity and epsilon.
14///
15/// Adds Lap(Δf / ε) noise to the true query answer.
16pub struct LaplaceMechanism {
17    /// Global L1 sensitivity of the query.
18    pub sensitivity: f64,
19    /// Privacy parameter ε.
20    pub epsilon: f64,
21    /// Derived scale b = sensitivity / epsilon.
22    pub scale: f64,
23}
24impl LaplaceMechanism {
25    /// Create a new Laplace mechanism.
26    ///
27    /// Achieves ε-pure DP for any query with L1 sensitivity `sensitivity`.
28    pub fn new(sensitivity: f64, epsilon: f64) -> Self {
29        assert!(sensitivity > 0.0, "sensitivity must be positive");
30        assert!(epsilon > 0.0, "epsilon must be positive");
31        let scale = sensitivity / epsilon;
32        LaplaceMechanism {
33            sensitivity,
34            epsilon,
35            scale,
36        }
37    }
38    /// Apply the mechanism: add Laplace noise to a true answer using
39    /// a uniform sample u ∈ (0, 1) (caller provides randomness).
40    pub fn apply(&self, true_answer: f64, u: f64) -> f64 {
41        assert!(u > 0.0 && u < 1.0, "u must be in (0, 1)");
42        let v = u - 0.5;
43        let noise = -self.scale * v.signum() * (1.0 - 2.0 * v.abs()).ln();
44        true_answer + noise
45    }
46    /// Privacy loss ε = sensitivity / scale (recovers the configured ε).
47    pub fn privacy_loss(&self) -> f64 {
48        self.sensitivity / self.scale
49    }
50}
51/// Renyi differential privacy (alpha-RDP).
52#[allow(dead_code)]
53#[derive(Debug, Clone)]
54pub struct RenyiDp {
55    pub alpha: f64,
56    pub epsilon: f64,
57}
58impl RenyiDp {
59    #[allow(dead_code)]
60    pub fn new(alpha: f64, epsilon: f64) -> Self {
61        assert!(alpha > 1.0, "RDP order alpha must be > 1");
62        assert!(epsilon >= 0.0, "epsilon must be >= 0");
63        Self { alpha, epsilon }
64    }
65    #[allow(dead_code)]
66    pub fn to_pure_dp(&self) -> (f64, f64) {
67        let delta: f64 = 1e-5;
68        let eps_prime = self.epsilon + (1.0_f64 / delta).ln() / (self.alpha - 1.0);
69        (eps_prime, delta)
70    }
71    #[allow(dead_code)]
72    pub fn compose(&self, other: &RenyiDp) -> Option<RenyiDp> {
73        if (self.alpha - other.alpha).abs() < 1e-10 {
74            Some(RenyiDp::new(self.alpha, self.epsilon + other.epsilon))
75        } else {
76            None
77        }
78    }
79    #[allow(dead_code)]
80    pub fn gaussian_mechanism_epsilon(alpha: f64, sigma: f64, sensitivity: f64) -> Self {
81        let eps = alpha * sensitivity * sensitivity / (2.0 * sigma * sigma);
82        Self::new(alpha, eps)
83    }
84}
85/// Privacy accounting via Rényi Differential Privacy (RDP).
86pub struct RenyiAccountant {
87    /// Accumulated RDP budgets per order α.
88    /// Each entry is (alpha, epsilon_rdp).
89    pub ledger: Vec<(f64, f64)>,
90}
91impl RenyiAccountant {
92    /// Create an empty RDP accountant.
93    pub fn new() -> Self {
94        RenyiAccountant { ledger: vec![] }
95    }
96    /// Record a mechanism with (α, ε_rdp)-RDP guarantee.
97    pub fn compose(&mut self, alpha: f64, eps_rdp: f64) {
98        if let Some(entry) = self
99            .ledger
100            .iter_mut()
101            .find(|(a, _)| (*a - alpha).abs() < 1e-10)
102        {
103            entry.1 += eps_rdp;
104        } else {
105            self.ledger.push((alpha, eps_rdp));
106        }
107    }
108    /// Convert (α, ε_rdp)-RDP to (ε, δ)-DP for a given δ.
109    ///
110    /// Formula: ε = ε_rdp + log(1/δ) / (α - 1).
111    pub fn to_approx_dp(&self, alpha: f64, eps_rdp: f64, delta: f64) -> f64 {
112        assert!(alpha > 1.0, "α must be > 1 for RDP to DP conversion");
113        assert!(delta > 0.0 && delta < 1.0);
114        eps_rdp + (1.0 / delta).ln() / (alpha - 1.0)
115    }
116    /// Find the best ε across all tracked α values for a given δ.
117    pub fn optimal_eps(&self, delta: f64) -> f64 {
118        self.ledger
119            .iter()
120            .filter(|(alpha, _)| *alpha > 1.0)
121            .map(|(alpha, eps_rdp)| self.to_approx_dp(*alpha, *eps_rdp, delta))
122            .fold(f64::INFINITY, f64::min)
123    }
124}
125/// Exponential mechanism for private selection.
126#[allow(dead_code)]
127#[derive(Debug, Clone)]
128pub struct ExponentialMechanismExt {
129    pub epsilon: f64,
130    pub sensitivity: f64,
131    pub output_range_name: String,
132}
133impl ExponentialMechanismExt {
134    #[allow(dead_code)]
135    pub fn new(eps: f64, sens: f64, range: &str) -> Self {
136        Self {
137            epsilon: eps,
138            sensitivity: sens,
139            output_range_name: range.to_string(),
140        }
141    }
142    #[allow(dead_code)]
143    pub fn sampling_probability_description(&self) -> String {
144        format!(
145            "Pr[output = r] proportional to exp(epsilon * u(D, r) / (2 * sensitivity)), range={}",
146            self.output_range_name
147        )
148    }
149    #[allow(dead_code)]
150    pub fn utility_guarantee(&self, opt_utility: f64, range_size: usize) -> f64 {
151        self.sensitivity * (range_size as f64).ln() / self.epsilon + opt_utility
152    }
153    #[allow(dead_code)]
154    pub fn is_epsilon_dp(&self) -> bool {
155        true
156    }
157}
158/// Laplace distribution sampler using the inverse CDF method.
159///
160/// Draws a sample from Laplace(0, b) using uniform random bits.
161/// In production, use a cryptographically secure RNG.
162pub struct LaplaceNoise {
163    /// Scale parameter b = Δf / ε.
164    pub scale: f64,
165}
166impl LaplaceNoise {
167    /// Create a new Laplace noise generator with the given scale.
168    pub fn new(scale: f64) -> Self {
169        assert!(scale > 0.0, "Laplace scale must be positive");
170        LaplaceNoise { scale }
171    }
172    /// Sample from Laplace(0, scale) using a provided uniform sample u ∈ (0,1).
173    ///
174    /// Uses the inverse CDF: X = -scale * sign(u - 0.5) * ln(1 - 2|u - 0.5|).
175    pub fn sample_from_uniform(&self, u: f64) -> f64 {
176        assert!(u > 0.0 && u < 1.0, "u must be in (0, 1)");
177        let v = u - 0.5;
178        -self.scale * v.signum() * (1.0 - 2.0 * v.abs()).ln()
179    }
180    /// Compute the scale parameter for (ε, 0)-DP given L1 sensitivity Δf.
181    pub fn scale_for_pure_dp(sensitivity: f64, eps: f64) -> f64 {
182        assert!(eps > 0.0, "ε must be positive");
183        sensitivity / eps
184    }
185}
186/// Differentially private synthetic data generation.
187#[allow(dead_code)]
188#[derive(Debug, Clone)]
189pub struct DpSyntheticData {
190    pub epsilon: f64,
191    pub delta: f64,
192    pub num_attributes: usize,
193    pub method: SyntheticDataMethod,
194}
195impl DpSyntheticData {
196    #[allow(dead_code)]
197    pub fn new(eps: f64, delta: f64, attrs: usize, method: SyntheticDataMethod) -> Self {
198        Self {
199            epsilon: eps,
200            delta,
201            num_attributes: attrs,
202            method,
203        }
204    }
205    #[allow(dead_code)]
206    pub fn marginal_error_bound(&self) -> f64 {
207        (self.num_attributes as f64).sqrt() / (self.epsilon * 100.0)
208    }
209}
210/// Gaussian noise sampler for (ε, δ)-DP.
211pub struct GaussianNoise {
212    /// Standard deviation σ.
213    pub sigma: f64,
214}
215impl GaussianNoise {
216    /// Create a new Gaussian noise generator.
217    pub fn new(sigma: f64) -> Self {
218        assert!(sigma > 0.0, "Gaussian sigma must be positive");
219        GaussianNoise { sigma }
220    }
221    /// Compute σ for (ε, δ)-DP given L2 sensitivity Δ₂f.
222    ///
223    /// Uses the analytic Gaussian mechanism: σ = Δ₂f * sqrt(2 * ln(1.25/δ)) / ε.
224    pub fn sigma_for_approx_dp(l2_sensitivity: f64, eps: f64, delta: f64) -> f64 {
225        assert!(eps > 0.0 && delta > 0.0 && delta < 1.0);
226        l2_sensitivity * (2.0 * (1.25f64 / delta).ln()).sqrt() / eps
227    }
228    /// Box-Muller transform: given two uniform samples u1, u2 ∈ (0,1),
229    /// return a standard normal sample.
230    pub fn box_muller(u1: f64, u2: f64) -> f64 {
231        assert!(u1 > 0.0 && u2 > 0.0);
232        let r = (-2.0 * u1.ln()).sqrt();
233        let theta = 2.0 * std::f64::consts::PI * u2;
234        r * theta.cos()
235    }
236    /// Scale a standard normal sample by σ.
237    pub fn scale_sample(&self, z: f64) -> f64 {
238        self.sigma * z
239    }
240}
241/// Zero Concentrated Differential Privacy (zCDP).
242#[allow(dead_code)]
243#[derive(Debug, Clone)]
244pub struct ZcdpBound {
245    pub rho: f64,
246}
247impl ZcdpBound {
248    #[allow(dead_code)]
249    pub fn new(rho: f64) -> Self {
250        assert!(rho >= 0.0);
251        Self { rho }
252    }
253    #[allow(dead_code)]
254    pub fn to_approximate_dp(&self, delta: f64) -> f64 {
255        self.rho + 2.0 * (self.rho * (1.0 / delta).ln()).sqrt()
256    }
257    #[allow(dead_code)]
258    pub fn gaussian_mechanism_rho(sigma: f64, sensitivity: f64) -> Self {
259        Self::new(sensitivity * sensitivity / (2.0 * sigma * sigma))
260    }
261    #[allow(dead_code)]
262    pub fn compose(&self, other: &ZcdpBound) -> ZcdpBound {
263        ZcdpBound::new(self.rho + other.rho)
264    }
265}
266/// Differentially private histogram.
267#[allow(dead_code)]
268#[derive(Debug, Clone)]
269pub struct DpHistogram {
270    pub bins: usize,
271    pub epsilon: f64,
272    pub noise_mechanism: NoiseType,
273}
274impl DpHistogram {
275    #[allow(dead_code)]
276    pub fn laplace(bins: usize, eps: f64) -> Self {
277        Self {
278            bins,
279            epsilon: eps,
280            noise_mechanism: NoiseType::Laplace,
281        }
282    }
283    #[allow(dead_code)]
284    pub fn l1_sensitivity(&self) -> f64 {
285        2.0
286    }
287    #[allow(dead_code)]
288    pub fn noise_scale(&self) -> f64 {
289        self.l1_sensitivity() / self.epsilon
290    }
291    #[allow(dead_code)]
292    pub fn expected_absolute_error(&self) -> f64 {
293        self.noise_scale()
294    }
295}
296/// Full Gaussian mechanism with sensitivity and (ε, δ) target.
297///
298/// Adds N(0, σ²) noise where σ = Δ₂f * sqrt(2 * ln(1.25/δ)) / ε.
299pub struct GaussianMechanism {
300    /// Global L2 sensitivity of the query.
301    pub l2_sensitivity: f64,
302    /// Privacy parameter ε.
303    pub epsilon: f64,
304    /// Privacy parameter δ.
305    pub delta: f64,
306    /// Derived standard deviation σ.
307    pub sigma: f64,
308}
309impl GaussianMechanism {
310    /// Create a new Gaussian mechanism satisfying (ε, δ)-DP.
311    pub fn new(l2_sensitivity: f64, epsilon: f64, delta: f64) -> Self {
312        assert!(l2_sensitivity > 0.0, "l2_sensitivity must be positive");
313        assert!(epsilon > 0.0, "epsilon must be positive");
314        assert!(delta > 0.0 && delta < 1.0, "delta must be in (0, 1)");
315        let sigma = l2_sensitivity * (2.0 * (1.25f64 / delta).ln()).sqrt() / epsilon;
316        GaussianMechanism {
317            l2_sensitivity,
318            epsilon,
319            delta,
320            sigma,
321        }
322    }
323    /// Apply the mechanism using Box-Muller transform with two uniform samples.
324    pub fn apply(&self, true_answer: f64, u1: f64, u2: f64) -> f64 {
325        let z = GaussianNoise::box_muller(u1, u2);
326        true_answer + self.sigma * z
327    }
328    /// RDP guarantee at order α: ε_rdp = α * Δ₂² / (2σ²).
329    pub fn rdp_guarantee(&self, alpha: f64) -> f64 {
330        assert!(alpha > 1.0, "α must be > 1");
331        alpha * self.l2_sensitivity * self.l2_sensitivity / (2.0 * self.sigma * self.sigma)
332    }
333}
334/// Exponential mechanism for discrete outputs.
335///
336/// Samples an output proportional to exp(ε * u(D, r) / (2 * Δu)).
337pub struct ExponentialMechanism {
338    /// Privacy parameter ε.
339    pub epsilon: f64,
340    /// Sensitivity of the utility function Δu.
341    pub utility_sensitivity: f64,
342}
343impl ExponentialMechanism {
344    /// Create a new Exponential mechanism.
345    pub fn new(epsilon: f64, utility_sensitivity: f64) -> Self {
346        assert!(epsilon > 0.0, "epsilon must be positive");
347        assert!(
348            utility_sensitivity > 0.0,
349            "utility_sensitivity must be positive"
350        );
351        ExponentialMechanism {
352            epsilon,
353            utility_sensitivity,
354        }
355    }
356    /// Compute sampling probabilities for each candidate given their utility scores.
357    pub fn probabilities(&self, utility_scores: &[f64]) -> Vec<f64> {
358        assert!(!utility_scores.is_empty(), "need at least one candidate");
359        let scale = self.epsilon / (2.0 * self.utility_sensitivity);
360        let weights: Vec<f64> = utility_scores.iter().map(|&u| (scale * u).exp()).collect();
361        let total: f64 = weights.iter().sum();
362        weights.iter().map(|&w| w / total).collect()
363    }
364    /// Select a candidate index given its probabilities and a uniform sample u ∈ [0, 1).
365    pub fn sample_index(&self, probs: &[f64], u: f64) -> usize {
366        assert!(u >= 0.0 && u < 1.0, "u must be in [0, 1)");
367        let mut cumulative = 0.0;
368        for (i, &p) in probs.iter().enumerate() {
369            cumulative += p;
370            if u < cumulative {
371                return i;
372            }
373        }
374        probs.len() - 1
375    }
376}
377/// Local differential privacy mechanism.
378#[allow(dead_code)]
379#[derive(Debug, Clone)]
380pub struct LocalDpMechanism {
381    pub epsilon: f64,
382    pub mechanism_type: LocalMechanismType,
383    pub domain_size: usize,
384}
385impl LocalDpMechanism {
386    #[allow(dead_code)]
387    pub fn randomized_response(eps: f64) -> Self {
388        Self {
389            epsilon: eps,
390            mechanism_type: LocalMechanismType::RandomizedResponse,
391            domain_size: 2,
392        }
393    }
394    #[allow(dead_code)]
395    pub fn unary_encoding(eps: f64, d: usize) -> Self {
396        Self {
397            epsilon: eps,
398            mechanism_type: LocalMechanismType::UnaryEncoding,
399            domain_size: d,
400        }
401    }
402    #[allow(dead_code)]
403    pub fn variance_estimate(&self) -> f64 {
404        let e = self.epsilon.exp();
405        let d = self.domain_size as f64;
406        match self.mechanism_type {
407            LocalMechanismType::RandomizedResponse => 4.0 * e / ((e - 1.0) * (e - 1.0)),
408            LocalMechanismType::UnaryEncoding => (e + 1.0) / (e - 1.0) * (e + 1.0) / (e - 1.0) / d,
409            _ => 1.0 / (d * self.epsilon * self.epsilon),
410        }
411    }
412    #[allow(dead_code)]
413    pub fn is_locally_private(&self) -> bool {
414        true
415    }
416}
417#[allow(dead_code)]
418#[derive(Debug, Clone, PartialEq, Eq)]
419pub enum SyntheticDataMethod {
420    PrivBayes,
421    Mst,
422    Aim,
423    Gem,
424}
425#[allow(dead_code)]
426#[derive(Debug, Clone, PartialEq, Eq)]
427pub enum LocalMechanismType {
428    RandomizedResponse,
429    UnaryEncoding,
430    OptimizedUnaryEncoding,
431    HadamardResponse,
432    SampledHistogram,
433}
434/// Privacy ledger tracking cumulative privacy cost.
435#[allow(dead_code)]
436#[derive(Debug, Clone, Default)]
437pub struct PrivacyLedger {
438    pub entries: Vec<PrivacyEntry>,
439}
440impl PrivacyLedger {
441    #[allow(dead_code)]
442    pub fn new() -> Self {
443        Self::default()
444    }
445    #[allow(dead_code)]
446    pub fn add_entry(&mut self, name: &str, eps: f64, delta: f64, comp: CompositionType) {
447        self.entries.push(PrivacyEntry {
448            mechanism_name: name.to_string(),
449            epsilon: eps,
450            delta,
451            composition: comp,
452        });
453    }
454    #[allow(dead_code)]
455    pub fn total_sequential_epsilon(&self) -> f64 {
456        self.entries
457            .iter()
458            .filter(|e| e.composition == CompositionType::Sequential)
459            .map(|e| e.epsilon)
460            .sum()
461    }
462    #[allow(dead_code)]
463    pub fn total_sequential_delta(&self) -> f64 {
464        self.entries
465            .iter()
466            .filter(|e| e.composition == CompositionType::Sequential)
467            .map(|e| e.delta)
468            .sum()
469    }
470    #[allow(dead_code)]
471    pub fn parallel_max_epsilon(&self) -> f64 {
472        self.entries
473            .iter()
474            .filter(|e| e.composition == CompositionType::Parallel)
475            .map(|e| e.epsilon)
476            .fold(0.0_f64, f64::max)
477    }
478}
479/// Report Noisy Max mechanism.
480#[allow(dead_code)]
481#[derive(Debug, Clone)]
482pub struct ReportNoisyMax {
483    pub epsilon: f64,
484    pub noise_type: NoiseType,
485}
486impl ReportNoisyMax {
487    #[allow(dead_code)]
488    pub fn with_laplace(epsilon: f64) -> Self {
489        Self {
490            epsilon,
491            noise_type: NoiseType::Laplace,
492        }
493    }
494    #[allow(dead_code)]
495    pub fn is_pure_dp(&self) -> bool {
496        true
497    }
498    #[allow(dead_code)]
499    pub fn scale(&self) -> f64 {
500        1.0 / self.epsilon
501    }
502}
503/// Privacy budget tracker for sequential (ε, δ)-DP composition.
504///
505/// Tracks total spent budget across sequential mechanism applications.
506pub struct PrivacyBudget {
507    /// Total allocated ε budget.
508    pub total_epsilon: f64,
509    /// Total allocated δ budget.
510    pub total_delta: f64,
511    /// Accumulated spent ε so far.
512    pub spent_epsilon: f64,
513    /// Accumulated spent δ so far.
514    pub spent_delta: f64,
515}
516impl PrivacyBudget {
517    /// Create a new budget with the given total allowance.
518    pub fn new(total_epsilon: f64, total_delta: f64) -> Self {
519        assert!(total_epsilon > 0.0, "total_epsilon must be positive");
520        assert!(total_delta >= 0.0, "total_delta must be non-negative");
521        PrivacyBudget {
522            total_epsilon,
523            total_delta,
524            spent_epsilon: 0.0,
525            spent_delta: 0.0,
526        }
527    }
528    /// Attempt to spend (eps, delta) from the budget.
529    ///
530    /// Returns `Ok(())` if sufficient budget remains, `Err` otherwise.
531    pub fn spend(&mut self, eps: f64, delta: f64) -> Result<(), String> {
532        let new_eps = self.spent_epsilon + eps;
533        let new_delta = self.spent_delta + delta;
534        if new_eps > self.total_epsilon + 1e-12 {
535            return Err(format!(
536                "Epsilon budget exceeded: need {:.4}, have {:.4}",
537                new_eps, self.total_epsilon
538            ));
539        }
540        if new_delta > self.total_delta + 1e-12 {
541            return Err(format!(
542                "Delta budget exceeded: need {:.4}, have {:.4}",
543                new_delta, self.total_delta
544            ));
545        }
546        self.spent_epsilon = new_eps;
547        self.spent_delta = new_delta;
548        Ok(())
549    }
550    /// Remaining ε budget.
551    pub fn remaining_epsilon(&self) -> f64 {
552        (self.total_epsilon - self.spent_epsilon).max(0.0)
553    }
554    /// Remaining δ budget.
555    pub fn remaining_delta(&self) -> f64 {
556        (self.total_delta - self.spent_delta).max(0.0)
557    }
558    /// True if the budget has not been exceeded.
559    pub fn is_valid(&self) -> bool {
560        self.spent_epsilon <= self.total_epsilon + 1e-12
561            && self.spent_delta <= self.total_delta + 1e-12
562    }
563}
564/// Differentially private mean estimation.
565#[allow(dead_code)]
566#[derive(Debug, Clone)]
567pub struct DpMeanEstimator {
568    pub epsilon: f64,
569    pub delta: f64,
570    pub range: (f64, f64),
571    pub n: usize,
572}
573impl DpMeanEstimator {
574    #[allow(dead_code)]
575    pub fn new(eps: f64, delta: f64, lo: f64, hi: f64, n: usize) -> Self {
576        Self {
577            epsilon: eps,
578            delta,
579            range: (lo, hi),
580            n,
581        }
582    }
583    #[allow(dead_code)]
584    pub fn clipped_sensitivity(&self) -> f64 {
585        (self.range.1 - self.range.0) / self.n as f64
586    }
587    #[allow(dead_code)]
588    pub fn mse_gaussian_mechanism(&self) -> f64 {
589        let sigma =
590            self.clipped_sensitivity() * (2.0 * (1.25 / self.delta).ln()).sqrt() / self.epsilon;
591        sigma * sigma
592    }
593}
594/// Differentially private stochastic gradient descent (DP-SGD).
595#[allow(dead_code)]
596#[derive(Debug, Clone)]
597pub struct DpSgd {
598    pub learning_rate: f64,
599    pub noise_multiplier: f64,
600    pub max_grad_norm: f64,
601    pub batch_size: usize,
602    pub num_steps: usize,
603    pub dataset_size: usize,
604}
605impl DpSgd {
606    #[allow(dead_code)]
607    pub fn new(
608        lr: f64,
609        noise_mult: f64,
610        max_norm: f64,
611        batch: usize,
612        steps: usize,
613        n: usize,
614    ) -> Self {
615        Self {
616            learning_rate: lr,
617            noise_multiplier: noise_mult,
618            max_grad_norm: max_norm,
619            batch_size: batch,
620            num_steps: steps,
621            dataset_size: n,
622        }
623    }
624    #[allow(dead_code)]
625    pub fn sampling_rate(&self) -> f64 {
626        self.batch_size as f64 / self.dataset_size as f64
627    }
628    #[allow(dead_code)]
629    pub fn privacy_spent_rdp_alpha(&self, alpha: f64) -> f64 {
630        let q = self.sampling_rate();
631        alpha * q * q / (2.0 * self.noise_multiplier * self.noise_multiplier)
632            * self.num_steps as f64
633    }
634    #[allow(dead_code)]
635    pub fn gradient_clipping_description(&self) -> String {
636        format!("Clip grad to L2 norm <= {}", self.max_grad_norm)
637    }
638}
639/// Privacy amplification by shuffling.
640#[allow(dead_code)]
641#[derive(Debug, Clone)]
642pub struct ShuffleAmplification {
643    pub local_epsilon: f64,
644    pub n: usize,
645}
646impl ShuffleAmplification {
647    #[allow(dead_code)]
648    pub fn new(local_eps: f64, n: usize) -> Self {
649        Self {
650            local_epsilon: local_eps,
651            n,
652        }
653    }
654    #[allow(dead_code)]
655    pub fn central_epsilon_approx(&self) -> f64 {
656        let e_eps = self.local_epsilon.exp();
657        e_eps * (((self.n as f64).ln()).sqrt()) / (self.n as f64).sqrt()
658    }
659    #[allow(dead_code)]
660    pub fn is_stronger_than_local_dp(&self) -> bool {
661        self.central_epsilon_approx() < self.local_epsilon
662    }
663}
664/// Differentially private median estimation.
665#[allow(dead_code)]
666#[derive(Debug, Clone)]
667pub struct DpMedianEstimator {
668    pub epsilon: f64,
669    pub domain_size: usize,
670}
671impl DpMedianEstimator {
672    #[allow(dead_code)]
673    pub fn new(eps: f64, d: usize) -> Self {
674        Self {
675            epsilon: eps,
676            domain_size: d,
677        }
678    }
679    #[allow(dead_code)]
680    pub fn exponential_mechanism_based(&self) -> bool {
681        true
682    }
683    #[allow(dead_code)]
684    pub fn sensitivity(&self) -> usize {
685        1
686    }
687}
688/// Inference attack model against differentially private output.
689#[allow(dead_code)]
690#[derive(Debug, Clone)]
691pub struct InferenceAttackModel {
692    pub adversary_advantage: f64,
693    pub privacy_bound: f64,
694}
695impl InferenceAttackModel {
696    #[allow(dead_code)]
697    pub fn new(adv: f64, eps: f64) -> Self {
698        Self {
699            adversary_advantage: adv,
700            privacy_bound: eps,
701        }
702    }
703    #[allow(dead_code)]
704    pub fn advantage_bounded_by_dp(&self) -> bool {
705        let dp_bound = self.privacy_bound.exp() - 1.0;
706        self.adversary_advantage <= dp_bound + 1e-10
707    }
708}
709/// A single privacy expense entry.
710#[allow(dead_code)]
711#[derive(Debug, Clone)]
712pub struct PrivacyEntry {
713    pub mechanism_name: String,
714    pub epsilon: f64,
715    pub delta: f64,
716    pub composition: CompositionType,
717}
718#[allow(dead_code)]
719#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum CompositionType {
721    Sequential,
722    Parallel,
723    PostProcessing,
724}