blob: 4d74f6b0ed146e12b900c0878ca2cf45d84b8444 [file] [log] [blame]
//
// Copyright 2022 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 <cmath>
#include <memory>
#include <utility>
#include <vector>
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/statusor.h"
#include "algorithms/approx-bounds.h"
#include "algorithms/bounded-mean.h"
#include "algorithms/numerical-mechanisms.h"
#include "proto/confidence-interval.pb.h"
#include "proto/data.pb.h"
namespace differential_privacy {
namespace {
using ::testing::_;
using ::testing::Gt;
using ::testing::NiceMock;
using ::testing::Return;
TEST(BoundedMeanTest, ConfidenceIntervalBasicPropertyTestForFixedBounds) {
// Runs the algorithm multiple times and checks that the true mean value is in
// the confidence interval according to the confidence level.
const double epsilon = std::log(3);
const double lower = -1.0;
const double upper = 1.0;
const double confidence_level = 0.95;
const int num_trials = 100000;
const std::vector<double> inputs = {-1.0, 0.0, 1.0, -0.5, 0.5,
0.0, 0.0, 0.8, 0.7, -0.2};
double inputs_sum = 0;
for (double d : inputs) {
inputs_sum += d;
}
const double raw_mean = inputs_sum / inputs.size();
int hits = 0;
for (int i = 0; i < num_trials; ++i) {
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> mean =
BoundedMean<double>::Builder()
.SetEpsilon(epsilon)
.SetLower(lower)
.SetUpper(upper)
.Build();
ASSERT_OK(mean);
for (double d : inputs) {
mean->get()->AddEntry(d);
}
absl::StatusOr<Output> output =
mean->get()->PartialResult(confidence_level);
ASSERT_OK(output);
const ConfidenceInterval ci =
output->elements(0).noise_confidence_interval();
if (ci.lower_bound() <= raw_mean && raw_mean <= ci.upper_bound()) {
++hits;
}
}
// This test fails with a probability of 1e-8. If it fails more frequently it
// should be considered as constantly failing. To get to 94609, we calculate
// the CDF of the binomial distribution for the 100,000 trials and the success
// probability of 0.95 and find the greatest number for which the CDF is less
// than 1e-8.
ASSERT_THAT(hits, Gt(94609));
}
class MockApproxBounds : public ApproxBounds<double> {
public:
MockApproxBounds()
: ApproxBounds(
/*epsilon=*/0.1,
/*num_bins=*/1024,
/*scale=*/1.0,
/*base=*/2.0,
/*success_probability=*/1.0 - 1e-9,
/*has_user_set_threshold=*/false,
/*mechanism=*/
LaplaceMechanism::Builder().SetEpsilon(0.1).Build().value()) {}
MOCK_METHOD(absl::StatusOr<Output>, GenerateResult,
(double noise_interval_level), (override));
};
TEST(BoundedMeanTest, ConfidenceIntervalBasicPropertyTestForApproxBounds) {
const double confidence_level = 0.95;
const int num_trials = 100000;
const std::vector<double> inputs = {-1.0, 0.0, 1.0, -0.5, 0.5,
0.0, 0.0, 0.8, 0.7, -0.2};
double inputs_sum = 0;
for (double d : inputs) {
inputs_sum += d;
}
const double raw_mean = inputs_sum / inputs.size();
int hits = 0;
for (int i = 0; i < num_trials; ++i) {
auto mock_approx_bounds = std::make_unique<NiceMock<MockApproxBounds>>();
Output approx_bounds_result;
approx_bounds_result.add_elements()->mutable_value()->set_float_value(-1.0);
approx_bounds_result.add_elements()->mutable_value()->set_float_value(1.0);
EXPECT_CALL(*mock_approx_bounds, GenerateResult(_))
.WillRepeatedly(Return(approx_bounds_result));
absl::StatusOr<std::unique_ptr<BoundedMean<double>>> mean =
BoundedMean<double>::Builder()
.SetEpsilon(1.1)
.SetApproxBounds(std::move(mock_approx_bounds))
.Build();
ASSERT_OK(mean);
for (double d : inputs) {
mean->get()->AddEntry(d);
}
absl::StatusOr<Output> output =
mean->get()->PartialResult(confidence_level);
ASSERT_OK(output);
const ConfidenceInterval ci =
output->elements(0).noise_confidence_interval();
if (ci.lower_bound() <= raw_mean && raw_mean <= ci.upper_bound()) {
++hits;
}
}
// This test fails with a probability of 1e-8. If it fails more frequently it
// should be considered as constantly failing. To get to 94609, we calculate
// the CDF of the binomial distribution for the 100,000 trials and the success
// probability of 0.95 and find the greatest number for which the CDF is less
// than 1e-8.
ASSERT_THAT(hits, Gt(94609));
}
} // namespace
} // namespace differential_privacy