| // |
| // 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 |