use indexmap::map::IndexMap;
use itertools::Itertools;
use crate::{base, proto, Warnable};
use crate::base::{DataType, IndexKey, NodeProperties, SensitivitySpace, Value, ValueProperties, ArrayProperties};
use crate::components::{Accuracy, Component, Expandable, Mechanism, Sensitivity};
use crate::errors::*;
use crate::utilities::{expand_mechanism, prepend};
use crate::utilities::privacy::{get_epsilon, privacy_usage_check, spread_privacy_usage};
impl Component for proto::LaplaceMechanism {
fn propagate_property(
&self,
privacy_definition: &Option<proto::PrivacyDefinition>,
_public_arguments: IndexMap<base::IndexKey, &Value>,
properties: base::NodeProperties,
_node_id: u32,
) -> Result<Warnable<ValueProperties>> {
let privacy_definition = privacy_definition.as_ref()
.ok_or_else(|| "privacy_definition must be defined")?;
if privacy_definition.protect_floating_point {
return Err("Floating-point protections are enabled. The laplace mechanism is susceptible to floating-point attacks.".into())
}
let mut data_property: ArrayProperties = properties.get::<IndexKey>(&"data".into())
.ok_or("data: missing")?.array()
.map_err(prepend("data:"))?.clone();
if data_property.data_type != DataType::Float && data_property.data_type != DataType::Int {
return Err("data: atomic type must be numeric".into());
}
let aggregator = data_property.aggregator.clone()
.ok_or_else(|| Error::from("aggregator: missing"))?;
aggregator.component.compute_sensitivity(
privacy_definition,
&aggregator.properties,
&SensitivitySpace::KNorm(1))?.array()?.cast_float()?;
aggregator.lipschitz_constants.array()?.cast_float()?;
let privacy_usage = self.privacy_usage.iter().cloned().map(Ok)
.fold1(|l, r| l? + r?).ok_or_else(|| "privacy_usage: must be defined")??;
let warnings = privacy_usage_check(
&privacy_usage,
data_property.num_records,
privacy_definition.strict_parameter_checks)?;
data_property.releasable = true;
data_property.aggregator = None;
Ok(Warnable(data_property.into(), warnings))
}
}
impl Expandable for proto::LaplaceMechanism {
fn expand_component(
&self,
privacy_definition: &Option<proto::PrivacyDefinition>,
component: &proto::Component,
_public_arguments: &IndexMap<IndexKey, &Value>,
properties: &base::NodeProperties,
component_id: u32,
maximum_id: u32,
) -> Result<base::ComponentExpansion> {
expand_mechanism(
&SensitivitySpace::KNorm(1),
privacy_definition,
self.privacy_usage.as_ref(),
component,
properties,
component_id,
maximum_id
)
}
}
impl Mechanism for proto::LaplaceMechanism {
fn get_privacy_usage(
&self,
privacy_definition: &proto::PrivacyDefinition,
release_usage: Option<&Vec<proto::PrivacyUsage>>,
properties: &NodeProperties
) -> Result<Option<Vec<proto::PrivacyUsage>>> {
let data_property = properties.get::<IndexKey>(&"data".into())
.ok_or("data: missing")?.array()
.map_err(prepend("data:"))?;
Some(release_usage.unwrap_or_else(|| &self.privacy_usage).iter()
.map(|usage| usage.effective_to_actual(
data_property.sample_proportion.unwrap_or(1.),
data_property.c_stability,
privacy_definition.group_size))
.collect::<Result<Vec<proto::PrivacyUsage>>>()).transpose()
}
}
impl Accuracy for proto::LaplaceMechanism {
fn accuracy_to_privacy_usage(
&self,
accuracies: &proto::Accuracies,
mut public_arguments: IndexMap<base::IndexKey, &Value>
) -> Result<Option<Vec<proto::PrivacyUsage>>> {
let sensitivities: Vec<_> = public_arguments.remove(&IndexKey::from("sensitivity"))
.ok_or_else(|| Error::from("sensitivity: missing in accuracy"))?.clone()
.array()?.cast_float()?
.gencolumns().into_iter()
.map(|sensitivity_col| sensitivity_col.into_iter().copied().fold1(|l, r| l.max(r)).unwrap())
.collect();
Ok(Some(sensitivities.into_iter().zip(accuracies.values.iter())
.map(|(sensitivity, accuracy)| proto::PrivacyUsage {
distance: Some(proto::privacy_usage::Distance::Approximate(proto::privacy_usage::DistanceApproximate {
epsilon: (1. / accuracy.alpha).ln() * (sensitivity as f64 / accuracy.value),
delta: 0.,
}))
})
.collect()))
}
fn privacy_usage_to_accuracy(
&self,
mut public_arguments: IndexMap<base::IndexKey, &Value>,
alpha: f64
) -> Result<Option<Vec<proto::Accuracy>>> {
let sensitivities: Vec<_> = public_arguments.remove(&IndexKey::from("sensitivity"))
.ok_or_else(|| Error::from("sensitivity: missing in accuracy"))?.clone()
.array()?.cast_float()?
.gencolumns().into_iter()
.map(|sensitivity_col| sensitivity_col.into_iter().copied().fold1(|l, r| l.max(r)).unwrap())
.collect();
let usages = spread_privacy_usage(&self.privacy_usage, sensitivities.len())?;
let epsilons = usages.iter().map(get_epsilon).collect::<Result<Vec<f64>>>()?;
Ok(Some(sensitivities.into_iter().zip(epsilons.into_iter())
.map(|(sensitivity, epsilon)| proto::Accuracy {
value: (1. / alpha).ln() * (sensitivity as f64 / epsilon),
alpha,
})
.collect()))
}
}