blob: f6d490f1f98da0fca63e1a41caf7a7675d5ebb3b [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 <limits.h>
#include <cmath>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/testing/proto_matchers.h"
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "algorithms/approx-bounds.h"
#include "algorithms/numerical-mechanisms-testing.h"
#include "algorithms/numerical-mechanisms.h"
#include "proto/util.h"
#include "proto/data.pb.h"
#include "proto/summary.pb.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, int64_t num_of_entries,
BoundedMean<T>* bm) {
bm->AddMultipleEntries(t, num_of_entries);
}
};
namespace {
using ::differential_privacy::test_utils::ZeroNoiseMechanism;
using ::testing::_;
using ::testing::DoubleEq;
using ::testing::DoubleNear;
using ::testing::Eq;
using ::differential_privacy::base::testing::EqualsProto;
using ::testing::Ge;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::Le;
using ::testing::Lt;
using ::testing::NotNull;
using ::testing::Property;
using ::differential_privacy::base::testing::StatusIs;
constexpr double kSmallEpsilon = 0.00000001;
constexpr double kNumSamples = 10000;
constexpr double kDefaultEpsilon = 1.1;
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, BasicTestWithExplicitLaplace) {
std::vector<TypeParam> a = {2, 4, 6, 8};
auto mean =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.SetLaplaceMechanism(std::make_unique<LaplaceMechanism::Builder>())
.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, BasicTestWithExplicitGaussian) {
std::vector<TypeParam> a = {2, 4, 6, 8};
auto mean =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1.0)
.SetDelta(1e-7)
.SetLower(1)
.SetUpper(9)
.SetLaplaceMechanism(std::make_unique<GaussianMechanism::Builder>())
.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 mean1 =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean1);
auto mean2 =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean2);
(*mean2)->AddEntries(a.begin(), a.end());
auto result1 = (*mean1)->PartialResult();
ASSERT_OK(result1);
auto result2 = (*mean2)->PartialResult();
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(std::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);
}
TEST(BoundedMeanTest, AddMultipleEntriesInvalidInputTest) {
auto mean =
typename BoundedMean<float>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-5)
.SetUpper(5)
.Build();
ASSERT_OK(mean);
(*mean)->AddEntry(4);
BoundedMeanTestPeer::AddMultipleEntries<float>(
std::numeric_limits<float>::quiet_NaN(), 1, mean.value().get());
auto result = (*mean)->PartialResult();
ASSERT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(*result), 4);
}
TYPED_TEST(BoundedMeanTest, AddMultipleEntriesInvalidNumberOfEntriesTest) {
auto mean =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-5)
.SetUpper(5)
.Build();
ASSERT_OK(mean);
(*mean)->AddEntry(4);
std::vector<int64_t> invalid_entries{0, -1,
std::numeric_limits<int64_t>::lowest()};
for (int64_t n_entries : invalid_entries) {
BoundedMeanTestPeer::AddMultipleEntries<TypeParam>(1, n_entries,
mean.value().get());
}
auto result = (*mean)->PartialResult();
ASSERT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(*result), 4);
}
TEST(BoundedMeanTest, InvalidParametersTest) {
EXPECT_THAT(BoundedMean<double>::Builder()
.SetEpsilon(std::numeric_limits<double>::infinity())
.Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Epsilon must be finite")));
EXPECT_THAT(BoundedMean<double>::Builder()
.SetEpsilon(std::numeric_limits<double>::quiet_NaN())
.Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Epsilon must be a valid numeric value")));
EXPECT_THAT(BoundedMean<double>::Builder().SetEpsilon(-0.5).Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Epsilon must be finite and positive")));
EXPECT_THAT(BoundedMean<double>::Builder().SetEpsilon(0).Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Epsilon must be finite and positive")));
}
TEST(BoundedMeanTest, NormalizedSumHasExpectedSensitivity) {
// Checking for rounding issues in the sensitivity calculation when the type
// of the bounds is int.
absl::StatusOr<std::unique_ptr<NumericalMechanism>> m =
BoundedMean<int>::BuildMechanismForNormalizedSum(
std::make_unique<LaplaceMechanism::Builder>(), kDefaultEpsilon,
/*delta=*/0,
/*l0_sensitivity=*/1,
/*max_contribution_per_partition=*/1, /*lower=*/0, /*upper=*/3);
ASSERT_OK(m);
LaplaceMechanism* lm = dynamic_cast<LaplaceMechanism*>(m->get());
ASSERT_THAT(lm, NotNull());
EXPECT_THAT(lm->GetSensitivity(), DoubleEq(3.0 / 2.0));
}
TYPED_TEST(BoundedMeanTest, InsufficientPrivacyBudgetTest) {
std::vector<TypeParam> a = {2, 4, 6, 8};
auto mean =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(1.0)
.SetLower(1)
.SetUpper(9)
.Build();
ASSERT_OK(mean);
(*mean)->AddEntries(a.begin(), a.end());
ASSERT_OK((*mean)->PartialResult());
EXPECT_THAT((*mean)->PartialResult(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("can only produce results once")));
}
// 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(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
// Automatic bounds are needed but there is no input, so the count-threshold
// 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;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-10)
.SetUpper(10)
.Build();
ASSERT_OK(bm);
BoundedMeanTestPeer::AddMultipleEntries<double>(
0, std::numeric_limits<int64_t>::max(), (*bm).get());
BoundedMeanTestPeer::AddMultipleEntries<double>(
0, std::numeric_limits<int64_t>::max(), (*bm).get());
(*bm)->AddEntry(1);
(*bm)->AddEntry(1);
(*bm)->AddEntry(1);
(*bm)->AddEntry(1);
absl::StatusOr<Output> result = (*bm)->PartialResult();
ASSERT_OK(result);
// If the int64_t partial_count_ overflows, it should wrap around to 2,
// resulting in a mean of (1+1+1+1) / 2 = 2, instead of the correct mean of
// nearly 0.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 2.0);
}
TEST(BoundedMeanTest, OverflowCountFromAddNoiseTypeCast) {
const double kBound = std::numeric_limits<int64_t>::max() / 2;
int i;
for (i = 0; i < 100; ++i) {
typename BoundedMean<double>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<LaplaceMechanism::Builder>())
.SetLower(-kBound)
.SetUpper(kBound)
.Build();
ASSERT_OK(bm);
BoundedMeanTestPeer::AddMultipleEntries<double>(
1, std::numeric_limits<int64_t>::max(), (*bm).get());
absl::StatusOr<Output> result = (*bm)->PartialResult();
ASSERT_OK(result);
// The noise applied to the count should eventually cause an overflow,
// resulting in a noised_count = 1, and thus a mean of around
// (1 * INT64_MAX) / 1 = INT64_MAX, clamped to the upper limit.
if (GetValue<double>(result.value()) >= kBound) {
// An overflow has happened, so return to end the test as a success.
return;
}
}
FAIL() << "No overflow occurred after " << i << " iterations.";
}
TEST(BoundedMeanTest, OverflowAddMultipleEntriesManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-std::numeric_limits<int64_t>::max() / 2)
.SetUpper(std::numeric_limits<int64_t>::max() / 2)
.Build();
ASSERT_OK(bm);
BoundedMeanTestPeer::AddMultipleEntries<int64_t>(
2, std::numeric_limits<int64_t>::max(), (*bm).get());
absl::StatusOr<Output> result = (*bm)->PartialResult();
ASSERT_OK(result);
// Adding 2 * int64_max should overflow and wrap around to -2, resulting in a
// mean of -2 / int64_max.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()),
-2.0 / std::numeric_limits<int64_t>::max());
}
TEST(BoundedMeanTest, OverflowAddEntryManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(0)
.SetUpper(std::numeric_limits<int64_t>::max())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntry(std::numeric_limits<int64_t>::max());
(*bm)->AddEntry(1);
(*bm)->AddEntry(1);
(*bm)->AddEntry(std::numeric_limits<int64_t>::max());
absl::StatusOr<Output> result = (*bm)->PartialResult();
EXPECT_OK(result);
// Overflowing should result in the running sum wrapping around to zero.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
}
TEST(BoundedMeanTest, UnderflowAddEntryManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(std::numeric_limits<int64_t>::lowest() + 1)
.SetUpper(0)
.Build();
ASSERT_OK(bm);
(*bm)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
(*bm)->AddEntry(-1);
(*bm)->AddEntry(-1);
(*bm)->AddEntry(-1);
(*bm)->AddEntry(-1);
(*bm)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
absl::StatusOr<Output> result = (*bm)->PartialResult();
EXPECT_OK(result);
// Underflowing should result in the running sum wrapping around to zero.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0);
}
TEST(BoundedMeanTest, OverflowRawCountMergeManualBoundsTest) {
typename BoundedMean<double>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(-10)
.SetUpper(10)
.Build();
ASSERT_OK(bm);
BoundedMeanTestPeer::AddMultipleEntries<double>(
0, std::numeric_limits<int64_t>::max(), (*bm).get());
Summary summary = (*bm)->Serialize();
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm2 = builder.Build();
ASSERT_OK(bm2);
BoundedMeanTestPeer::AddMultipleEntries<double>(
0, std::numeric_limits<int64_t>::max(), (*bm2).get());
(*bm2)->AddEntry(1);
(*bm2)->AddEntry(1);
(*bm2)->AddEntry(1);
(*bm2)->AddEntry(1);
ASSERT_OK((*bm2)->Merge(summary));
absl::StatusOr<Output> result = (*bm2)->PartialResult();
ASSERT_OK(result);
// If the int64_t partial_count_ overflows, it should wrap around to 2,
// resulting in a mean of (1+1+1+1) / 2 = 2, instead of the correct mean of
// nearly 0.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 2);
// Test post-overflow serialize & merge
summary = (*bm2)->Serialize();
bm2 = builder.Build();
EXPECT_OK((*bm2)->Merge(summary));
result = (*bm2)->PartialResult();
EXPECT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 2);
}
TEST(BoundedMeanTest, OverflowMergeManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(0)
.SetUpper(std::numeric_limits<int64_t>::max())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntry(std::numeric_limits<int64_t>::max());
Summary summary = (*bm)->Serialize();
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm2 = builder.Build();
(*bm2)->AddEntry(1);
(*bm2)->AddEntry(1);
(*bm2)->AddEntry(std::numeric_limits<int64_t>::max());
ASSERT_OK((*bm2)->Merge(summary));
absl::StatusOr<Output> result = (*bm2)->PartialResult();
EXPECT_OK(result);
// Overflowing should result in the running sum wrapping around to zero.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
// Test post-overflow serialize & merge
summary = (*bm2)->Serialize();
bm2 = builder.Build();
EXPECT_OK((*bm2)->Merge(summary));
result = (*bm2)->PartialResult();
EXPECT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
}
TEST(BoundedMeanTest, UnderflowMergeManualBoundsTest) {
typename BoundedMean<int64_t>::Builder builder;
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm =
builder
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetLower(std::numeric_limits<int64_t>::lowest() + 1)
.SetUpper(0)
.Build();
ASSERT_OK(bm);
(*bm)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
(*bm)->AddEntry(-1);
Summary summary = (*bm)->Serialize();
absl::StatusOr<std::unique_ptr<BoundedMean<int64_t>>> bm2 = builder.Build();
(*bm2)->AddEntry(-1);
(*bm2)->AddEntry(-1);
(*bm2)->AddEntry(-1);
(*bm2)->AddEntry(std::numeric_limits<int64_t>::lowest() + 1);
ASSERT_OK((*bm2)->Merge(summary));
absl::StatusOr<Output> result = (*bm2)->PartialResult();
EXPECT_OK(result);
// Underflowing should result in the running sum wrapping around to zero.
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
// Test post-overflow serialize & merge
summary = (*bm2)->Serialize();
bm2 = builder.Build();
EXPECT_OK((*bm2)->Merge(summary));
result = (*bm2)->PartialResult();
EXPECT_OK(result);
EXPECT_DOUBLE_EQ(GetValue<double>(result.value()), 0.0);
}
TYPED_TEST(BoundedMeanTest, SerializeMergeTest) {
typename BoundedMean<TypeParam>::Builder builder;
auto bm1 =
builder
.SetLaplaceMechanism(std::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) {
// Automatic bounding, so entries will be split and stored as partial sums.
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetThresholdForTest(0.5)
.SetNumBins(10)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(kDefaultEpsilon / 2)
.Build();
ASSERT_OK(bounds);
auto bm1 =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetApproxBounds(std::move(bounds).value())
.SetEpsilon(kDefaultEpsilon)
.Build();
ASSERT_OK(bm1);
(*bm1)->AddEntry(-10);
(*bm1)->AddEntry(4);
Summary summary = (*bm1)->Serialize();
(*bm1)->AddEntry(6);
// Merge summary into second BoundedVariance.
auto bounds2 =
typename ApproxBounds<TypeParam>::Builder()
.SetThresholdForTest(0.5)
.SetNumBins(10)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(kDefaultEpsilon / 2)
.Build();
ASSERT_OK(bounds2);
auto bm2 =
typename BoundedMean<TypeParam>::Builder()
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetApproxBounds(std::move(bounds2).value())
.SetEpsilon(kDefaultEpsilon)
.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)
.SetThresholdForTest(1.5)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(kDefaultEpsilon / 2)
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetApproxBounds(std::move(bounds).value())
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
absl::StatusOr<Output> output = (*bm)->PartialResult();
ASSERT_OK(output);
// 9 gets clamped to -1.
EXPECT_THAT(output->elements_size(), Eq(1));
EXPECT_THAT(GetValue<double>(output->elements(0).value()), DoubleEq(-3.0));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().lower_bound()),
Eq(-8));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().upper_bound()),
Eq(-1));
EXPECT_THAT(output->error_report().bounding_report().num_inputs(),
DoubleEq(a.size()));
EXPECT_THAT(output->error_report().bounding_report().num_outside(),
DoubleEq(2.0));
}
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)
.SetThresholdForTest(1.5)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(kDefaultEpsilon / 2)
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetApproxBounds(std::move(bounds).value())
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(kDefaultEpsilon)
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
absl::StatusOr<Output> output = (*bm)->PartialResult();
ASSERT_OK(output);
// -9 gets clamped to 1.
EXPECT_THAT(output->elements_size(), Eq(1));
EXPECT_THAT(GetValue<double>(output->elements(0).value()), DoubleEq(3.0));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().lower_bound()),
Eq(1));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().upper_bound()),
Eq(8));
EXPECT_THAT(output->error_report().bounding_report().num_inputs(),
DoubleEq(a.size()));
EXPECT_THAT(output->error_report().bounding_report().num_outside(),
DoubleEq(2.0));
}
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(0.5)
.SetThresholdForTest(0.5)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm = BoundedMean<int>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(bounds).value())
.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(0.5)
.SetNumBins(4)
.SetBase(2)
.SetScale(1)
.SetThresholdForTest(2)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(bounds).value())
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
absl::StatusOr<Output> output = (*bm)->PartialResult();
ASSERT_OK(output);
EXPECT_THAT(output->elements_size(), Eq(1));
EXPECT_THAT(GetValue<double>(output->elements(0).value()), DoubleEq(1.5));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().lower_bound()),
Eq(-1));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().upper_bound()),
Eq(4));
EXPECT_THAT(output->error_report().bounding_report().num_inputs(),
DoubleEq(a.size()));
EXPECT_THAT(output->error_report().bounding_report().num_outside(),
DoubleEq(2.0));
}
// Test not providing ApproxBounds and instead using the default.
TYPED_TEST(BoundedMeanTest, AutomaticBoundsDefault) {
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetLaplaceMechanism(std::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(BoundedMeanTest, BuilderWithApproxBoundsMoreBudgetThanTotalBudgetFails) {
absl::StatusOr<std::unique_ptr<ApproxBounds<double>>> bounds =
ApproxBounds<double>::Builder().SetEpsilon(1.1).Build();
ASSERT_OK(bounds);
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
BoundedMean<double>::Builder()
.SetEpsilon(1.09)
.SetApproxBounds(std::move(bounds).value())
.Build();
ASSERT_THAT(bm.status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Approx Bounds consumes more epsilon")));
}
// 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(0.5)
.SetNumBins(4)
.SetBase(2)
.SetScale(1)
.SetThresholdForTest(1.5)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetEpsilon(1)
.SetApproxBounds(std::move(bounds).value())
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(bm);
(*bm)->AddEntries(a.begin(), a.end());
absl::StatusOr<Output> output = (*bm)->PartialResult();
ASSERT_OK(output);
// -2 gets clamped to 0. 7 gets clamped to 4.
EXPECT_THAT(output->elements_size(), Eq(1));
EXPECT_THAT(GetValue<double>(output->elements(0).value()), DoubleEq(2.0));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().lower_bound()),
Eq(0));
EXPECT_THAT(GetValue<TypeParam>(
output->error_report().bounding_report().upper_bound()),
Eq(4));
EXPECT_THAT(output->error_report().bounding_report().num_inputs(),
DoubleEq(a.size()));
EXPECT_THAT(output->error_report().bounding_report().num_outside(),
DoubleEq(2.0));
}
TYPED_TEST(BoundedMeanTest, Reset) {
// Construct bounded sum with approximate bounding.
auto bounds =
typename ApproxBounds<TypeParam>::Builder()
.SetNumBins(3)
.SetBase(10)
.SetScale(1)
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetThresholdForTest(0.5)
.SetEpsilon(kDefaultEpsilon / 2)
.Build();
ASSERT_OK(bounds);
auto bm =
typename BoundedMean<TypeParam>::Builder()
.SetApproxBounds(std::move(bounds).value())
.SetLaplaceMechanism(std::make_unique<ZeroNoiseMechanism::Builder>())
.SetEpsilon(kDefaultEpsilon)
.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;
absl::StatusOr<std::unique_ptr<BoundedMean<TypeParam>>> bm =
typename BoundedMean<TypeParam>::Builder().SetEpsilon(epsilon).Build();
ASSERT_OK(bm);
auto* bmi = dynamic_cast<BoundedMeanWithApproxBounds<TypeParam>*>(bm->get());
EXPECT_NEAR(bmi->GetEpsilon(), epsilon, 1e-10);
EXPECT_NEAR(bmi->GetEpsilon(),
bmi->GetBoundingEpsilon() + bmi->GetAggregationEpsilon(), 1e-10);
EXPECT_GT(bmi->GetBoundingEpsilon(), 0);
EXPECT_LT(bmi->GetBoundingEpsilon(), epsilon);
EXPECT_GT(bmi->GetAggregationEpsilon(), 0);
EXPECT_LT(bmi->GetAggregationEpsilon(), epsilon);
}
TEST(BoundedMeanWithApproxBoundsTest, ConsumesAllBudgetOfNumericalMechanisms) {
std::unique_ptr<test_utils::MockLaplaceMechanism> mock_count_mechanism =
std::make_unique<test_utils::MockLaplaceMechanism>();
std::unique_ptr<test_utils::MockLaplaceMechanism> mock_sum_mechanism =
std::make_unique<test_utils::MockLaplaceMechanism>();
test_utils::MockLaplaceMechanism* mock_count_ptr = mock_count_mechanism.get();
test_utils::MockLaplaceMechanism* mock_sum_ptr = mock_sum_mechanism.get();
// For a double bounded mean, we add int noise to the count and double noise
// to the sum.
EXPECT_CALL(*mock_count_ptr, AddInt64Noise(_)).Times(1);
EXPECT_CALL(*mock_sum_ptr, AddDoubleNoise(_)).Times(1);
BoundedMeanWithFixedBounds<double> bm(
/*epsilon=*/1.0,
/*delta=*/0,
/*lower=*/-1,
/*upper=*/1, std::move(mock_sum_mechanism),
std::move(mock_count_mechanism));
for (int i = 0; i < 10; ++i) {
bm.AddEntry(1.0);
}
EXPECT_OK(bm.PartialResult());
}
TEST(BoundedMeanWithFixedBoundsTest, ApproxBoundsMechanismHasExpectedVariance) {
const int max_partitions_contributed = 2;
const int max_contributions_per_partition = 3;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetMaxPartitionsContributed(max_partitions_contributed)
.SetMaxContributionsPerPartition(max_contributions_per_partition)
.Build();
ASSERT_OK(bm);
auto* bm_with_approx_bounds =
static_cast<BoundedMeanWithApproxBounds<double>*>(bm.value().get());
ASSERT_THAT(bm_with_approx_bounds, NotNull());
const double expected_variance =
LaplaceMechanism::Builder()
.SetEpsilon(kDefaultEpsilon / 2)
.SetL0Sensitivity(max_partitions_contributed)
.SetLInfSensitivity(max_contributions_per_partition)
.Build()
.value()
->GetVariance();
ASSERT_THAT(bm_with_approx_bounds->GetApproxBoundsForTesting()
->GetMechanismForTesting()
->GetVariance(),
DoubleEq(expected_variance));
}
TEST(BoundedMeanTest, ConfidenceIntervalWithNoisedResultOkWithPosMidpoint) {
const double lower = -1;
const double upper = 1;
const double confidence_level = 0.95;
// Use 1000 as noised contribution count and 500 as noised total sum -> CI
// midpoint should be close to 0.5.
const double noised_count = 1000;
const double noised_sum = 500;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
typename BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(bm);
BoundedMeanWithFixedBounds<double>* fixed_bm =
dynamic_cast<BoundedMeanWithFixedBounds<double>*>(bm->get());
ASSERT_THAT(fixed_bm, NotNull());
absl::StatusOr<ConfidenceInterval> ci = fixed_bm->NoiseConfidenceInterval(
confidence_level, noised_sum, noised_count);
ASSERT_OK(ci);
EXPECT_THAT(ci->lower_bound(), Lt(ci->upper_bound()));
EXPECT_THAT(ci->confidence_level(), DoubleEq(confidence_level));
const double ci_midpoint = (ci->upper_bound() + ci->lower_bound()) / 2.0;
ASSERT_THAT(ci_midpoint, DoubleNear(0.5, 0.01));
}
TEST(BoundedMeanTest, ConfidenceIntervalWithNoisedResultOkWithNegMidpoint) {
const double lower = -1;
const double upper = 1;
const double confidence_level = 0.95;
// Use 1000 as noised contribution count and -500 as noised total sum -> CI
// midpoint should be close to -0.5.
const double noised_count = 1000;
const double noised_sum = -500;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
typename BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(bm);
BoundedMeanWithFixedBounds<double>* fixed_bm =
dynamic_cast<BoundedMeanWithFixedBounds<double>*>(bm->get());
ASSERT_THAT(fixed_bm, NotNull());
absl::StatusOr<ConfidenceInterval> ci = fixed_bm->NoiseConfidenceInterval(
confidence_level, noised_sum, noised_count);
ASSERT_OK(ci);
EXPECT_THAT(ci->lower_bound(), Lt(ci->upper_bound()));
EXPECT_THAT(ci->confidence_level(), DoubleEq(confidence_level));
const double ci_midpoint = (ci->upper_bound() + ci->lower_bound()) / 2.0;
ASSERT_THAT(ci_midpoint, DoubleNear(-0.5, 0.01));
}
TEST(BoundedMeanTest, ConfidenceIntervalWithLowerLevelGetsTighter) {
const double lower = -1;
const double upper = 1;
const double confidence_level_higher = 0.95;
const double confidence_level_lower = 0.8;
const double noised_count = 1000;
const double noised_sum = -500;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
typename BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(bm);
BoundedMeanWithFixedBounds<double>* fixed_bm =
dynamic_cast<BoundedMeanWithFixedBounds<double>*>(bm->get());
ASSERT_THAT(fixed_bm, NotNull());
absl::StatusOr<ConfidenceInterval> ci_higher =
fixed_bm->NoiseConfidenceInterval(confidence_level_higher, noised_sum,
noised_count);
ASSERT_OK(ci_higher);
absl::StatusOr<ConfidenceInterval> ci_lower =
fixed_bm->NoiseConfidenceInterval(confidence_level_lower, noised_sum,
noised_count);
ASSERT_OK(ci_lower);
// The CI returned with higher confidence level has to be included in the CI
// for the lower confidence level.
EXPECT_THAT(ci_higher->lower_bound(), Lt(ci_lower->lower_bound()));
EXPECT_THAT(ci_higher->upper_bound(), Gt(ci_lower->upper_bound()));
}
TEST(BoundedMeanTest, ConfidenceIntervalWithMoreDataGetsTighter) {
const double lower = -1;
const double upper = 1;
const double confidence_level = 0.95;
const double noised_count_many_users = 1000;
const double noised_sum_many_users = -500;
const double noised_count_fewer_users = 100;
const double noised_sum_fewer_users = -50;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
typename BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(bm);
BoundedMeanWithFixedBounds<double>* fixed_bm =
dynamic_cast<BoundedMeanWithFixedBounds<double>*>(bm->get());
ASSERT_THAT(fixed_bm, NotNull());
absl::StatusOr<ConfidenceInterval> ci_many_users =
fixed_bm->NoiseConfidenceInterval(confidence_level, noised_sum_many_users,
noised_count_many_users);
ASSERT_OK(ci_many_users);
absl::StatusOr<ConfidenceInterval> ci_fewer_users =
fixed_bm->NoiseConfidenceInterval(
confidence_level, noised_sum_fewer_users, noised_count_fewer_users);
ASSERT_OK(ci_fewer_users);
// The CI returned with fewer users has to be included in the CI for many
// users.
EXPECT_THAT(ci_fewer_users->lower_bound(), Lt(ci_many_users->lower_bound()));
EXPECT_THAT(ci_fewer_users->upper_bound(), Gt(ci_many_users->upper_bound()));
}
TEST(BoundedMeanTest, EmptyFixedBoundsMeanOutputHasConfidenceInterval) {
const double confidence_level = 0.987654;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetLower(0.0)
.SetUpper(1.0)
.Build();
ASSERT_OK(bm);
absl::StatusOr<Output> output = bm->get()->PartialResult(confidence_level);
ASSERT_OK(output);
EXPECT_TRUE(output->elements(0).has_noise_confidence_interval());
EXPECT_THAT(
output->elements(0).noise_confidence_interval().confidence_level(),
DoubleEq(confidence_level));
}
TEST(BoundedMeanTest, FixedBoundsMeanOutputHasConfidenceIntervalWithinBounds) {
const double confidence_level = 0.987654;
const double lower = -0.01;
const double upper = 1.01;
const int num_contributions = 1000;
const double input = (lower + upper) / 2.0;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
BoundedMean<double>::Builder()
.SetEpsilon(kDefaultEpsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(bm);
for (int i = 0; i < num_contributions; ++i) {
bm->get()->AddEntry(input);
}
absl::StatusOr<Output> output = bm->get()->PartialResult(confidence_level);
ASSERT_OK(output);
EXPECT_THAT(
output->elements(0).noise_confidence_interval().confidence_level(),
DoubleEq(confidence_level));
EXPECT_THAT(output->elements(0).noise_confidence_interval().lower_bound(),
Gt(lower));
EXPECT_THAT(output->elements(0).noise_confidence_interval().upper_bound(),
Lt(upper));
}
TEST(BoundedMeanTest, ApproxBoundsMeanOutputHasConfidenceInterval) {
const double confidence_level = 0.95432;
const int num_contributions = 1000;
const double input = 3.2;
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> bm =
BoundedMean<double>::Builder().SetEpsilon(kDefaultEpsilon).Build();
ASSERT_OK(bm);
for (int i = 0; i < num_contributions; ++i) {
bm->get()->AddEntry(input);
}
absl::StatusOr<Output> output = bm->get()->PartialResult(confidence_level);
ASSERT_OK(output);
const double lower_bound =
GetValue<double>(output->error_report().bounding_report().lower_bound());
const double upper_bound =
GetValue<double>(output->error_report().bounding_report().upper_bound());
EXPECT_THAT(
output->elements(0).noise_confidence_interval(),
AllOf(Property("confidence_level", &ConfidenceInterval::confidence_level,
DoubleEq(confidence_level)),
Property("lower_bound", &ConfidenceInterval::lower_bound,
Ge(lower_bound)),
Property("upper_bound", &ConfidenceInterval::upper_bound,
Le(upper_bound))));
}
} // namespace
} // namespace differential_privacy