blob: 7f548a99dca392292323ef940d302b08913c76b5 [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/order-statistics.h"
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/distributions.h"
#include "algorithms/numerical-mechanisms-testing.h"
#include "algorithms/util.h"
namespace differential_privacy {
namespace continuous {
namespace {
using ::differential_privacy::test_utils::ZeroNoiseMechanism;
using ::testing::HasSubstr;
using ::differential_privacy::base::testing::StatusIs;
static constexpr size_t kDataSize = 10000;
static constexpr double kNumSamples = 10000;
TEST(OrderStatisticsTest, Max) {
double epsilon = std::log(3);
int64_t lower = 0, upper = 2048;
base::StatusOr<std::unique_ptr<Max<int64_t>>> search =
Max<int64_t>::Builder()
.SetEpsilon(epsilon)
.SetLower(lower)
.SetUpper(upper)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(search);
for (int64_t i = 0; i < kDataSize; ++i) {
(*search)->AddEntry(std::round(static_cast<double>(200) * i / kDataSize));
}
EXPECT_NEAR(GetValue<int64_t>((*search)->PartialResult(1.0).ValueOrDie()), 200,
10);
}
TEST(OrderStatisticsTest, Min) {
double epsilon = std::log(3);
int64_t lower = 0, upper = 2048;
base::StatusOr<std::unique_ptr<Min<int64_t>>> search =
Min<int64_t>::Builder()
.SetEpsilon(epsilon)
.SetLower(lower)
.SetUpper(upper)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
for (int64_t i = 0; i < kDataSize; ++i) {
(*search)->AddEntry(std::round(static_cast<double>(200) * i / kDataSize));
}
base::StatusOr<Output> result = (*search)->PartialResult(1.0);
ASSERT_OK(result);
EXPECT_NEAR(GetValue<int64_t>(*result), 0, 10);
}
TEST(OrderStatisticsTest, Median) {
double epsilon = std::log(3);
int64_t lower = 0, upper = 2048;
base::StatusOr<std::unique_ptr<Median<int64_t>>> search =
Median<int64_t>::Builder()
.SetEpsilon(epsilon)
.SetLower(lower)
.SetUpper(upper)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(search);
for (int64_t i = 0; i < kDataSize; ++i) {
(*search)->AddEntry(std::round(static_cast<double>(200) * i / kDataSize));
}
base::StatusOr<Output> result = (*search)->PartialResult(1.0);
ASSERT_OK(result);
EXPECT_EQ(GetValue<int64_t>(*result), 100);
}
TEST(OrderStatisticsTest, MedianLinfIncreasesVariance) {
// Median is 0
const std::vector<double> input = {1, 0, 0, -1};
std::function<double(int)> sample_variance_for_max_contributions =
[&input](int max_contributions) {
double sum = 0;
for (int i = 0; i < kNumSamples; ++i) {
base::StatusOr<std::unique_ptr<Median<double>>> bounded_sum =
Median<double>::Builder()
.SetMaxContributionsPerPartition(max_contributions)
.SetEpsilon(1)
.SetLower(-1)
.SetUpper(1)
.Build();
CHECK_EQ(bounded_sum.status(), absl::OkStatus());
base::StatusOr<Output> out =
(*bounded_sum)->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 3 to be
// bigger than with max contribution 1.
EXPECT_GT(sample_variance_for_max_contributions(3),
1.1 * sample_variance_for_max_contributions(1));
}
TEST(OrderStatisticsTest, Percentile) {
double epsilon = std::log(3);
int64_t lower = 0, upper = 2048;
base::StatusOr<std::unique_ptr<Percentile<int64_t>>> search =
Percentile<int64_t>::Builder()
.SetPercentile(.45)
.SetEpsilon(epsilon)
.SetLower(lower)
.SetUpper(upper)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(search);
for (int64_t i = 0; i < kDataSize; ++i) {
(*search)->AddEntry(std::round(static_cast<double>(200) * i / kDataSize));
}
base::StatusOr<Output> result = (*search)->PartialResult(1.0);
ASSERT_OK(result);
EXPECT_EQ(GetValue<int64_t>(*result), 90);
}
TEST(OrderStatisticsTest, PercentileGetter) {
double epsilon = std::log(3), expectedPercentile = 0.9;
int64_t lower = 0, upper = 2048;
base::StatusOr<std::unique_ptr<Percentile<int64_t>>> percentile =
Percentile<int64_t>::Builder()
.SetPercentile(expectedPercentile)
.SetEpsilon(epsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(percentile);
EXPECT_EQ((*percentile)->GetPercentile(), expectedPercentile);
}
TEST(OrderStatisticsTest, InvalidParameters) {
Percentile<int64_t>::Builder builder;
EXPECT_OK(builder.SetPercentile(.9).SetLower(1).SetUpper(2).Build());
EXPECT_THAT(
builder.SetLower(3).Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Lower bound cannot be greater than upper bound")));
EXPECT_THAT(builder.SetLower(1).SetPercentile(-1).Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Percentile must be between 0 and 1")));
EXPECT_THAT(builder.SetPercentile(2).Build(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Percentile must be between 0 and 1")));
}
TEST(OrderStatisticsTest, Median_DefaultBounds) {
double epsilon = std::log(3);
base::StatusOr<std::unique_ptr<Median<int64_t>>> search =
Median<int64_t>::Builder()
.SetEpsilon(epsilon)
.SetLaplaceMechanism(absl::make_unique<ZeroNoiseMechanism::Builder>())
.Build();
ASSERT_OK(search);
for (int64_t i = 0; i < kDataSize; ++i) {
(*search)->AddEntry(std::round(static_cast<double>(200) * i / kDataSize));
}
base::StatusOr<Output> result = (*search)->PartialResult(1.0);
ASSERT_OK(result);
EXPECT_EQ(GetValue<int64_t>(*result), 100);
}
} // namespace
} // namespace continuous
} // namespace differential_privacy