blob: 215b1a763a52b6b4b3e669ded0c8217074b74b36 [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 "testing/stochastic_tester.h"
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/distributions.h"
#include "base/statusor.h"
#include "algorithms/algorithm.h"
#include "algorithms/bounded-sum.h"
#include "algorithms/count.h"
#include "algorithms/numerical-mechanisms-testing.h"
#include "algorithms/numerical-mechanisms.h"
#include "algorithms/util.h"
#include "testing/sequence.h"
namespace differential_privacy {
namespace testing {
namespace {
// Trivial non-DP sum that returns the exact value determinisitically.
template <typename T,
typename std::enable_if<std::is_integral<T>::value ||
std::is_floating_point<T>::value>::type* =
nullptr>
class NonDpSum : public Algorithm<T> {
public:
NonDpSum() : Algorithm<T>(0), result_(0) {}
void AddEntry(const T& t) override { result_ += t; }
base::StatusOr<Output> GenerateResult(
double /*privacy_budget*/, double /*noise_interval_level*/) override {
return MakeOutput<T>(result_);
}
void ResetState() override { result_ = 0; }
Summary Serialize() override { return Summary(); }
base::Status Merge(const Summary& summary) override {
return base::OkStatus();
}
int64_t MemoryUsed() override { return sizeof(NonDpSum<T>); };
private:
T result_;
};
// Trivial non-DP count that returns the exact value determinisitically.
template <typename T>
class NonDpCount : public Algorithm<T> {
public:
NonDpCount() : Algorithm<T>(0), result_(0) {}
void AddEntry(const T& t) override { ++result_; }
base::StatusOr<Output> GenerateResult(
double /*privacy_budget*/, double /*noise_interval_level*/) override {
return MakeOutput<int64_t>(result_);
}
void ResetState() override { result_ = 0; }
Summary Serialize() override { return Summary(); }
base::Status Merge(const Summary& summary) override {
return base::OkStatus();
}
int64_t MemoryUsed() override { return sizeof(NonDpCount<T>); };
private:
int64_t result_;
};
// A version of BoundedSum where Epsilon() is overridden to report half of the
// actual epsilon value, so we have an algorithm that claims stronger privacy
// guarantees than it actually provides.
template <typename T,
typename std::enable_if<std::is_integral<T>::value ||
std::is_floating_point<T>::value>::type* =
nullptr>
class BoundedSumWithInsufficientNoise : public BoundedSum<T> {
public:
BoundedSumWithInsufficientNoise(
double epsilon, T lower, T upper,
std::unique_ptr<LaplaceMechanism::Builder> builder)
: BoundedSum<T>(epsilon, lower, upper, 1, 1, std::move(builder), nullptr,
nullptr) {}
double GetEpsilon() const override { return Algorithm<T>::GetEpsilon() / 2; }
};
// BoundedSum but it returns a error status with a fixed probability.
template <typename T,
typename std::enable_if<std::is_integral<T>::value ||
std::is_floating_point<T>::value>::type* =
nullptr>
class BoundedSumWithError : public BoundedSum<T> {
public:
BoundedSumWithError(double epsilon, T lower, T upper,
std::unique_ptr<LaplaceMechanism::Builder> builder)
: BoundedSum<T>(epsilon, lower, upper, 1, 1, builder->Clone(), nullptr,
nullptr),
mechanism_(absl::WrapUnique(dynamic_cast<LaplaceMechanism*>(
builder->Build().ValueOrDie().release()))) {}
base::StatusOr<Output> GenerateResult(double privacy_budget,
double noise_interval_level) override {
if (mechanism_->GetUniformDouble() < 0.25) {
return base::InvalidArgumentError("BoundedSumWithError returns error.");
}
return BoundedSum<T>::GenerateResult(privacy_budget, noise_interval_level);
}
private:
std::unique_ptr<LaplaceMechanism> mechanism_;
};
// Count but returns error without dp for some results.
template <typename T>
class CountNoDpError : public Count<T> {
public:
explicit CountNoDpError(double epsilon)
: Count<T>(epsilon, LaplaceMechanism::Builder()
.SetEpsilon(epsilon)
.Build()
.ValueOrDie()) {}
base::StatusOr<Output> GenerateResult(double privacy_budget,
double noise_interval_level) override {
if (Count<T>::GetCount() == 0) {
return base::InvalidArgumentError("CountNoDpError returns error.");
}
return Count<T>::GenerateResult(privacy_budget, noise_interval_level);
}
};
// Trivial class that only returns error.
template <typename T,
typename std::enable_if<std::is_integral<T>::value ||
std::is_floating_point<T>::value>::type* =
nullptr>
class AlwaysError : public Algorithm<T> {
public:
AlwaysError() : Algorithm<T>(0), result_(0) {}
void AddEntry(const T& t) override {}
base::StatusOr<Output> GenerateResult(
double /*privacy_budget*/, double /*noise_interval_level*/) override {
return base::InvalidArgumentError("AlwaysError returns error.");
}
void ResetState() override {}
Summary Serialize() override { return Summary(); }
base::Status Merge(const Summary& summary) override {
return base::OkStatus();
}
int64_t MemoryUsed() override { return sizeof(AlwaysError<T>); };
private:
T result_;
};
TEST(StochasticTesterTest, SingleDatasetBoundedSumTest) {
auto sequence = absl::make_unique<HaltonSequence<double>>(
DefaultDatasetSize(), true /* sorted_only */, DefaultDataScale(),
DefaultDataOffset());
auto algorithm =
BoundedSum<double>::Builder()
.SetLaplaceMechanism(
absl::make_unique<test_utils::SeededLaplaceMechanism::Builder>())
.SetEpsilon(std::log(3))
.SetLower(sequence->RangeMin())
.SetUpper(sequence->RangeMax())
.Build()
.ValueOrDie();
StochasticTester<double> tester(std::move(algorithm), std::move(sequence),
/*num_datasets=*/1,
DefaultNumSamplesPerHistogram());
EXPECT_TRUE(tester.Run());
}
TEST(StochasticTesterTest, SingleDatasetNonDpSumTest) {
auto sequence = absl::make_unique<HaltonSequence<double>>(
DefaultDatasetSize(), true /* sorted_only */, DefaultDataScale(),
DefaultDataOffset());
auto algorithm = absl::make_unique<NonDpSum<double>>();
StochasticTester<double> tester(std::move(algorithm), std::move(sequence),
/*num_datasets=*/1,
DefaultNumSamplesPerHistogram());
EXPECT_FALSE(tester.Run());
}
TEST(StochasticTesterTest, SingleDatasetCountTest) {
std::vector<std::vector<double>> datasets({{1.0, 2.0, 3.0}});
auto sequence = absl::make_unique<StoredSequence<double>>(datasets);
std::unique_ptr<Count<double>> algorithm =
Count<double>::Builder()
.SetLaplaceMechanism(
absl::make_unique<test_utils::SeededLaplaceMechanism::Builder>())
.SetEpsilon(std::log(3))
.Build()
.ValueOrDie();
StochasticTester<double, int64_t> tester(
std::move(algorithm), std::move(sequence),
/*num_datasets=*/1, DefaultNumSamplesPerHistogram());
EXPECT_TRUE(tester.Run());
}
TEST(StochasticTesterTest, SingleDatasetNonDpCountTest) {
std::vector<std::vector<double>> datasets({{1.0, 2.0, 3.0}});
auto sequence = absl::make_unique<StoredSequence<double>>(datasets);
auto algorithm = absl::make_unique<NonDpCount<double>>();
StochasticTester<double, int64_t> tester(
std::move(algorithm), std::move(sequence),
/*num_datasets=*/1, DefaultNumSamplesPerHistogram());
EXPECT_FALSE(tester.Run());
}
TEST(StochasticTesterTest, SingleDatasetCountNoBranchingTest) {
std::vector<std::vector<double>> datasets({{1.0, 2.0, 3.0}});
auto sequence = absl::make_unique<StoredSequence<double>>(datasets);
std::unique_ptr<Count<double>> algorithm =
Count<double>::Builder()
.SetLaplaceMechanism(
absl::make_unique<test_utils::SeededLaplaceMechanism::Builder>())
.SetEpsilon(std::log(3))
.Build()
.ValueOrDie();
StochasticTester<double, int64_t> tester(
std::move(algorithm), std::move(sequence),
/*num_datasets=*/1, DefaultNumSamplesPerHistogram(),
/*disable_search_branching=*/true);
EXPECT_TRUE(tester.Run());
}
TEST(StochasticTesterTest, SingleDatasetNonDpCountNoBranchingTest) {
std::vector<std::vector<double>> datasets({{1.0, 2.0, 3.0}});
auto sequence = absl::make_unique<StoredSequence<double>>(datasets);
auto algorithm = absl::make_unique<NonDpCount<double>>();
StochasticTester<double, int64_t> tester(
std::move(algorithm), std::move(sequence),
/*num_datasets=*/1, DefaultNumSamplesPerHistogram(),
/*disable_search_branching=*/true);
EXPECT_FALSE(tester.Run());
}
TEST(StochasticTesterTest, MultipleDatasetBoundedSumTest) {
auto sequence = absl::make_unique<HaltonSequence<double>>(
DefaultDatasetSize(), /*sorted_only=*/true, DefaultDataScale(),
DefaultDataOffset());
auto algorithm =
BoundedSum<double>::Builder()
.SetLaplaceMechanism(
absl::make_unique<test_utils::SeededLaplaceMechanism::Builder>())
.SetEpsilon(std::log(3))
.SetLower(sequence->RangeMin())
.SetUpper(sequence->RangeMax())
.Build()
.ValueOrDie();
StochasticTester<double, int64_t> tester(std::move(algorithm),
std::move(sequence));
EXPECT_TRUE(tester.Run());
}
TEST(StochasticTesterTest, MultipleDatasetBoundedSumWithInsufficientNoiseTest) {
auto sequence = absl::make_unique<HaltonSequence<double>>(
DefaultDatasetSize(), /*sorted_only=*/true, DefaultDataScale(),
DefaultDataOffset());
auto algorithm = absl::make_unique<BoundedSumWithInsufficientNoise<double>>(
std::log(3), sequence->RangeMin(), sequence->RangeMax(),
absl::make_unique<test_utils::SeededLaplaceMechanism::Builder>());
StochasticTester<double> tester(std::move(algorithm), std::move(sequence));
EXPECT_FALSE(tester.Run());
}
TEST(StochasticTesterTest, ReplaceErrorWithValue) {
auto sequence = absl::make_unique<HaltonSequence<double>>(
DefaultDatasetSize(), /*sorted_only=*/true, DefaultDataScale(),
DefaultDataOffset());
auto algorithm = absl::make_unique<BoundedSumWithError<double>>(
std::log(3), sequence->RangeMin(), sequence->RangeMax(),
absl::make_unique<test_utils::SeededLaplaceMechanism::Builder>());
StochasticTester<double> tester(std::move(algorithm), std::move(sequence));
EXPECT_TRUE(tester.Run());
}
// Test an algorithm that throws error deterministically.
TEST(StochasticTesterTest, ErrorStatusWithoutDP) {
auto sequence = absl::make_unique<HaltonSequence<int64_t>>(
DefaultDatasetSize(), /*sorted_only=*/true, DefaultDataScale(),
DefaultDataOffset());
auto algorithm = absl::make_unique<CountNoDpError<int64_t>>(std::log(3));
StochasticTester<int64_t> tester(std::move(algorithm), std::move(sequence));
EXPECT_FALSE(tester.Run());
}
// Test a class that always determines error.
TEST(StochasticTesterTest, AllError) {
auto sequence = absl::make_unique<HaltonSequence<double>>(
DefaultDatasetSize(), /*sorted_only=*/true, DefaultDataScale(),
DefaultDataOffset());
auto algorithm = absl::make_unique<AlwaysError<double>>();
StochasticTester<double> tester(std::move(algorithm), std::move(sequence));
EXPECT_TRUE(tester.Run());
}
} // namespace
} // namespace testing
} // namespace differential_privacy