blob: d9f918306ee0b26d7c1e4d73b960a7dadd0cc0c6 [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.
//
#include "algorithms/quantile-tree.h"
#include <memory>
#include "base/testing/proto_matchers.h"
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/random.h"
#include "algorithms/numerical-mechanisms-testing.h"
#include "proto/confidence-interval.pb.h"
namespace differential_privacy {
// Provides limited-scope static methods for interacting with a QuantileTree
// object for testing purposes.
class QuantileTreeTestPeer {
public:
template <typename T>
static void AddMultipleEntries(const T& t, int64_t num_of_entries,
QuantileTree<T>* qt) {
qt->AddMultipleEntries(t, num_of_entries);
}
};
namespace {
using ::differential_privacy::test_utils::ZeroNoiseMechanism;
using ::testing::HasSubstr;
using ::differential_privacy::base::testing::StatusIs;
const double kTestDefaultEpsilon = 0.5;
const double kDefaultDelta = 1e-5;
const int kDefaultMaxContributionsPerPartition = 5;
const int kDefaultMaxPartitionsContributed = 12;
const int kDefaultDatasetSize = 1001;
const int kNumRanksToTest = 10;
const std::vector<double> kQuantilesToTest{0.0, 0.005, 0.01, 0.05, 0.25, 0.5,
0.75, 0.95, 0.99, 0.995, 1.0};
const std::vector<double> kConfidenceLevelsToTest{
0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.999, 0.9999};
template <typename T>
class QuantileTreeTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
void StatisticallyAssertConfidenceLevel(
const std::vector<T> entries,
typename QuantileTree<T>::Builder tree_builder,
std::unique_ptr<NumericalMechanismBuilder> mech_builder) {
// Prepare a hit counter that counts the number of times a confidence
// interval contains the raw value keyed by quantile and confidence level.
std::unordered_map<double, std::unordered_map<double, int>> hit_counter;
std::unique_ptr<QuantileTree<T>> quantile_tree =
tree_builder.Build().value();
for (const T& entry : entries) {
quantile_tree->AddEntry(entry);
}
typename QuantileTree<T>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder =
std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<T>::Privatized zero_noise_results =
quantile_tree->MakePrivate(dp_params).value();
// Approximate the raw quantile values by querying a zero noise instance of
// the quantiles mechanism.
std::unordered_map<double, double> zero_noise_quantiles;
for (double quantile : kQuantilesToTest) {
zero_noise_quantiles[quantile] =
zero_noise_results.GetQuantile(quantile).value();
}
dp_params.mechanism_builder = std::move(mech_builder);
// Sample the hit frequencies.
for (int i = 0; i < kNumberOfSamples_; i++) {
typename QuantileTree<T>::Privatized noised_results =
quantile_tree->MakePrivate(dp_params).value();
// Check whether the confidence intervals contain the respective raw value
// for all quantiles and confidence levels.
for (double quantile : kQuantilesToTest) {
for (double level : kConfidenceLevelsToTest) {
absl::StatusOr<ConfidenceInterval> noised_ci_or_status =
noised_results.ComputeNoiseConfidenceInterval(quantile, level);
EXPECT_OK(noised_ci_or_status);
ConfidenceInterval noised_ci = noised_ci_or_status.value();
if (noised_ci.lower_bound() <= zero_noise_quantiles[quantile] &&
zero_noise_quantiles[quantile] <= noised_ci.upper_bound()) {
++hit_counter[quantile][level];
}
}
}
}
// Expect that the hit frequency was sufficiently large for all quantiles
// and confidence levels.
for (double quantile : kQuantilesToTest) {
for (double level : kConfidenceLevelsToTest) {
EXPECT_GE(hit_counter[quantile][level], kAcceptableThresholds_[level]);
}
}
}
const int kNumberOfSamples_ = 2500;
// Minimum number of times the raw value needs to be contained in the
// confidence interval for a given alpha so that the statistical test accepts.
// The failure probability is less than 10^-6.
std::unordered_map<double, int> kAcceptableThresholds_ = {
{0.9999, 2494}, {0.999, 2486}, {0.99, 2447}, {0.95, 2319},
{0.9, 2175}, {0.75, 1769}, {0.5, 1130}, {0.25, 523},
{0.1, 181}, {0.05, 76}, {0.01, 4}};
};
typedef ::testing::Types<int64_t, double> NumericTypes;
TYPED_TEST_SUITE(QuantileTreeTest, NumericTypes);
TEST(QuantileTreeTest, InvalidParametersTest) {
EXPECT_THAT(QuantileTree<double>::Builder()
.SetTreeHeight(0)
.SetLower(0)
.SetUpper(1)
.Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Tree height must be at least 1")));
EXPECT_THAT(QuantileTree<double>::Builder()
.SetBranchingFactor(1)
.SetLower(0)
.SetUpper(1)
.Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Branching factor must be at least 2")));
EXPECT_THAT(QuantileTree<double>::Builder().SetLower(2).SetUpper(1).Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Lower bound must be less than upper")));
EXPECT_THAT(QuantileTree<double>::Builder().Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Lower and upper bounds")));
}
// Input must be sorted.
template <typename T>
double TrueQuantileFromSorted(std::vector<T> inputs, double quantile) {
int rank = std::round(quantile * (inputs.size() - 1));
return inputs[rank];
}
TYPED_TEST(QuantileTreeTest, ApproximatesTrueQuantile) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (TypeParam input : inputs) {
test_quantiles->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
double tolerance = 0.01; // > upper - lower / branchingFactor ^ treeHeight
std::sort(inputs.begin(), inputs.end());
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_NEAR(results.GetQuantile(quantile).value(),
TrueQuantileFromSorted(inputs, quantile), tolerance);
}
}
TYPED_TEST(QuantileTreeTest, EmptyLinearlyDistributed) {
double lower = -50;
double upper = 50;
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(upper)
.SetLower(lower)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
double tolerance = 0.01; // < (upper - lower) / branchingFactor ^ treeHeight
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
// Avoid extreme quantiles, as they're special-cased.
if (quantile < 0.1 || quantile > 0.9) continue;
double expected = quantile * (upper - lower) + lower;
EXPECT_NEAR(results.GetQuantile(quantile).value(), expected, tolerance);
}
}
TYPED_TEST(QuantileTreeTest, LowerBoundClamps) {
double lower = -50;
double upper = 50;
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(upper)
.SetLower(lower)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
for (int i = 0; i < kDefaultDatasetSize; ++i) {
test_quantiles->AddEntry(-100);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_GE(results.GetQuantile(quantile).value(), lower);
}
}
TYPED_TEST(QuantileTreeTest, UpperBoundClamps) {
double lower = -50;
double upper = 50;
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(upper)
.SetLower(lower)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
for (int i = 0; i < kDefaultDatasetSize; ++i) {
test_quantiles->AddEntry(100);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_LE(results.GetQuantile(quantile).value(), upper);
}
}
TYPED_TEST(QuantileTreeTest, ApproximatesTrueQuantileNearUpperBound) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
for (int i = 0; i < kDefaultDatasetSize; ++i) {
test_quantiles->AddEntry(50);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
double tolerance = 0.01; // < (upper - lower) / branchingFactor ^ treeHeight
EXPECT_NEAR(results.GetQuantile(0.5).value(), 50, tolerance);
}
TYPED_TEST(QuantileTreeTest, ApproximatesTrueQuantileNearLowerBound) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
for (int i = 0; i < kDefaultDatasetSize; ++i) {
test_quantiles->AddEntry(-50);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
double tolerance = 0.01; // < (upper - lower) / branchingFactor ^ treeHeight
EXPECT_NEAR(results.GetQuantile(0.5).value(), -50, tolerance);
}
TYPED_TEST(QuantileTreeTest, InputOrderInvariant) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles1 =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles2 =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
absl::BitGen gen;
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(gen, -25, 25));
}
for (TypeParam input : inputs) {
test_quantiles1->AddEntry(input);
}
std::shuffle(inputs.begin(), inputs.end(), gen);
for (TypeParam input : inputs) {
test_quantiles2->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results1 =
test_quantiles1->MakePrivate(dp_params).value();
typename QuantileTree<TypeParam>::Privatized results2 =
test_quantiles2->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_EQ(results1.GetQuantile(quantile).value(),
results2.GetQuantile(quantile).value());
}
}
// This should hold even with noise enabled.
TYPED_TEST(QuantileTreeTest, RepeatedResultsIdentical) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
absl::BitGen gen;
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(gen, -25, 25));
}
for (TypeParam input : inputs) {
test_quantiles->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_EQ(results.GetQuantile(quantile).value(),
results.GetQuantile(quantile).value());
}
}
// This should hold even with noise enabled.
TYPED_TEST(QuantileTreeTest, ResultsIncreaseMonotonically) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
absl::BitGen gen;
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(gen, -25, 25));
}
for (TypeParam input : inputs) {
test_quantiles->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
double last_result = std::numeric_limits<double>::lowest();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_GE(results.GetQuantile(quantile).value(), last_result);
last_result = results.GetQuantile(quantile).value();
}
}
TYPED_TEST(QuantileTreeTest, InvalidRanksReturnErrors) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
EXPECT_THAT(results.GetQuantile(-0.5),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("quantile must be in [0, 1]")));
EXPECT_THAT(results.GetQuantile(1.5),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("quantile must be in [0, 1]")));
}
TYPED_TEST(QuantileTreeTest, SerializeMergeTest) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles1 =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles2 =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
absl::BitGen gen;
std::vector<TypeParam> first_inputs;
std::vector<TypeParam> second_inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
TypeParam input = absl::Uniform(gen, -25, 25);
if (i < kDefaultDatasetSize / 2) {
first_inputs.push_back(input);
} else {
second_inputs.push_back(input);
}
}
for (TypeParam input : first_inputs) {
test_quantiles1->AddEntry(input);
}
EXPECT_OK(test_quantiles2->Merge(test_quantiles1->Serialize()));
for (TypeParam input : second_inputs) {
test_quantiles1->AddEntry(input);
test_quantiles2->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results1 =
test_quantiles1->MakePrivate(dp_params).value();
typename QuantileTree<TypeParam>::Privatized results2 =
test_quantiles2->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_EQ(results1.GetQuantile(quantile).value(),
results2.GetQuantile(quantile).value());
}
}
TEST(QuantileTreeTest, MergeFailsWithBadBounds) {
std::unique_ptr<QuantileTree<double>> test_quantiles =
typename QuantileTree<double>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<double>> wrong_lower =
typename QuantileTree<double>::Builder()
.SetUpper(50)
.SetLower(-49)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<double>> wrong_upper =
typename QuantileTree<double>::Builder()
.SetUpper(49)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
EXPECT_THAT(wrong_lower->Merge(test_quantiles->Serialize()),
StatusIs(absl::StatusCode::kInternal, HasSubstr("Bounds")));
EXPECT_THAT(wrong_upper->Merge(test_quantiles->Serialize()),
StatusIs(absl::StatusCode::kInternal, HasSubstr("Bounds")));
}
TYPED_TEST(QuantileTreeTest, Reset) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles1 =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles2 =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
absl::BitGen gen;
std::vector<TypeParam> first_inputs;
std::vector<TypeParam> second_inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
TypeParam input = absl::Uniform(gen, -25, 25);
if (i < kDefaultDatasetSize / 2) {
first_inputs.push_back(input);
} else {
second_inputs.push_back(input);
}
}
for (TypeParam input : first_inputs) {
test_quantiles1->AddEntry(input);
}
test_quantiles1->Reset();
for (TypeParam input : second_inputs) {
test_quantiles1->AddEntry(input);
test_quantiles2->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results1 =
test_quantiles1->MakePrivate(dp_params).value();
typename QuantileTree<TypeParam>::Privatized results2 =
test_quantiles2->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_EQ(results1.GetQuantile(quantile).value(),
results2.GetQuantile(quantile).value());
}
}
TEST(QuantileTreeTest, IgnoresNaN) {
std::unique_ptr<QuantileTree<double>> test_quantiles =
typename QuantileTree<double>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
typename QuantileTree<double>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
test_quantiles->AddEntry(5.0);
typename QuantileTree<double>::Privatized results1 =
test_quantiles->MakePrivate(dp_params).value();
for (int i = 0; i < 100; ++i) {
test_quantiles->AddEntry(std::nan(""));
}
typename QuantileTree<double>::Privatized results2 =
test_quantiles->MakePrivate(dp_params).value();
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
EXPECT_EQ(results1.GetQuantile(quantile).value(),
results2.GetQuantile(quantile).value());
}
}
TEST(QuantileTreeTest, TreeOverflowsWithInputs) {
std::unique_ptr<QuantileTree<int64_t>> test_quantiles =
typename QuantileTree<int64_t>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
QuantileTreeTestPeer::AddMultipleEntries<int64_t>(
25, std::numeric_limits<int64_t>::max(), test_quantiles.get());
test_quantiles->AddEntry(25);
typename QuantileTree<int64_t>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<int64_t>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
// With no noise and no overflow, we should always get 25. With overflow,
// those nodes should register as empty, meaning the whole tree will be empty,
// meaning the median should be 0 (middle of the range).
EXPECT_EQ(results.GetQuantile(0.5).value(), 0);
}
TEST(QuantileTreeTest, TreeOverflowsWithNoise) {
std::unique_ptr<QuantileTree<int64_t>> test_quantiles =
typename QuantileTree<int64_t>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
QuantileTreeTestPeer::AddMultipleEntries<int64_t>(
25, std::numeric_limits<int64_t>::max(), test_quantiles.get());
typename QuantileTree<int64_t>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
// All the entries are in one leaf node. If the counts don't overflow, they
// should always be much larger than all the noisy zeroes, and so we should
// ~always get a median in that bucket. If the count can overflow, it should
// do so ~50% of the time. If the count overflows, the total count should
// be negative, and we should conclude the tree is empty. Therefore, we will
// pick the middle of the range (0).
//
// That means that if overflows can happen, we should get a 0 ~50% of the
// time. Given 10^3 tries, an event with p=.5 should ~always occur.
for (int i = 0; i < 1e3; ++i) {
typename QuantileTree<int64_t>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
if (results.GetQuantile(0.5).value() == 0) {
// An overflow occurred, so we can return from the test with a success.
return;
}
}
FAIL() << "No overflow occurred after 1e3 iterations.";
}
TYPED_TEST(QuantileTreeTest, PrivatizedConstantWithExtraInput) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
for (int i = 0; i < 100; ++i) {
test_quantiles->AddEntry(-25);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
double tolerance = 0.01; // > upper - lower / branchingFactor ^ treeHeight
EXPECT_NEAR(results.GetQuantile(0.5).value(), -25, tolerance);
for (int i = 0; i < 1000; ++i) {
test_quantiles->AddEntry(25);
}
EXPECT_NEAR(results.GetQuantile(0.5).value(), -25, tolerance);
}
TYPED_TEST(QuantileTreeTest, ZeroNoiseConfidenceIntervalsMatchQuantile) {
std::unique_ptr<QuantileTree<TypeParam>> test_quantiles =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (const TypeParam& input : inputs) {
test_quantiles->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<ZeroNoiseMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
test_quantiles->MakePrivate(dp_params).value();
double tolerance = 0.01; // > upper - lower / branchingFactor ^ treeHeight
std::sort(inputs.begin(), inputs.end());
double confidence_level = 0.95;
for (int i = 0; i < kNumRanksToTest; ++i) {
double quantile = static_cast<double>(i) / kNumRanksToTest;
double quantile_result = results.GetQuantile(quantile).value();
absl::StatusOr<ConfidenceInterval> ci_or_status =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
EXPECT_OK(ci_or_status);
EXPECT_NEAR(quantile_result, ci_or_status.value().lower_bound(), tolerance);
EXPECT_NEAR(quantile_result, ci_or_status.value().upper_bound(), tolerance);
EXPECT_NEAR(confidence_level, ci_or_status.value().confidence_level(),
tolerance);
}
}
TYPED_TEST(QuantileTreeTest,
ConfidenceIntervalsLowerBoundLessThanOrEqualsUpperBoundWithNoInput) {
for (int i = 0; i < 1000; i++) {
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
// Use a small confidence level to increase the chance of a violation.
double confidence_level = 0.01;
for (double quantile : kQuantilesToTest) {
absl::StatusOr<ConfidenceInterval> ci_or_status =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
EXPECT_OK(ci_or_status);
EXPECT_LE(ci_or_status.value().lower_bound(),
ci_or_status.value().upper_bound());
}
}
}
TYPED_TEST(QuantileTreeTest,
ConfidenceIntervalsLowerBoundLessThanOrEqualsUpperBoundWithInput) {
for (int i = 0; i < 1000; i++) {
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (TypeParam input : inputs) {
quantile_tree->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
// Use a small confidence level to increase the chance of a violation.
double confidence_level = 0.01;
for (double quantile : kQuantilesToTest) {
absl::StatusOr<ConfidenceInterval> ci_or_status =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
EXPECT_OK(ci_or_status);
EXPECT_LE(ci_or_status.value().lower_bound(),
ci_or_status.value().upper_bound());
}
}
}
TYPED_TEST(QuantileTreeTest, ConfidenceIntervalWithinBounds) {
const TypeParam lower_bound = 1;
const TypeParam upper_bound = 2;
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(upper_bound)
.SetLower(lower_bound)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
for (int i = 0; i < 10; ++i) {
quantile_tree->AddEntry(lower_bound);
quantile_tree->AddEntry(upper_bound);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
// To increase the chance of a violation, we use a high confidence level and
// test the confidence intervals of the min and max quantiles, which match the
// bounds of the input range.
absl::StatusOr<ConfidenceInterval> ci_or_status =
results.ComputeNoiseConfidenceInterval(0.0, 0.99);
EXPECT_OK(ci_or_status);
EXPECT_GE(ci_or_status.value().lower_bound(), lower_bound);
ci_or_status = results.ComputeNoiseConfidenceInterval(1.0, 0.99);
EXPECT_OK(ci_or_status);
EXPECT_LE(ci_or_status.value().upper_bound(), upper_bound);
}
TYPED_TEST(QuantileTreeTest,
ConfidenceIntervalWithGaussianNoiseReturnsSameResults) {
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (TypeParam input : inputs) {
quantile_tree->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<GaussianMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
double confidence_level = 0.95;
for (double quantile : kQuantilesToTest) {
absl::StatusOr<ConfidenceInterval> ci_or_status_1 =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
absl::StatusOr<ConfidenceInterval> ci_or_status_2 =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
EXPECT_OK(ci_or_status_1);
EXPECT_OK(ci_or_status_2);
EXPECT_EQ(ci_or_status_1.value().lower_bound(),
ci_or_status_2.value().lower_bound());
EXPECT_EQ(ci_or_status_1.value().upper_bound(),
ci_or_status_2.value().upper_bound());
}
}
TYPED_TEST(QuantileTreeTest,
ConfidenceIntervalWithLaplaceNoiseReturnsSameResults) {
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (TypeParam input : inputs) {
quantile_tree->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
double confidence_level = 0.95;
for (double quantile : kQuantilesToTest) {
absl::StatusOr<ConfidenceInterval> ci_or_status_1 =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
absl::StatusOr<ConfidenceInterval> ci_or_status_2 =
results.ComputeNoiseConfidenceInterval(quantile, confidence_level);
EXPECT_OK(ci_or_status_1);
EXPECT_OK(ci_or_status_2);
EXPECT_EQ(ci_or_status_1.value().lower_bound(),
ci_or_status_2.value().lower_bound());
EXPECT_EQ(ci_or_status_1.value().upper_bound(),
ci_or_status_2.value().upper_bound());
}
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithGaussianNoiseReturnsLowerLevelIntervalsWithinHigherLevelIntervals) {
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (TypeParam input : inputs) {
quantile_tree->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<GaussianMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
for (double quantile : kQuantilesToTest) {
absl::StatusOr<ConfidenceInterval> ci_or_status_1 =
results.ComputeNoiseConfidenceInterval(quantile, 0.9);
absl::StatusOr<ConfidenceInterval> ci_or_status_2 =
results.ComputeNoiseConfidenceInterval(quantile, 0.99);
EXPECT_OK(ci_or_status_1);
EXPECT_OK(ci_or_status_2);
EXPECT_LE(ci_or_status_1.value().lower_bound(),
ci_or_status_2.value().lower_bound());
EXPECT_GE(ci_or_status_1.value().upper_bound(),
ci_or_status_2.value().upper_bound());
}
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithLaplaceNoiseReturnsLowerLevelIntervalsWithinHigherLevelIntervals) {
std::unique_ptr<QuantileTree<TypeParam>> quantile_tree =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::vector<TypeParam> inputs;
for (int i = 0; i < kDefaultDatasetSize; ++i) {
inputs.push_back(absl::Uniform(absl::BitGen(), -25, 25));
}
for (TypeParam input : inputs) {
quantile_tree->AddEntry(input);
}
typename QuantileTree<TypeParam>::DPParams dp_params;
dp_params.epsilon = kTestDefaultEpsilon;
dp_params.delta = kDefaultDelta;
dp_params.max_contributions_per_partition =
kDefaultMaxContributionsPerPartition;
dp_params.max_partitions_contributed_to = kDefaultMaxPartitionsContributed;
dp_params.mechanism_builder = std::make_unique<LaplaceMechanism::Builder>();
typename QuantileTree<TypeParam>::Privatized results =
quantile_tree->MakePrivate(dp_params).value();
// Test that all higher levels' confidence intervals are within lower levels'
// confidence intervals.
for (double quantile : kQuantilesToTest) {
for (int i = 0; i < kConfidenceLevelsToTest.size(); ++i) {
for (int j = i + 1; j < kConfidenceLevelsToTest.size(); ++j) {
absl::StatusOr<ConfidenceInterval> ci_or_status_1 =
results.ComputeNoiseConfidenceInterval(quantile,
kConfidenceLevelsToTest[i]);
absl::StatusOr<ConfidenceInterval> ci_or_status_2 =
results.ComputeNoiseConfidenceInterval(quantile,
kConfidenceLevelsToTest[j]);
EXPECT_OK(ci_or_status_1);
EXPECT_OK(ci_or_status_2);
EXPECT_LE(ci_or_status_1.value().lower_bound(),
ci_or_status_2.value().lower_bound());
EXPECT_GE(ci_or_status_1.value().upper_bound(),
ci_or_status_2.value().upper_bound());
}
}
}
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithGaussianNoiseSatisfiesConfidenceLevelWithOneEntry) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries = {0};
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<GaussianMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithLaplaceNoiseSatisfiesConfidenceLevelWithOneEntry) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries = {0};
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<LaplaceMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithGaussianNoiseSatisfiesConfidenceLevelWithUniformEntries) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(250)
.SetLower(-250)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries;
for (int i = -250; i <= 250; ++i) {
entries.push_back(i);
}
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<GaussianMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithLaplaceNoiseSatisfiesConfidenceLevelWithUniformEntries) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(250)
.SetLower(-250)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries;
for (int i = -250; i <= 250; ++i) {
entries.push_back(i);
}
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<LaplaceMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithGaussianNoiseSatisfiesConfidenceLevelWithConstantEntries) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries;
for (int i = 0; i <= 20; ++i) {
entries.push_back(3);
}
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<GaussianMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithLaplaceNoiseSatisfiesConfidenceLevelWithConstantEntries) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries;
for (int i = 0; i <= 20; ++i) {
entries.push_back(3);
}
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<LaplaceMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithGaussianNoiseSatisfiesConfidenceLevelWithBernoulliEntries) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries;
for (int i = 0; i <= 100; ++i) {
entries.push_back(1);
entries.push_back(-1);
}
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<GaussianMechanism::Builder>());
}
TYPED_TEST(
QuantileTreeTest,
ConfidenceIntervalWithLaplaceNoiseSatisfiesConfidenceLevelWithBernoulliEntries) {
typename QuantileTree<TypeParam>::Builder builder =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10);
std::vector<TypeParam> entries;
for (int i = 0; i <= 100; ++i) {
entries.push_back(1);
entries.push_back(-1);
}
this->StatisticallyAssertConfidenceLevel(
entries, builder, std::make_unique<LaplaceMechanism::Builder>());
}
TYPED_TEST(QuantileTreeTest, MemoryUsed) {
std::unique_ptr<QuantileTree<TypeParam>> empty =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<TypeParam>> once =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
std::unique_ptr<QuantileTree<TypeParam>> twice =
typename QuantileTree<TypeParam>::Builder()
.SetUpper(50)
.SetLower(-50)
.SetTreeHeight(4)
.SetBranchingFactor(10)
.Build()
.value();
once->AddEntry(-49);
twice->AddEntry(49);
twice->AddEntry(49);
EXPECT_GT(once->MemoryUsed(), empty->MemoryUsed());
EXPECT_EQ(once->MemoryUsed(), twice->MemoryUsed());
}
} // namespace
} // namespace differential_privacy