blob: 78ec589689a6a9da3e0d3471d05fb023667ce9f0 [file] [log] [blame]
# Copyright 2021 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for central_privacy_utils."""
from unittest import mock
from absl.testing import absltest
from absl.testing import parameterized
import numpy as np
from scipy import stats
from clustering import central_privacy_utils
from clustering import clustering_params
from clustering.central_privacy_utils import AveragePrivacyParam
from clustering.central_privacy_utils import CountPrivacyParam
class CentralPrivacyUtilsTest(parameterized.TestCase):
def test_average_privacy_param(self):
average_privacy_param = AveragePrivacyParam.from_budget_split(
clustering_params.DifferentialPrivacyParam(epsilon=10, delta=1e-2),
clustering_params.PrivacyBudgetSplit(
frac_sum=0.7, frac_group_count=0.3),
radius=4.3)
self.assertAlmostEqual(
average_privacy_param.gaussian_standard_deviation, 1.927768, delta=1e-5)
self.assertEqual(average_privacy_param.sensitivity, 4.3)
def test_average_privacy_param_error(self):
with self.assertRaises(
ValueError,
msg='Gaussian standard deviation was -0.1, but it must be nonnegative.'
):
AveragePrivacyParam(gaussian_standard_deviation=-0.1, sensitivity=2.0)
with self.assertRaises(
ValueError,
msg='Sensitivity was 0, but it must be positive.'
):
AveragePrivacyParam(gaussian_standard_deviation=5.0, sensitivity=0.0)
with self.assertRaises(
ValueError,
msg='Sensitivity was -0.2, but it must be positive.'
):
AveragePrivacyParam(gaussian_standard_deviation=5.0, sensitivity=-0.2)
def test_average_privacy_param_infinite_eps(self):
average_privacy_param = AveragePrivacyParam.from_budget_split(
clustering_params.DifferentialPrivacyParam(epsilon=np.inf, delta=1e-2),
clustering_params.PrivacyBudgetSplit(
frac_sum=0.7, frac_group_count=0.3),
radius=4.3)
self.assertEqual(average_privacy_param.gaussian_standard_deviation, 0)
self.assertEqual(average_privacy_param.sensitivity, 4.3)
@parameterized.named_parameters(
('basic', [[1, 2, 1], [0.4, 0.2, 0.8], [3, 0, 3]], [3.1, 5.55, 0.2]),
('empty', [], [2, 5, -1]))
@mock.patch.object(
np.random, 'normal', return_value=np.array([8, 20, -4]), autospec=True)
def test_get_private_average(self, nonprivate_points, expected_center,
mock_normal_fn):
private_count = 4
average_privacy_param = AveragePrivacyParam(
gaussian_standard_deviation=1.9, sensitivity=4.3)
result = central_privacy_utils.get_private_average(
nonprivate_points, private_count, average_privacy_param, dim=3)
self.assertSequenceAlmostEqual(result, expected_center)
mock_normal_fn.assert_called_once()
self.assertEqual(mock_normal_fn.call_args[1]['size'], 3)
self.assertEqual(mock_normal_fn.call_args[1]['scale'], 1.9)
def test_get_private_average_error(self):
nonprivate_points = [[1, 2, 1], [0.4, 0.2, 0.8], [3, 0, 3]]
average_privacy_param = AveragePrivacyParam(
gaussian_standard_deviation=1.9, sensitivity=4.3)
with self.assertRaises(ValueError):
central_privacy_utils.get_private_average(
nonprivate_points, 0, average_privacy_param, dim=3)
with self.assertRaises(ValueError):
central_privacy_utils.get_private_average(
nonprivate_points, -2, average_privacy_param, dim=3)
def test_get_private_average_zero_std_dev(self):
nonprivate_points = [[1, 2, 1], [0.2, 0.1, 0.8], [3, 0, 3]]
private_count = 3
expected_center = [1.4, 0.7, 1.6]
average_privacy_param = AveragePrivacyParam(
gaussian_standard_deviation=0, sensitivity=4.3)
self.assertSequenceAlmostEqual(
central_privacy_utils.get_private_average(
nonprivate_points, private_count, average_privacy_param, dim=3),
expected_center)
def test_private_count_param(self):
privacy_param = clustering_params.DifferentialPrivacyParam(
epsilon=10, delta=1e-2)
privacy_budget_split = clustering_params.PrivacyBudgetSplit(
frac_sum=0.2, frac_group_count=0.8)
max_tree_depth = 3
count_privacy_param = CountPrivacyParam.from_budget_split(
privacy_param, privacy_budget_split, max_tree_depth)
self.assertEqual(count_privacy_param.laplace_param, 2.0)
def test_private_count_param_error(self):
with self.assertRaises(
ValueError, msg='Laplace param was 0, but it must be positive.'):
CountPrivacyParam(laplace_param=0)
with self.assertRaises(
ValueError, msg='Laplace param was -0.2, but it must be positive.'):
CountPrivacyParam(laplace_param=-0.2)
def test_private_count_param_infinite_eps(self):
privacy_param = clustering_params.DifferentialPrivacyParam(
epsilon=np.inf, delta=1e-2)
privacy_budget_split = clustering_params.PrivacyBudgetSplit(
frac_sum=0.2, frac_group_count=0.8)
max_tree_depth = 3
count_privacy_param = CountPrivacyParam.from_budget_split(
privacy_param, privacy_budget_split, max_tree_depth)
self.assertEqual(count_privacy_param.laplace_param, np.inf)
@parameterized.named_parameters(('basic', 10, 70), ('not_clip', -80, -20))
@mock.patch.object(stats.dlaplace, 'rvs', autospec=True)
def test_get_private_count(self, dlaplace_noise, expected_private_count,
mock_dlaplace_fn):
mock_dlaplace_fn.return_value = dlaplace_noise
nonprivate_count = 60
count_privacy_param = CountPrivacyParam(laplace_param=2.0)
result = central_privacy_utils.get_private_count(nonprivate_count,
count_privacy_param)
self.assertEqual(result, expected_private_count)
mock_dlaplace_fn.assert_called_once_with(2)
def test_get_private_count_inf_laplace_param(self):
nonprivate_count = 60
count_privacy_param = CountPrivacyParam(laplace_param=np.inf)
self.assertEqual(
central_privacy_utils.get_private_count(nonprivate_count,
count_privacy_param),
nonprivate_count)
if __name__ == '__main__':
absltest.main()