Skip to content

Commit ab98495

Browse files
Merge pull request #433 from dvadym/quantile_tree_merge
Implement QuantileTree.Merge method
2 parents a409a4b + 0750b67 commit ab98495

2 files changed

Lines changed: 54 additions & 10 deletions

File tree

src/bindings/PyDP/algorithms/qunatile_tree.cpp

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,21 @@ std::unique_ptr<dp::QuantileTree<double>> CreateQuantileTree(double lower, doubl
3030

3131
dp::QuantileTree<double>::Privatized GetPrivatizeTree(
3232
dp::QuantileTree<double>& tree, double epsilon, double delta,
33-
int max_partitions_contributed_to, int max_contributions_per_partition,
33+
int max_partitions_contributed, int max_contributions_per_partition,
3434
const std::string& noise_type) {
3535
dp::QuantileTree<double>::DPParams dp_params;
3636
dp_params.epsilon = epsilon;
3737
dp_params.delta = delta;
3838
dp_params.max_contributions_per_partition = max_contributions_per_partition;
39-
dp_params.max_partitions_contributed_to = max_partitions_contributed_to;
39+
dp_params.max_partitions_contributed_to = max_partitions_contributed;
4040
// Create DP mechanism.
4141
if (noise_type == "laplace") {
4242
dp_params.mechanism_builder = std::make_unique<dp::LaplaceMechanism::Builder>();
4343
} else if (noise_type == "gaussian") {
4444
dp_params.mechanism_builder = std::make_unique<dp::GaussianMechanism::Builder>();
4545
} else {
4646
throw py::value_error("noise_type can be 'laplace' or 'gaussian', but it is '" +
47-
noise_type + "'./**/");
47+
noise_type + "'.");
4848
}
4949
auto status_or_result = tree.MakePrivate(dp_params);
5050
if (!status_or_result.ok()) {
@@ -89,15 +89,28 @@ void init_algorithms_quantile_tree(py::module& m) {
8989
to_return.mutable_data()->PackFrom(obj.Serialize());
9090
return to_return;
9191
});
92-
py_class.def("merge", &dp::QuantileTree<double>::Merge, py::arg("summary"));
92+
py_class.def(
93+
"merge",
94+
[](dp::QuantileTree<double>& tree, const dp::Summary& summary) {
95+
if (!summary.has_data()) {
96+
throw std::runtime_error("Cannot merge summary, no data.");
97+
}
98+
99+
dp::BoundedQuantilesSummary quantiles_summary;
100+
if (!summary.data().UnpackTo(&quantiles_summary)) {
101+
throw std::runtime_error("Fail to upack data");
102+
}
103+
tree.Merge(quantiles_summary);
104+
},
105+
py::arg("summary"));
93106

94107
py_class.def(
95108
"compute_quantiles",
96109
[](dp::QuantileTree<double>& tree, double epsilon, double delta,
97-
int max_partitions_contributed_to, int max_contributions_per_partition,
110+
int max_partitions_contributed, int max_contributions_per_partition,
98111
const std::vector<double>& quantiles, const std::string& noise_type) {
99112
dp::QuantileTree<double>::Privatized privatized_tree =
100-
GetPrivatizeTree(tree, epsilon, delta, max_partitions_contributed_to,
113+
GetPrivatizeTree(tree, epsilon, delta, max_partitions_contributed,
101114
max_contributions_per_partition, noise_type);
102115

103116
std::vector<double> output;
@@ -110,18 +123,18 @@ void init_algorithms_quantile_tree(py::module& m) {
110123
}
111124
return output;
112125
},
113-
py::arg("epsilon"), py::arg("delta"), py::arg("max_partitions_contributed_to"),
126+
py::arg("epsilon"), py::arg("delta"), py::arg("max_partitions_contributed"),
114127
py::arg("max_contributions_per_partition"), py::arg("quantiles"),
115128
py::arg("noise_type") = "laplace", "Compute multiple quantiles.");
116129

117130
py_class.def(
118131
"compute_quantiles_and_confidence_intervals",
119132
[](dp::QuantileTree<double>& tree, double epsilon, double delta,
120-
int max_contributions_per_partition, int max_partitions_contributed_to,
133+
int max_contributions_per_partition, int max_partitions_contributed,
121134
const std::vector<double>& quantiles, double confidence_interval_level,
122135
const std::string& noise_type) {
123136
dp::QuantileTree<double>::Privatized privatized_tree =
124-
GetPrivatizeTree(tree, epsilon, delta, max_partitions_contributed_to,
137+
GetPrivatizeTree(tree, epsilon, delta, max_partitions_contributed,
125138
max_contributions_per_partition, noise_type);
126139

127140
std::vector<QuantileConfidenceInterval> output;
@@ -142,7 +155,7 @@ void init_algorithms_quantile_tree(py::module& m) {
142155
}
143156
return output;
144157
},
145-
py::arg("epsilon"), py::arg("delta"), py::arg("max_partitions_contributed_to"),
158+
py::arg("epsilon"), py::arg("delta"), py::arg("max_partitions_contributed"),
146159
py::arg("max_contributions_per_partition"), py::arg("quantiles"),
147160
py::arg("confidence_interval_level"), py::arg("noise_type") = "laplace",
148161
"Compute multiple quantiles and confidence intervals for them.");

tests/algorithms/test_quantile_tree.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
import pydp._pydp as dp
34
from pydp.algorithms.quantile_tree import QuantileTree
45

56

@@ -70,3 +71,33 @@ def test_quantiles_and_confidence_intervals(self):
7071
<= dp_quantile_ci.upper_bound
7172
)
7273
assert dp_quantile_ci.upper_bound - dp_quantile_ci.lower_bound < 0.01
74+
75+
def test_serialize_deserialize(self):
76+
lower, upper = 0, 1000
77+
height, branching_factor = 5, 10
78+
tree1 = QuantileTree(lower, upper, height, branching_factor)
79+
80+
# Add elements 0,..1000 to the tree.
81+
for i in range(1001):
82+
tree1.add_entry(i)
83+
84+
serialized_tree = tree1.serialize().to_bytes()
85+
86+
# Deserialize
87+
# 1.Create empty tree with the same parameters.
88+
tree2 = QuantileTree(lower, upper, height, branching_factor)
89+
# 2. Merge serialized_tree to tree2.
90+
tree2.merge(dp.bytes_to_summary(serialized_tree))
91+
92+
# Check that tree2 computes correct quantiles. For this use high
93+
# epsilon, which means small noise and close to the real quantiles.
94+
eps, delta = 10000, 0
95+
quantiles_to_compute = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.9, 0.9]
96+
dp_quantiles = tree2.compute_quantiles(
97+
eps, delta, 1, 1, quantiles_to_compute, "laplace"
98+
)
99+
100+
# Check that DP quantiles are close to expected.
101+
for quantile, dp_quantile in zip(quantiles_to_compute, dp_quantiles):
102+
expected_quantile = quantile * upper
103+
assert abs(expected_quantile - dp_quantile) < 0.1

0 commit comments

Comments
 (0)