use core::f64;
use dashu::{base::Signed, rational::RBig};
use opendp_derive::{bootstrap, proven};
use crate::core::{Measure, Metric, MetricSpace};
use crate::measurements::noise::nature::Nature;
use crate::measurements::{MakeNoise, NoiseDomain, NoisePrivacyMap, ZExpFamily};
use crate::{
core::{Domain, Measurement, PrivacyMap},
error::Fallible,
measures::MaxDivergence,
metrics::L1Distance,
traits::InfCast,
};
#[cfg(test)]
mod test;
#[cfg(feature = "ffi")]
pub(crate) mod ffi;
#[bootstrap(
features("contrib"),
arguments(k(default = b"null")),
generics(DI(suppress), MI(suppress), MO(default = "MaxDivergence"))
)]
pub fn make_laplace<DI: Domain, MI: Metric, MO: Measure>(
input_domain: DI,
input_metric: MI,
scale: f64,
k: Option<i32>,
) -> Fallible<Measurement<DI, MI, MO, DI::Carrier>>
where
DiscreteLaplace: MakeNoise<DI, MI, MO>,
(DI, MI): MetricSpace,
{
DiscreteLaplace { scale, k }.make_noise((input_domain, input_metric))
}
pub struct DiscreteLaplace {
pub scale: f64,
pub k: Option<i32>,
}
#[proven(proof_path = "measurements/noise/distribution/laplace/MakeNoise_for_DiscreteLaplace.tex")]
impl<DI: NoiseDomain, MI: Metric, MO: 'static + Measure> MakeNoise<DI, MI, MO> for DiscreteLaplace
where
(DI, MI): MetricSpace,
DI::Atom: Nature,
<DI::Atom as Nature>::RV<1>: MakeNoise<DI, MI, MO>,
{
fn make_noise(self, input_space: (DI, MI)) -> Fallible<Measurement<DI, MI, MO, DI::Carrier>> {
DI::Atom::new_distribution(self.scale, self.k)?.make_noise(input_space)
}
}
#[proven(
proof_path = "measurements/noise/distribution/laplace/NoisePrivacyMap_for_ZExpFamily1.tex"
)]
impl NoisePrivacyMap<L1Distance<RBig>, MaxDivergence> for ZExpFamily<1> {
fn noise_privacy_map(
&self,
_input_metric: &L1Distance<RBig>,
_output_measure: &MaxDivergence,
) -> Fallible<PrivacyMap<L1Distance<RBig>, MaxDivergence>> {
let ZExpFamily { scale } = self.clone();
if scale < RBig::ZERO {
return fallible!(MakeMeasurement, "scale ({}) must not be negative", scale);
}
Ok(PrivacyMap::new_fallible(move |d_in: &RBig| {
if d_in.is_negative() {
return fallible!(FailedMap, "sensitivity ({}) must be positive", d_in);
}
if d_in.is_zero() {
return Ok(0.0);
}
if scale.is_zero() {
return Ok(f64::INFINITY);
}
f64::inf_cast(d_in / scale.clone())
}))
}
}