blob: 701a533fbba8f37981bf58d700889d9e4536abde [file] [log] [blame]
//
// Copyright 2019 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.
//
#include "algorithms/bounded-mean.h"
#include "base/testing/proto_matchers.h"
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "algorithms/approx-bounds.h"
#include "algorithms/numerical-mechanisms-testing.h"
namespace differential_privacy {
// Provides limited-scope static methods for interacting with a BoundedMean
// object for testing purposes.
class BoundedMeanTestPeer {
public:
template <typename T>
static void AddMultipleEntries(const T& t, uint64_t num_of_entries,
BoundedMean<T>* bm) {
bm->AddMultipleEntries(t, num_of_entries);
}
};
namespace {
using ::differential_privacy::test_utils::ZeroNoiseMechanism;
using ::differential_privacy::base::testing::EqualsProto;
using ::testing::HasSubstr;
using ::differential_privacy::base::testing::StatusIs;
constexpr double kSmallEpsilon = 0.00000001;
constexpr double kNumSamples = 10000;
template <typename T>
class BoundedMeanTest : public ::testing::Test {};
typedef ::testing::Types<int64_t, double> NumericTypes;
TYPED_TEST_SUITE(BoundedMeanTest, NumericTypes);
TYPED_TEST(BoundedMeanTest, BasicTest) {
std::vector<TypeParam> a = {2, 4, 6, 8};
auto mean = typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean);
auto result = (*mean)->Result(a.begin(), a.end());
ASSERT_OK(result);
EXPECT_GE(GetValue<double>(*result), 1);
EXPECT_LE(GetValue<double>(*result), 9);
}
TYPED_TEST(BoundedMeanTest, RepeatedResultTest) {
std::vector<TypeParam> a = {2, 4, 6, 8};
auto mean =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean);
(*mean)->AddEntries(a.begin(), a.end());
auto result1 = (*mean)->PartialResult(0.5);
ASSERT_OK(result1);
auto result2 = (*mean)->PartialResult(0.5);
ASSERT_OK(result2);
EXPECT_DOUBLE_EQ(GetValue<double>(*result1), GetValue<double>(*result2));
}
TYPED_TEST(BoundedMeanTest, BasicTestWithoutIterator) {
std::vector<TypeParam> a = {2, 4, 6, 8};
auto mean = typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean);
for (const auto& input : a) {
(*mean)->AddEntry(input);
}
auto result = (*mean)->PartialResult();
ASSERT_OK(result);
EXPECT_GE(GetValue<double>(*result), 1);
EXPECT_LE(GetValue<double>(*result), 9);
}
TYPED_TEST(BoundedMeanTest, BasicMultipleEntriesTest) {
std::vector<TypeParam> a = {1, 2, 3, 4, 5};
auto mean =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(1)
.SetUpper(5)
.Build();
ASSERT_OK(mean);
for (const auto& input : a) {
BoundedMeanTestPeer::AddMultipleEntries<TypeParam>(input, input,
mean.value().get());
}
auto result = (*mean)->PartialResult();
ASSERT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(*result), 11.0 / 3.0);
}
// This test verifies that BoundedMean never returns a value outside of the
// bounds, even if BoundedSum/Count would be outside the bounds.
TYPED_TEST(BoundedMeanTest, LowClampTest) {
std::vector<TypeParam> a = {0, 0, 0, 0};
for (int i = 0; i < kNumSamples; ++i) {
auto mean = typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(kSmallEpsilon)
.SetLower(0)
.SetUpper(10)
.Build();
ASSERT_OK(mean);
auto result = (*mean)->Result(a.begin(), a.end());
ASSERT_OK(result);
EXPECT_GE(GetValue<double>(*result), 0);
}
}
TYPED_TEST(BoundedMeanTest, HighClampTest) {
std::vector<TypeParam> a = {10, 10, 10, 10};
for (int i = 0; i < kNumSamples; ++i) {
auto mean = typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(kSmallEpsilon)
.SetLower(0)
.SetUpper(10)
.Build();
ASSERT_OK(mean);
auto result = (*mean)->Result(a.begin(), a.end());
EXPECT_LE(GetValue<double>(*result), 10);
}
}
TYPED_TEST(BoundedMeanTest, LargeEpsilonTest) {
std::vector<TypeParam> a = {6, 3, 5, 1, 7, 2, 3, 3, 4, 6, 5, 1};
// Compute the expected mean
double expected = 0;
for (auto value : a) {
expected += value;
}
expected /= a.size();
auto mean = typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(std::pow(10, 20))
.SetLower(1)
.SetUpper(7)
.Build();
ASSERT_OK(mean);
auto actual = (*mean)->Result(a.begin(), a.end());
ASSERT_OK(actual);
EXPECT_DOUBLE_EQ(GetValue<double>(*actual), expected);
}
TYPED_TEST(BoundedMeanTest, PropagateApproxBoundsError) {
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
// Automatic bounds are needed but there is no input, so the count-threshhold
// should exceed any bin count.
EXPECT_THAT((*bm)->PartialResult(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("Bin count threshold was too large")));
}
TYPED_TEST(BoundedMeanTest, MaxContributionsVarianceTest) {
// Use following inputs with mean 0.
const std::vector<TypeParam> input = {1, 1, -1, -1};
std::function<double(int)> sample_variance_for_max_contributions =
[&input](int max_contributions) {
double sum = 0;
for (int i = 0; i < kNumSamples; ++i) {
auto mean = typename BoundedMean<TypeParam>::Builder()
.SetMaxContributionsPerPartition(max_contributions)
.SetEpsilon(1)
.SetLower(-1)
.SetUpper(1)
.Build();
CHECK_EQ(mean.status(), absl::OkStatus());
auto out = (*mean)->Result(input.begin(), input.end());
CHECK_EQ(out.status(), absl::OkStatus());
sum += std::pow(GetValue<double>(*out), 2);
}
return sum / (kNumSamples - 1);
};
// We expect the sample variance with max contribution 2 to be (significantly)
// bigger than with max contribution 1.
EXPECT_GT(sample_variance_for_max_contributions(2),
1.1 * sample_variance_for_max_contributions(1));
}
TEST(BoundedMeanTest, OverflowRawCountTest) {
typename BoundedMean<double>::Builder builder;
std::unique_ptr<BoundedMean<double>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(0)
.SetUpper(10)
.Build()
.ValueOrDie();
BoundedMeanTestPeer::AddMultipleEntries<double>(
1, std::numeric_limits<uint64_t>::max(), bm.get());
bm->AddEntry(1);
bm->AddEntry(1);
auto result = bm->PartialResult();
EXPECT_OK(result.status());
EXPECT_DOUBLE_EQ(
GetValue<double>(result.value()),
(static_cast<double>(std::numeric_limits<uint64_t>::max()) + 2) /
std::numeric_limits<uint64_t>::max());
}
TEST(BoundedMeanTest, OverflowAddMultipleEntriesManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
std::unique_ptr<BoundedMean<int64_t>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-std::numeric_limits<int64_t>::max() / 2)
.SetUpper(std::numeric_limits<int64_t>::max() / 2)
.Build()
.ValueOrDie();
BoundedMeanTestPeer::AddMultipleEntries<int64_t>(
2, std::numeric_limits<int64_t>::max(), bm.get());
auto result = bm->PartialResult();
EXPECT_OK(result.status());
// Expect 1, since 2 * int64_max should be capped at int64_max by
// SafeMultiply(), resulting in a mean of int64_max / int64_max = 1.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 1.0);
}
TEST(BoundedMeanTest, OverflowAddEntryManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
std::unique_ptr<BoundedMean<int64_t>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(0)
.SetUpper(std::numeric_limits<int64_t>::max())
.Build()
.ValueOrDie();
bm->AddEntry(std::numeric_limits<int64_t>::max());
bm->AddEntry(1);
bm->AddEntry(1);
bm->AddEntry(std::numeric_limits<int64_t>::max());
auto result = bm->PartialResult();
EXPECT_OK(result.status());
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
std::numeric_limits<int64_t>::max() / 4);
}
TEST(BoundedMeanTest, UnderflowAddEntryManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
std::unique_ptr<BoundedMean<int64_t>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(std::numeric_limits<int64_t>::lowest())
.SetUpper(0)
.Build()
.ValueOrDie();
bm->AddEntry(std::numeric_limits<int64_t>::lowest());
bm->AddEntry(-1);
bm->AddEntry(-1);
bm->AddEntry(std::numeric_limits<int64_t>::lowest());
auto result = bm->PartialResult();
EXPECT_OK(result.status());
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
std::numeric_limits<int64_t>::lowest() / 4);
}
TEST(BoundedMeanTest, OverflowRawCountMergeManualBoundsTest) {
typename BoundedMean<double>::Builder builder;
std::unique_ptr<BoundedMean<double>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-std::numeric_limits<double>::max() / 2)
.SetUpper(std::numeric_limits<double>::max() / 2)
.Build()
.ValueOrDie();
BoundedMeanTestPeer::AddMultipleEntries<double>(
1, std::numeric_limits<uint64_t>::max(), bm.get());
Summary summary = bm->Serialize();
std::unique_ptr<BoundedMean<double>> bm2 = builder.Build().ValueOrDie();
bm2->AddEntry(1);
bm2->AddEntry(1);
EXPECT_OK(bm2->Merge(summary));
auto result = bm2->PartialResult();
EXPECT_OK(result.status());
EXPECT_DOUBLE_EQ(
GetValue<double>(result.value()),
(static_cast<double>(std::numeric_limits<uint64_t>::max()) + 2) /
std::numeric_limits<uint64_t>::max());
}
TEST(BoundedMeanTest, OverflowMergeManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
std::unique_ptr<BoundedMean<int64_t>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(0)
.SetUpper(std::numeric_limits<int64_t>::max())
.Build()
.ValueOrDie();
bm->AddEntry(std::numeric_limits<int64_t>::max());
Summary summary = bm->Serialize();
std::unique_ptr<BoundedMean<int64_t>> bm2 = builder.Build().ValueOrDie();
bm2->AddEntry(1);
bm2->AddEntry(1);
bm2->AddEntry(1);
EXPECT_OK(bm2->Merge(summary));
auto result = bm2->PartialResult();
EXPECT_OK(result.status());
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
std::numeric_limits<int64_t>::max() / 4);
}
TEST(BoundedMeanTest, UnderflowMergeManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
std::unique_ptr<BoundedMean<int64_t>> bm =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(std::numeric_limits<int64_t>::lowest() + 1)
.SetUpper(0)
.Build()
.ValueOrDie();
bm->AddEntry(std::numeric_limits<int64_t>::lowest());
Summary summary = bm->Serialize();
std::unique_ptr<BoundedMean<int64_t>> bm2 = builder.Build().ValueOrDie();
bm2->AddEntry(-1);
bm2->AddEntry(-1);
bm2->AddEntry(-1);
EXPECT_OK(bm2->Merge(summary));
auto result = bm2->PartialResult();
EXPECT_OK(result.status());
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
std::numeric_limits<int64_t>::lowest() / 4);
}
TYPED_TEST(BoundedMeanTest, SerializeMergeTest) {
typename BoundedMean<TypeParam>::Builder builder;
auto bm1 =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(0)
.SetUpper(3)
.Build();
ASSERT_OK(bm1);
(*bm1)->AddEntry(1);
Summary summary = (*bm1)->Serialize();
(*bm1)->AddEntry(3);
auto bm2 = builder.Build();
ASSERT_OK(bm2);
EXPECT_OK((*bm2)->Merge(summary));
(*bm2)->AddEntry(3);
auto result1 = (*bm1)->PartialResult();
ASSERT_OK(result1);
auto result2 = (*bm2)->PartialResult();
ASSERT_OK(result2);
EXPECT_DOUBLE_EQ(GetValue<double>(*result1), GetValue<double>(*result2));
}
TYPED_TEST(BoundedMeanTest, SerializeMergePartialSumsTest) {
typename ApproxBounds<TypeParam>::Builder bounds_builder;
typename BoundedMean<TypeParam>::Builder builder;
// Automatic bounding, so entries will be split and stored as partial sums.
auto bounds =
bounds_builder.SetThreshold(1)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm1 =
builder
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetApproxBounds(std::move(*bounds))
.Build();
ASSERT_OK(bm1);
(*bm1)->AddEntry(-10);
(*bm1)->AddEntry(4);
Summary summary = (*bm1)->Serialize();
(*bm1)->AddEntry(6);
// Merge summary into second BoundedVariance.
auto bounds2 = bounds_builder.Build();
ASSERT_OK(bounds2);
auto bm2 = builder.SetApproxBounds(std::move(*bounds2)).Build();
ASSERT_OK(bm2);
(*bm2)->AddEntry(6);
EXPECT_OK((*bm2)->Merge(summary));
// Check equality. Bounds are set to [-16, 8].
auto result1 = (*bm1)->PartialResult();
ASSERT_OK(result1);
auto result2 = (*bm2)->PartialResult();
ASSERT_OK(result2);
EXPECT_DOUBLE_EQ(GetValue<double>(*result1), GetValue<double>(*result2));
}
TYPED_TEST(BoundedMeanTest, AutomaticBoundsNegative) {
std::vector<TypeParam> a = {9, -2, -2, -1, -6, -6};
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetNumBins(5)
.SetBase(2)
.SetScale(1)
.SetThreshold(2)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(*bounds))
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
// 9 gets clamped to -1.
Output expected_output;
AddToOutput<double>(&expected_output, -3);
BoundingReport* report =
expected_output.mutable_error_report()->mutable_bounding_report();
SetValue<TypeParam>(report->mutable_lower_bound(), -8);
SetValue<TypeParam>(report->mutable_upper_bound(), -1);
report->set_num_inputs(a.size());
report->set_num_outside(2);
auto actual_output = (*bm)->PartialResult();
ASSERT_OK(actual_output);
EXPECT_THAT(*actual_output, EqualsProto(expected_output));
}
TYPED_TEST(BoundedMeanTest, AutomaticBoundsPositive) {
std::vector<TypeParam> a = {-9, 2, 2, 1, 6, 6};
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetNumBins(5)
.SetBase(2)
.SetScale(1)
.SetThreshold(2)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetApproxBounds(std::move(*bounds))
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
// -9 gets clamped to 1.
Output expected_output;
AddToOutput<double>(&expected_output, 3);
BoundingReport* report =
expected_output.mutable_error_report()->mutable_bounding_report();
SetValue<TypeParam>(report->mutable_lower_bound(), 1);
SetValue<TypeParam>(report->mutable_upper_bound(), 8);
report->set_num_inputs(a.size());
report->set_num_outside(2);
auto actual_output = (*bm)->PartialResult();
ASSERT_OK(actual_output);
EXPECT_THAT(*actual_output, EqualsProto(expected_output));
}
TEST(BoundedMeanTest, DropNanEntries) {
std::vector<double> a = {2, 4, 6, NAN, 8};
auto mean = BoundedMean<double>::Builder()
.SetEpsilon(1)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean);
auto result = (*mean)->Result(a.begin(), a.end());
EXPECT_GE(GetValue<double>(*result), 1);
EXPECT_LE(GetValue<double>(*result), 9);
}
TEST(BoundedMeanTest, SensitivityOverflow) {
// Check for error when upper - lower causes integer overflow.
EXPECT_EQ(BoundedMean<int>::Builder()
.SetEpsilon(1.0)
.SetLower(INT_MIN)
.SetUpper(INT_MAX)
.Build()
.status()
.message(),
"Upper - lower caused integer overflow.");
}
TEST(BoundedMeanTest, SensitivityOverflowApproxBounds) {
auto bounds =
ApproxBounds<int>::Builder()
.SetEpsilon(1)
.SetThreshold(1)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm = BoundedMean<int>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(*bounds))
.Build();
ASSERT_OK(bm);
// Adding these two entries make the bounds [-1, max]. Sensitivity is
// calculated |max - (-1)|, which overflowss.
(*bm)->AddEntry(-1);
(*bm)->AddEntry(INT_MAX);
EXPECT_THAT((*bm)->PartialResult(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Upper - lower caused integer overflow.")));
}
// Test when 0 is in [lower, upper].
TYPED_TEST(BoundedMeanTest, AutomaticBoundsContainZero) {
std::vector<TypeParam> a = {4,
4,
-1,
-1,
std::numeric_limits<TypeParam>::lowest(),
std::numeric_limits<TypeParam>::max()};
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetEpsilon(1)
.SetNumBins(4)
.SetBase(2)
.SetScale(1)
.SetThreshold(2)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(*bounds))
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
Output expected_output;
AddToOutput<double>(&expected_output, 1.5);
BoundingReport* report =
expected_output.mutable_error_report()->mutable_bounding_report();
SetValue<TypeParam>(report->mutable_lower_bound(), -1);
SetValue<TypeParam>(report->mutable_upper_bound(), 4);
report->set_num_inputs(a.size());
report->set_num_outside(2);
auto actual_output = (*bm)->PartialResult();
ASSERT_OK(actual_output);
EXPECT_THAT(*actual_output, EqualsProto(expected_output));
}
// Test not providing ApproxBounds and instead using the default.
TYPED_TEST(BoundedMeanTest, AutomaticBoundsDefault) {
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
std::vector<TypeParam> big(1000, 10);
std::vector<TypeParam> small(1000, -10);
(*bm)->AddEntries(big.begin(), big.end());
(*bm)->AddEntries(small.begin(), small.end());
BoundingReport bounding_report;
SetValue<TypeParam>(bounding_report.mutable_lower_bound(), -16);
SetValue<TypeParam>(bounding_report.mutable_upper_bound(), 16);
bounding_report.set_num_inputs(big.size() + small.size());
bounding_report.set_num_outside(0);
Output::ErrorReport expected_report;
*(expected_report.mutable_bounding_report()) = bounding_report;
auto result = (*bm)->PartialResult();
ASSERT_OK(result);
EXPECT_THAT(result->error_report(), EqualsProto(expected_report));
EXPECT_NEAR(GetValue<double>(result->elements(0).value()), 0.0,
std::pow(10, -10));
}
// Test when a bound is 0.
TYPED_TEST(BoundedMeanTest, AutomaticBoundsZero) {
std::vector<TypeParam> a = {0, 0, 4, 4, -2, 2, 7};
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetEpsilon(1)
.SetNumBins(4)
.SetBase(2)
.SetScale(1)
.SetThreshold(2)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(*bounds))
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
// -2 gets clamped to 0. 7 gets clamped to 4.
Output expected_output;
AddToOutput<double>(&expected_output, 2);
BoundingReport* report =
expected_output.mutable_error_report()->mutable_bounding_report();
SetValue<TypeParam>(report->mutable_lower_bound(), 0);
SetValue<TypeParam>(report->mutable_upper_bound(), 4);
report->set_num_inputs(a.size());
report->set_num_outside(2);
auto actual_output = (*bm)->PartialResult();
ASSERT_OK(actual_output);
EXPECT_THAT(*actual_output, EqualsProto(expected_output));
}
TYPED_TEST(BoundedMeanTest, Reset) {
// Construct bounded sum with approximate bounding.
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetNumBins(3)
.SetBase(10)
.SetScale(1)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.SetThreshold(1)
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetApproxBounds(std::move(*bounds))
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
// Reset between adding vectors.
std::vector<TypeParam> a = {-10, 1000};
std::vector<TypeParam> b = {-100, 100, 3};
(*bm)->AddEntries(a.begin(), a.end());
(*bm)->Reset();
(*bm)->AddEntries(b.begin(), b.end());
// Check result is only affected by vector b.
auto result = (*bm)->PartialResult();
EXPECT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(result->elements(0).value()), 1);
}
TYPED_TEST(BoundedMeanTest, MemoryUsed) {
auto bm = typename BoundedMean<TypeParam>::Builder().Build();
EXPECT_GT((*bm)->MemoryUsed(), 0);
}
TYPED_TEST(BoundedMeanTest, SplitsEpsilonWithAutomaticBounds) {
double epsilon = 1.0;
auto bm =
typename BoundedMean<TypeParam>::Builder().SetEpsilon(epsilon).Build();
EXPECT_NEAR((*bm)->GetEpsilon(), epsilon, 1e-10);
EXPECT_NEAR((*bm)->GetEpsilon(),
(*bm)->GetBoundingEpsilon() + (*bm)->GetAggregationEpsilon(),
1e-10);
EXPECT_GT((*bm)->GetBoundingEpsilon(), 0);
EXPECT_LT((*bm)->GetBoundingEpsilon(), epsilon);
EXPECT_GT((*bm)->GetAggregationEpsilon(), 0);
EXPECT_LT((*bm)->GetAggregationEpsilon(), epsilon);
}
} // namespace
} // namespace differential_privacy