1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use indexmap::map::IndexMap;
use ndarray::prelude::*;
use crate::{base, Float, proto, Warnable};
use crate::base::{AggregatorProperties, DataType, IndexKey, Nature, NatureContinuous, NodeProperties, SensitivitySpace, Value, ValueProperties, Vector1DNull};
use crate::components::{Component, Sensitivity};
use crate::errors::*;
use crate::utilities::prepend;
impl Component for proto::Sum {
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 mut data_property = properties.get::<IndexKey>(&"data".into())
.ok_or("data: missing")?.array()
.map_err(prepend("data:"))?.clone();
if !data_property.releasable {
data_property.assert_is_not_aggregated()?;
}
let num_columns = data_property.num_columns()?;
data_property.aggregator = Some(AggregatorProperties::new(
proto::component::Variant::Sum(self.clone()), properties, num_columns));
if data_property.data_type != DataType::Float && data_property.data_type != DataType::Int {
return Err("data: atomic type must be numeric".into())
}
data_property.nature = data_property.num_records.and_then(|n| Some(Nature::Continuous(NatureContinuous {
lower: match data_property.data_type {
DataType::Int => Vector1DNull::Int(data_property
.lower_int().ok()?.iter().map(|l| Some(l * n)).collect()),
DataType::Float => Vector1DNull::Float(data_property
.lower_float().ok()?.iter().map(|l| Some(l * (n as Float))).collect()),
_ => unreachable!()
},
upper: match data_property.data_type {
DataType::Int => Vector1DNull::Int(data_property
.upper_int().ok()?.iter().map(|u| Some(u * n)).collect()),
DataType::Float => Vector1DNull::Float(data_property
.upper_float().ok()?.iter().map(|u| Some(u * (n as Float))).collect()),
_ => unreachable!()
},
})));
data_property.num_records = Some(1);
data_property.dataset_id = Some(node_id as i64);
Ok(ValueProperties::Array(data_property).into())
}
}
impl Sensitivity for proto::Sum {
fn compute_sensitivity(
&self,
privacy_definition: &proto::PrivacyDefinition,
properties: &NodeProperties,
sensitivity_type: &SensitivitySpace,
) -> Result<Value> {
match sensitivity_type {
SensitivitySpace::KNorm(k) => {
let data_property = properties.get::<IndexKey>(&"data".into())
.ok_or("data: missing")?.array()
.map_err(prepend("data:"))?.clone();
data_property.assert_is_not_aggregated()?;
data_property.assert_non_null()?;
use proto::privacy_definition::Neighboring;
let neighboring_type = Neighboring::from_i32(privacy_definition.neighboring)
.ok_or_else(|| Error::from("neighboring definition must be either \"AddRemove\" or \"Substitute\""))?;
macro_rules! compute_sensitivity {
($lower:expr, $upper:expr) => {
{
let row_sensitivity = match k {
1 | 2 => match neighboring_type {
Neighboring::AddRemove => $lower.iter()
.zip($upper.iter())
.map(|(min, max)| min.abs().max(max.abs()))
.collect::<Vec<_>>(),
Neighboring::Substitute => $lower.iter()
.zip($upper.iter())
.map(|(min, max)| (max - min))
.collect::<Vec<_>>()
}
_ => return Err("KNorm sensitivity is only supported in L1 and L2 spaces".into())
};
let mut array_sensitivity = Array::from(row_sensitivity).into_dyn();
array_sensitivity.insert_axis_inplace(Axis(0));
Ok(array_sensitivity.into())
}
}
}
match data_property.data_type {
DataType::Int => compute_sensitivity!(data_property.lower_int()?, data_property.upper_int()?),
DataType::Float => compute_sensitivity!(data_property.lower_float()?, data_property.upper_float()?),
_ => return Err(Error::from("sum data must be numeric"))
}
}
_ => Err("Sum sensitivity is only implemented for KNorm".into())
}
}
}