blob: a6a257f4489d0d36f77ea186dfb65b7a4a410e7c [file] [log] [blame]
// Copyright 2020 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
//
// https://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.
#include "accounting/privacy_loss_distribution.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "base/statusor.h"
#include "accounting/common/common.h"
#include "base/testing/status_matchers.h"
namespace differential_privacy {
namespace accounting {
// Test peer is required to set private instance variables in order to test
// behaviors independently.
class PrivacyLossDistributionTestPeer {
public:
static std::unique_ptr<PrivacyLossDistribution> Create(
const ProbabilityMassFunction& probability_mass_function,
double infinity_mass = 0, double discretization_interval = 1e-4,
EstimateType estimate_type = EstimateType::kPessimistic) {
return absl::WrapUnique(
new PrivacyLossDistribution(discretization_interval, infinity_mass,
probability_mass_function, estimate_type));
}
};
namespace {
using ::testing::DoubleNear;
using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::HasSubstr;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedPointwise;
using ::testing::Values;
using ::differential_privacy::base::testing::IsOk;
using ::differential_privacy::base::testing::StatusIs;
constexpr double kMaxError = 1e-4f;
TEST(PrivacyLossDistributionTest, CreateBasic) {
ProbabilityMassFunction pmf_lo = {{1, 0.5}, {2, 0.5}};
ProbabilityMassFunction pmf_hi = {{1, 0.6}, {2, 0.4}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::Create(pmf_lo, pmf_hi);
EXPECT_THAT(pld->Pmf(), UnorderedElementsAre(
FieldsAre(Eq(-2231), DoubleNear(0.4, kMaxError)),
FieldsAre(Eq(1824), DoubleNear(0.6, kMaxError))));
EXPECT_EQ(pld->InfinityMass(), 0);
EXPECT_EQ(pld->DiscretizationInterval(), 0.0001);
}
TEST(PrivacyLossDistributionTest, CreateInfinityMass) {
ProbabilityMassFunction pmf_lo = {{1, 0.5}, {3, 0.5}};
ProbabilityMassFunction pmf_hi = {{1, 0.6}, {2, 0.4}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::Create(pmf_lo, pmf_hi);
EXPECT_THAT(pld->Pmf(), UnorderedElementsAre(Pair(1824, 0.6)));
EXPECT_NEAR(pld->InfinityMass(), 0.4, kMaxError);
}
TEST(PrivacyLossDistributionTest, CreatePessimistic) {
ProbabilityMassFunction pmf_lo = {{1, 0.5}, {2, 0.5}};
ProbabilityMassFunction pmf_hi = {{1, 0.6}, {2, 0.4}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::Create(pmf_lo, pmf_hi,
EstimateType::kPessimistic, 1e-4, -0.55);
EXPECT_THAT(pld->Pmf(), UnorderedElementsAre(Pair(1824, 0.6)));
EXPECT_NEAR(pld->InfinityMass(), 0.40, kMaxError);
}
TEST(PrivacyLossDistributionTest, CreateOptimistic) {
ProbabilityMassFunction pmf_lo = {{1, 0.5}, {2, 0.5}};
ProbabilityMassFunction pmf_hi = {{1, 0.6}, {2, 0.4}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::Create(pmf_lo, pmf_hi, EstimateType::kOptimistic,
1e-4, -0.55);
ProbabilityMassFunction expected_pmf = {{1823, 0.6}};
EXPECT_THAT(pld->Pmf(), UnorderedElementsAre(
FieldsAre(Eq(1823), DoubleNear(0.6, kMaxError))));
EXPECT_NEAR(pld->InfinityMass(), 0.0, kMaxError);
}
TEST(PrivacyLossDistributionTest, CreateMismatch) {
ProbabilityMassFunction pmf_lo = {{1, 0.5}, {2, 0.5}};
ProbabilityMassFunction pmf_hi = {{4, 0.6}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::Create(pmf_lo, pmf_hi,
EstimateType::kPessimistic);
EXPECT_EQ(pld->Pmf().size(), 0);
}
TEST(PrivacyLossDistributionTest, CreateIdentity) {
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::CreateIdentity(1e-3);
ProbabilityMassFunction expected_pmf = {{0, 1}};
EXPECT_THAT(pld->Pmf(),
UnorderedElementsAre(FieldsAre(Eq(0), DoubleNear(1, kMaxError))));
EXPECT_NEAR(pld->InfinityMass(), 0.0, kMaxError);
EXPECT_NEAR(pld->DiscretizationInterval(), 1e-3, kMaxError);
}
TEST(PrivacyLossDistributionTest, HockeyStickDivergence) {
// 1824: is ceiling(value_discretization_interval * ln(1.2))
// ceiling(1000 * 0.1823).
// 1.2: so that hockey stick divergence at 0 is
// (1 - e^{-ln(1.2)}) * 0.6 = 0.1
ProbabilityMassFunction pmf = {{1824, 0.6}, {-2231, 0.4}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(pmf);
double value = pld->GetDeltaForEpsilon(0);
EXPECT_NEAR(value, 0.1, kMaxError);
EXPECT_GE(value, 1e-5);
}
TEST(PrivacyLossDistributionTest, Compose) {
ProbabilityMassFunction pmf = {{0, 1}, {1, 4}, {2, 2}, {3, 5}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(pmf,
/*infinity_mass=*/0.3,
/*discretization_interval=*/1e-4);
ProbabilityMassFunction pmf_other = {{0, 3}, {1, 4}, {2, 1}};
std::unique_ptr<PrivacyLossDistribution> pld_other =
PrivacyLossDistributionTestPeer::Create(pmf_other,
/*infinity_mass=*/0.2,
/*discretization_interval=*/1e-4);
EXPECT_OK(pld->Compose(*pld_other));
EXPECT_THAT(pld->InfinityMass(), DoubleNear(0.44, kMaxError));
EXPECT_FALSE(pld->Pmf().empty());
}
TEST(PrivacyLossDistributionTest, GetDeltaForEpsilonForComposedPLD) {
ProbabilityMassFunction pmf = {{0, 0.1}, {1, 0.7}, {2, 0.1}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(pmf,
/*infinity_mass=*/0.1,
/*discretization_interval=*/0.4);
ProbabilityMassFunction pmf_other = {{1, 0.1}, {2, 0.6}, {3, 0.25}};
std::unique_ptr<PrivacyLossDistribution> pld_other =
PrivacyLossDistributionTestPeer::Create(pmf_other,
/*infinity_mass=*/0.05,
/*discretization_interval=*/0.4);
base::StatusOr<double> delta =
pld->GetDeltaForEpsilonForComposedPLD(*pld_other, /*epsilon=*/1.1);
ASSERT_OK(delta);
EXPECT_THAT(*delta, DoubleNear(0.2956, kMaxError));
}
TEST(PrivacyLossDistributionTest, ComposeTruncation) {
ProbabilityMassFunction pmf = {{0, 0.1}, {1, 0.7}, {2, 0.1}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(pmf,
/*infinity_mass=*/0.1,
/*discretization_interval=*/1);
ProbabilityMassFunction pmf_other = {{0, 0.1}, {1, 0.6}, {2, 0.2}};
std::unique_ptr<PrivacyLossDistribution> pld_other =
PrivacyLossDistributionTestPeer::Create(pmf_other,
/*infinity_mass=*/0.1,
/*discretization_interval=*/1);
EXPECT_OK(pld->Compose(*pld_other, 0.021));
EXPECT_THAT(pld->InfinityMass(), DoubleNear(0.211, kMaxError));
EXPECT_THAT(pld->Pmf(), UnorderedElementsAre(
FieldsAre(Eq(1), DoubleNear(0.13, kMaxError)),
FieldsAre(Eq(2), DoubleNear(0.45, kMaxError)),
FieldsAre(Eq(3), DoubleNear(0.20, kMaxError)),
FieldsAre(Eq(4), DoubleNear(0.02, kMaxError))));
}
TEST(PrivacyLossDistributionTest, ComposeNumTimes) {
ProbabilityMassFunction pmf = {{1, 0.6}, {2, 0.4}};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(pmf,
/*infinity_mass=*/0.3,
/*discretization_interval=*/1e-4);
constexpr int num_times = 2;
pld->Compose(num_times);
ProbabilityMassFunction expected_pmf = {{2, 0.36}, {3, 0.48}, {4, 0.16}};
EXPECT_THAT(pld->InfinityMass(), DoubleNear(0.51, kMaxError));
EXPECT_THAT(pld->Pmf(), UnorderedElementsAre(
FieldsAre(Eq(2), DoubleNear(0.36, kMaxError)),
FieldsAre(Eq(3), DoubleNear(0.48, kMaxError)),
FieldsAre(Eq(4), DoubleNear(0.16, kMaxError))));
}
TEST(PrivacyLossDistributionTest,
ComposeErrorDifferentDiscretizationIntervals) {
ProbabilityMassFunction pmf = {};
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(pmf, 0.3, 1e-4);
std::unique_ptr<PrivacyLossDistribution> pld_other =
PrivacyLossDistributionTestPeer::Create(pmf, 0.3, 2e-4);
std::string error_msg = "discretization interval";
EXPECT_THAT(
pld->ValidateComposition(*pld_other),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr(error_msg)));
EXPECT_THAT(
pld->Compose(*pld_other),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr(error_msg)));
EXPECT_THAT(
pld->GetDeltaForEpsilonForComposedPLD(*pld_other, /*epsilon=*/1),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr(error_msg)));
}
TEST(PrivacyLossDistributionTest, ComposeErrorDifferentEstimateTypes) {
ProbabilityMassFunction pmf;
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(
pmf, /*infinity_mass=*/0.3, /*discretization_interval=*/1e-4,
/*estimate_type=*/EstimateType::kPessimistic);
std::unique_ptr<PrivacyLossDistribution> pld_other =
PrivacyLossDistributionTestPeer::Create(
pmf, /*infinity_mass=*/0.3, /*discretization_interval=*/1e-4,
/*estimate_type=*/EstimateType::kOptimistic);
std::string error_msg = "estimate type";
EXPECT_THAT(
pld->ValidateComposition(*pld_other),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr(error_msg)));
EXPECT_THAT(
pld->Compose(*pld_other),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr(error_msg)));
EXPECT_THAT(
pld->GetDeltaForEpsilonForComposedPLD(*pld_other, /*epsilon=*/1),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr(error_msg)));
}
struct GetEpsilonFromDeltaParam {
double discretization_interval;
double infinity_mass;
ProbabilityMassFunction pmf;
double delta;
double expected_epsilon;
};
class GetEpsilonFromDeltaTest
: public testing::TestWithParam<GetEpsilonFromDeltaParam> {};
INSTANTIATE_TEST_SUITE_P(
PrivacyLossDistributionSuite, GetEpsilonFromDeltaTest,
Values(GetEpsilonFromDeltaParam{.discretization_interval = 0.5,
.infinity_mass = 0.1,
.pmf = {{4, 0.2}, {2, 0.7}},
.delta = 0.5,
.expected_epsilon = 0.56358432},
GetEpsilonFromDeltaParam{.discretization_interval = 0.5,
.infinity_mass = 0.1,
.pmf = {{4, 0.2}, {2, 0.7}},
.delta = 0.2,
.expected_epsilon = 1.30685282},
GetEpsilonFromDeltaParam{.discretization_interval = 1,
.infinity_mass = 0.1,
.pmf = {{1, 0.2}, {-1, 0.7}},
.delta = 0.4,
.expected_epsilon = 0},
GetEpsilonFromDeltaParam{.discretization_interval = 1,
.infinity_mass = 0,
.pmf = {{-1, 0.1}},
.delta = 0,
.expected_epsilon = 0},
// Test resilience against overflow
GetEpsilonFromDeltaParam{.discretization_interval = 1,
.infinity_mass = 0,
.pmf = {{5000, 1}},
.delta = 0.1,
.expected_epsilon = 5000},
GetEpsilonFromDeltaParam{
.discretization_interval = 1,
.infinity_mass = 0.1,
.pmf = {{5000, 0.2}, {4000, 0.1}, {3000, 0.7}},
.delta = 0.4,
.expected_epsilon = 4000}));
TEST_P(GetEpsilonFromDeltaTest, EpsilonFromDeltaBasic) {
GetEpsilonFromDeltaParam param = GetParam();
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create(param.pmf, param.infinity_mass,
param.discretization_interval);
EXPECT_NEAR(pld->GetEpsilonForDelta(param.delta), param.expected_epsilon,
kMaxError);
}
TEST(GetEpsilonFromDeltaTest, EpsilonFromDeltaInfinity) {
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistributionTestPeer::Create({{1, 0.2}, {-1, 0.7}},
/*infinity_mass=*/0.5,
/*discretization_interval=*/1);
EXPECT_EQ(pld->GetEpsilonForDelta(0.4),
std::numeric_limits<double>::infinity());
}
TEST(PrivacyLossDistributionTest, DivergenceFromMechansim) {
base::StatusOr<std::unique_ptr<GaussianPrivacyLoss>> noise_privacy_loss =
GaussianPrivacyLoss::Create(
/*standard_deviation=*/1,
/*sensitivity=*/1);
ASSERT_OK(noise_privacy_loss);
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::CreateForAdditiveNoise(
*noise_privacy_loss.value(), EstimateType::kPessimistic,
/*discretization_interval=*/1e-4);
constexpr double epsilon = 1;
// Delta for the noise privacy loss of the mechanism
double delta_mechanism =
noise_privacy_loss.value()->GetDeltaForEpsilon(epsilon);
// Delta for the PMF discretized from that noise privacy loss
double delta_pmf = pld->GetDeltaForEpsilon(epsilon);
EXPECT_NEAR(delta_mechanism, delta_pmf, kMaxError);
}
TEST(PrivacyLossDistributionTest, GaussianOptimistic) {
base::StatusOr<std::unique_ptr<GaussianPrivacyLoss>> noise_privacy_loss =
GaussianPrivacyLoss::Create(
/*standard_deviation=*/1,
/*sensitivity=*/2,
/*estimate_type=*/EstimateType::kOptimistic,
/*mass_truncation_bound=*/-0.999345);
ASSERT_OK(noise_privacy_loss);
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::CreateForAdditiveNoise(
*noise_privacy_loss.value(), EstimateType::kOptimistic,
/*discretization_interval=*/1);
ProbabilityMassFunction expected_pmf = {
{0, 0.124477}, {1, 0.191462}, {2, 0.191462}, {3, 0.308537}};
EXPECT_THAT(
pld->Pmf(),
UnorderedElementsAre(FieldsAre(Eq(0), DoubleNear(0.124477, kMaxError)),
FieldsAre(Eq(1), DoubleNear(0.191462, kMaxError)),
FieldsAre(Eq(2), DoubleNear(0.191462, kMaxError)),
FieldsAre(Eq(3), DoubleNear(0.308537, kMaxError))));
EXPECT_NEAR(pld->InfinityMass(), 0, kMaxError);
}
TEST(PrivacyLossDistributionTest, AccurateComposition) {
base::StatusOr<std::unique_ptr<GaussianPrivacyLoss>> noise_privacy_loss =
GaussianPrivacyLoss::Create(
/*standard_deviation=*/4,
/*sensitivity=*/1);
ASSERT_OK(noise_privacy_loss);
std::unique_ptr<PrivacyLossDistribution> pld =
PrivacyLossDistribution::CreateForAdditiveNoise(
*noise_privacy_loss.value(), EstimateType::kPessimistic,
/*discretization_interval=*/1e-4);
constexpr int num_times = 40;
pld->Compose(num_times);
constexpr double epsilon = 10;
double delta = pld->GetDeltaForEpsilon(epsilon);
EXPECT_NEAR(delta, 3.33762759e-9, 1e-10);
}
} // namespace
} // namespace accounting
} // namespace differential_privacy