blob: 4f02f65a6ab9f38063df14447173429d7f06761b [file] [log] [blame] [edit]
//
// Copyright 2025 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/approx-bounds-provider.h"
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <vector>
#include "base/testing/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "algorithms/bounds-provider.h"
#include "algorithms/internal/clamped-calculation-without-bounds.h"
#include "algorithms/numerical-mechanisms-testing.h"
namespace differential_privacy {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::differential_privacy::test_utils::ZeroNoiseMechanism;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::Lt;
template <typename T>
class ApproxBoundsProviderTypedTest : public ::testing::Test {};
using TestingTypes = ::testing::Types<int64_t, double>;
TYPED_TEST_SUITE(ApproxBoundsProviderTypedTest, TestingTypes);
TYPED_TEST(ApproxBoundsProviderTypedTest, ReturnsErrorOnEmptyEpsilon) {
typename ApproxBoundsProvider<TypeParam>::Options options{};
EXPECT_THAT(
ApproxBoundsProvider<TypeParam>::Create(options),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Epsilon must be finite and positive, but is 0")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
ReturnsErrorOnEmptyMaxPartitionsContributed) {
typename ApproxBoundsProvider<TypeParam>::Options options{1};
EXPECT_THAT(
ApproxBoundsProvider<TypeParam>::Create(options),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Maximum number of partitions that can be contributed to "
"(i.e., L0 sensitivity) must be positive, but is 0")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
ReturnsErrorOnEmptyMaxContributionsPerPartition) {
typename ApproxBoundsProvider<TypeParam>::Options options{1, 1};
EXPECT_THAT(ApproxBoundsProvider<TypeParam>::Create(options),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Maximum number of contributions per "
"partition must be positive, but is 0")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsReturnsErrorOnEmptyInput) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1.0, // epsilon
1, // max_partitions_contributed
1 // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
EXPECT_THAT((*approx_bound_provider)->FinalizeAndCalculateBounds(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("run over a larger dataset")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest, BasicTestIntegerDivByZero) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1.0, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
2, // scale
1, // base
2, // num_bins
0.9, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
EXPECT_THAT(ApproxBoundsProvider<TypeParam>::Create(options),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Base must be greater than 1")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsReturnsResultOnLargeInput) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
for (int i = -10000; i < 10000; ++i) {
(*approx_bound_provider)->AddEntry(i);
}
EXPECT_THAT(
(*approx_bound_provider)->FinalizeAndCalculateBounds(),
IsOkAndHolds(AllOf(
Field("lower_bound", &BoundsResult<TypeParam>::lower_bound, Lt(0)),
Field("upper_bound", &BoundsResult<TypeParam>::upper_bound, Gt(0)))));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsReturnsCorrectLowerAndUpperBounds) {
std::vector<TypeParam> a = {0, 0, 0, 0, 1, 3, 7, 8, 8, 8};
typename ApproxBoundsProvider<TypeParam>::Options options{
1.0, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
10, // num_bins
0.9, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
for (const auto& input : a) {
(*approx_bound_provider)->AddEntry(input);
}
const absl::StatusOr<BoundsResult<TypeParam>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
ASSERT_OK(result);
EXPECT_EQ(result->lower_bound, 0);
EXPECT_EQ(result->upper_bound, 1);
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsInputBeyondBins) {
std::vector<TypeParam> a = {-1, -1, -1, -1, 3, 9, 9, 9, 28, 12, 34};
typename ApproxBoundsProvider<TypeParam>::Options options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
4, // num_bins
0.95, // success_probability, threshold = 3.04886
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
for (const auto& input : a) {
(*approx_bound_provider)->AddEntry(input);
}
absl::StatusOr<BoundsResult<TypeParam>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
ASSERT_OK(result);
EXPECT_FLOAT_EQ(result->lower_bound, -1);
EXPECT_FLOAT_EQ(result->upper_bound, 8);
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsHandleOverflowPosBins) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
2, // num_bins
0.95, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
// Add std::numeric_limits<int64_t>::max() + 3 entries to the same bin to try to
// cause an overflow.
(*approx_bound_provider)->AddEntry(std::numeric_limits<int64_t>::max());
(*approx_bound_provider)->AddEntry(1);
(*approx_bound_provider)->AddEntry(1);
(*approx_bound_provider)->AddEntry(1);
absl::StatusOr<BoundsResult<TypeParam>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
// An overflow should cause a negative bin count and all bins to be below
// the threshold, resulting in an error when there are no bins to return.
// Thus, if there is no error, there was no overflow.
EXPECT_THAT(result.status(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("Bin count threshold was too large to find "
"approximate bounds.")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsHandleOverflowNegBins) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
2, // num_bins
0.95, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
// Add std::numeric_limits<int64_t>::max() + 3 entries to the same bin to try to
// cause an overflow.
(*approx_bound_provider)->AddEntry(std::numeric_limits<int64_t>::max());
(*approx_bound_provider)->AddEntry(-1);
(*approx_bound_provider)->AddEntry(-1);
(*approx_bound_provider)->AddEntry(-1);
absl::StatusOr<BoundsResult<TypeParam>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
// An overflow should cause a negative bin count and all bins to be below
// the threshold, resulting in an error when there are no bins to return.
// Thus, if there is no error, there was no overflow.
EXPECT_THAT(result.status(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("Bin count threshold was too large to find "
"approximate bounds.")));
}
TEST(ApproxBoundsProviderTest,
FinalizeAndCalculateBoundsHandleInfinityEntries) {
std::vector<double> a = {1, 1, 1, std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
typename ApproxBoundsProvider<double>::Options options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
7, // scale
2, // base
13, // num_bins
0.5, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<double>>>
approx_bound_provider = ApproxBoundsProvider<double>::Create(options);
for (const auto& input : a) {
(*approx_bound_provider)->AddEntry(input);
}
absl::StatusOr<BoundsResult<double>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
ASSERT_OK(result);
EXPECT_FLOAT_EQ(result->lower_bound, 0);
EXPECT_FLOAT_EQ(result->upper_bound,
28672); // scale * std::pow(base, bins - 1)
}
TEST(ApproxBoundsProviderTest, FinalizeAndCalculateBoundsNumPositiveBins) {
std::vector<double> a = {1, 1, 1, std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
typename ApproxBoundsProvider<double>::Options options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
2, // num_bins
0.5, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<double>>>
approx_bound_provider = ApproxBoundsProvider<double>::Create(options);
ASSERT_OK(approx_bound_provider.status());
for (const auto& input : a) {
(*approx_bound_provider)->AddEntry(input);
}
absl::StatusOr<BoundsResult<double>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
ASSERT_OK(result);
EXPECT_FLOAT_EQ(result->lower_bound, 0);
EXPECT_FLOAT_EQ(result->upper_bound, 2);
}
TEST(ApproxBoundsProviderTest, FinalizeAndCalculateBoundsSmallScale) {
std::vector<double> a = {0, -0.5, -0.5, 0.1, -0.7, 0.7,
0.8, 0.3, -0.5, 0.6, 0.5, 0.1};
typename ApproxBoundsProvider<double>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
0.1, // scale
2, // base
4, // num_bins
0.9, // success_probability
std::make_unique<ZeroNoiseMechanism::Builder>(), // mechanism_builder
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<double>>>
approx_bound_provider = ApproxBoundsProvider<double>::Create(options);
ASSERT_OK(approx_bound_provider.status());
for (const auto& input : a) {
(*approx_bound_provider)->AddEntry(input);
}
absl::StatusOr<BoundsResult<double>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
ASSERT_OK(result);
EXPECT_FLOAT_EQ(result->lower_bound, -0.8);
EXPECT_FLOAT_EQ(result->upper_bound, 0.8);
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
FinalizeAndCalculateBoundsAfterResetReturnsError) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
for (int i = -10000; i < 10000; ++i) {
(*approx_bound_provider)->AddEntry(i);
}
(*approx_bound_provider)->Reset();
EXPECT_THAT((*approx_bound_provider)->FinalizeAndCalculateBounds(),
StatusIs(absl::StatusCode::kFailedPrecondition,
HasSubstr("run over a larger dataset")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
GetBoundingReportReturnsReportWithSameBounds) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
for (int i = -10000; i < 10000; ++i) {
(*approx_bound_provider)->AddEntry(i);
}
const absl::StatusOr<BoundsResult<TypeParam>> result =
(*approx_bound_provider)->FinalizeAndCalculateBounds();
ASSERT_OK(result.status());
const BoundingReport report =
(*approx_bound_provider)->GetBoundingReport(result.value());
EXPECT_THAT(GetValue<TypeParam>(report.lower_bound()),
Eq(result->lower_bound));
EXPECT_THAT(GetValue<TypeParam>(report.upper_bound()),
Eq(result->upper_bound));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
MemoryUsedReturnsValueLargerThanObjectSize) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
EXPECT_THAT((*approx_bound_provider)->MemoryUsed(),
Gt(sizeof(ApproxBoundsProvider<TypeParam>)));
}
TYPED_TEST(ApproxBoundsProviderTypedTest, GetEpsilonReturnsExpectedValue) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1.234, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
EXPECT_THAT((*approx_bound_provider)->GetEpsilon(), Eq(1.234));
}
TYPED_TEST(ApproxBoundsProviderTypedTest, GetDeltaReturnsZero) {
// Delta is currently ignored for approx bounds.
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
EXPECT_THAT((*approx_bound_provider)->GetDelta(), Eq(0));
}
TYPED_TEST(ApproxBoundsProviderTypedTest, MergeWithEmptyBoundsSummaryFails) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
EXPECT_THAT(
(*approx_bound_provider)->Merge(BoundsSummary()),
StatusIs(absl::StatusCode::kInternal,
HasSubstr("Cannot merge summary with no histogram data")));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
SerializeFromPopulatedAndMergeEmptyBoundsProviderReturnsBounds) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
populated_approx_bounds_provider =
ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(populated_approx_bounds_provider.status());
for (int i = -5; i < 5; ++i) {
(*populated_approx_bounds_provider)->AddEntry(i);
}
typename ApproxBoundsProvider<TypeParam>::Options empty_approx_bounds_options{
1e20, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
empty_approx_bounds_provider =
ApproxBoundsProvider<TypeParam>::Create(empty_approx_bounds_options);
ASSERT_OK(empty_approx_bounds_provider.status());
ASSERT_OK((*empty_approx_bounds_provider)
->Merge((*populated_approx_bounds_provider)->Serialize()));
EXPECT_THAT(
(*empty_approx_bounds_provider)->FinalizeAndCalculateBounds(),
IsOkAndHolds(AllOf(
Field("lower_bound", &BoundsResult<TypeParam>::lower_bound, Lt(0)),
Field("upper_bound", &BoundsResult<TypeParam>::upper_bound, Gt(0)))));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
CreateClampedCalculationWithoutBoundsHasSameProperties) {
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
2, // scale
3, // base
10, // num_bins
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
std::unique_ptr<internal::ClampedCalculationWithoutBounds<TypeParam>>
clamped_calculation =
(*approx_bound_provider)->CreateClampedCalculationWithoutBounds();
EXPECT_THAT(clamped_calculation->GetNumBins(), Eq(10));
EXPECT_THAT(clamped_calculation->GetBaseForTesting(), Eq(3));
EXPECT_THAT(clamped_calculation->GetScaleForTesting(), Eq(2));
}
TYPED_TEST(ApproxBoundsProviderTypedTest,
ComputeFromPartialsCountValidityTest) {
int n_bins = 4;
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
n_bins, // num_bins
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
std::vector<TypeParam> pos_sum(n_bins, 0);
std::vector<TypeParam> neg_sum(n_bins, 0);
auto difference = [](TypeParam val1, TypeParam val2) { return val1 - val2; };
(*approx_bound_provider)
->template AddToPartials<TypeParam>(&pos_sum, 6, difference);
(*approx_bound_provider)
->template AddToPartials<TypeParam>(&neg_sum, -3, difference);
std::vector<int64_t> invalid_entries{-1,
std::numeric_limits<int64_t>::lowest()};
absl::StatusOr<TypeParam> result;
for (int64_t n_entries : invalid_entries) {
result = (*approx_bound_provider)
->template ComputeFromPartials<TypeParam>(
pos_sum, neg_sum, [](TypeParam x) { return x; }, -4, 4,
n_entries);
EXPECT_THAT(result.status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Count must be non-negative")));
}
result = (*approx_bound_provider)
->template ComputeFromPartials<TypeParam>(
pos_sum, neg_sum, [](TypeParam x) { return x; }, -4, 4, 0);
EXPECT_OK(result.status());
}
TYPED_TEST(ApproxBoundsProviderTypedTest, ComputeSumFromPartials) {
int n_bins = 4;
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
n_bins, // num_bins
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
ASSERT_OK(approx_bound_provider.status());
std::vector<TypeParam> pos_sum(n_bins, 0);
std::vector<TypeParam> neg_sum(n_bins, 0);
auto difference = [](TypeParam val1, TypeParam val2) { return val1 - val2; };
(*approx_bound_provider)
->template AddToPartials<TypeParam>(&pos_sum, 6, difference);
(*approx_bound_provider)
->template AddToPartials<TypeParam>(&neg_sum, -3, difference);
absl::StatusOr<TypeParam> result =
(*approx_bound_provider)
->template ComputeFromPartials<TypeParam>(
pos_sum, neg_sum, [](TypeParam x) { return x; }, -4, 4, 2);
ASSERT_OK(result);
EXPECT_EQ(result.value(), 1);
result = (*approx_bound_provider)
->template ComputeFromPartials<TypeParam>(
pos_sum, neg_sum, [](TypeParam x) { return x; }, -4, -1, 2);
ASSERT_OK(result);
EXPECT_EQ(result.value(), -4);
result = (*approx_bound_provider)
->template ComputeFromPartials<TypeParam>(
pos_sum, neg_sum, [](TypeParam x) { return x; }, 1, 4, 2);
ASSERT_OK(result);
EXPECT_EQ(result.value(), 5);
}
TYPED_TEST(ApproxBoundsProviderTypedTest, OverflowComputeFromPartials) {
int64_t int64lowest = std::numeric_limits<int64_t>::lowest();
int64_t int64max = std::numeric_limits<int64_t>::max();
std::function<int64_t(int64_t)> value_transform = [](int64_t x) { return x; };
typename ApproxBoundsProvider<TypeParam>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
1, // scale
2, // base
4, // num_bins
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<TypeParam>>>
approx_bound_provider = ApproxBoundsProvider<TypeParam>::Create(options);
std::vector<int64_t> neg_sum = {0, -1, -2, int64lowest};
std::vector<int64_t> pos_sum = {0, 0, 0, 0};
absl::StatusOr<int64_t> result =
(*approx_bound_provider)
->template ComputeFromPartials<int64_t>(
pos_sum, neg_sum, value_transform, int64lowest, int64max, 2);
ASSERT_OK(result);
// The negative sums should overflow to positive
EXPECT_GT(result.value(), 0);
result = (*approx_bound_provider)
->template ComputeFromPartials<int64_t>(
pos_sum, neg_sum, value_transform, int64lowest, -1, 2);
ASSERT_OK(result);
// The negative sums should overflow to positive
EXPECT_GT(result.value(), 0);
neg_sum = {0, 0, 0, 0};
pos_sum = {0, 1, 2, int64max};
result = (*approx_bound_provider)
->template ComputeFromPartials<int64_t>(
pos_sum, neg_sum, value_transform, int64lowest, int64max, 2);
ASSERT_OK(result);
// The positive sums should overflow to negative
EXPECT_LT(result.value(), 0);
result = (*approx_bound_provider)
->template ComputeFromPartials<int64_t>(
pos_sum, neg_sum, value_transform, 1, int64max, 2);
ASSERT_OK(result);
// The positive sums should overflow to negative
EXPECT_LT(result.value(), 0);
}
TEST(ApproxBoundsProviderTest, ComputeSumFromPartialsAcrossOne) {
const typename ApproxBoundsProvider<double>::Options options{
1, // epsilon
1, // max_partitions_contributed
1, // max_contributions_per_partition
};
absl::StatusOr<std::unique_ptr<ApproxBoundsProvider<double>>>
approx_bound_provider = ApproxBoundsProvider<double>::Create(options);
ASSERT_OK(approx_bound_provider);
std::vector<double> pos_sum((*approx_bound_provider)->NumPositiveBins(), 0);
std::vector<double> neg_sum((*approx_bound_provider)->NumPositiveBins(), 0);
auto difference = [](double val1, double val2) { return val1 - val2; };
(*approx_bound_provider)
->template AddToPartials<double>(&pos_sum, 6, difference);
(*approx_bound_provider)
->template AddToPartials<double>(&neg_sum, -3, difference);
absl::StatusOr<double> result =
(*approx_bound_provider)
->template ComputeFromPartials<double>(
pos_sum, neg_sum, [](double x) { return x; }, -4, -0.5, 2);
ASSERT_OK(result);
EXPECT_DOUBLE_EQ(result.value(), -3.5);
result = (*approx_bound_provider)
->template ComputeFromPartials<double>(
pos_sum, neg_sum, [](double x) { return x; }, 0.5, 4, 2);
ASSERT_OK(result);
EXPECT_DOUBLE_EQ(result.value(), 4.5);
}
} // namespace
} // namespace differential_privacy