Skip to content

Commit ec0b439

Browse files
authored
C++: Fix multiple contributions for implicit ApproxBounds (#87)
This is a backport of the fix in the main branch ae9c6b6 This change contains a critical fix for cases where ApproxBounds is used implicitly (not specified in the builder) and we use multiple contributions per user or multiple contributions per partition for sum, mean, variance, and stddev.
1 parent 8d1c557 commit ec0b439

File tree

4 files changed

+49
-0
lines changed

4 files changed

+49
-0
lines changed

cc/algorithms/approx-bounds.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,10 @@ class ApproxBounds : public Algorithm<T> {
388388
return report;
389389
}
390390

391+
// Returns a pointer to the mechanism for testing. Does not transfer
392+
// ownership.
393+
NumericalMechanism* GetMechanismForTesting() { return mechanism_.get(); }
394+
391395
protected:
392396
ApproxBounds(double epsilon, int64_t num_bins, double scale, double base,
393397
double k, bool preset_k,

cc/algorithms/bounded-algorithm.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,18 @@ class BoundedAlgorithmBuilder : public AlgorithmBuilder<T, Algorithm, Builder> {
9797
remaining_epsilon_ =
9898
AlgorithmBuilder::GetEpsilon().value() - bounds_epsilon;
9999
auto mech_builder = AlgorithmBuilder::GetMechanismBuilderClone();
100+
const int max_partitions_contributed =
101+
AlgorithmBuilder::GetMaxPartitionsContributed().value_or(1);
102+
const int max_contributions_per_partition =
103+
AlgorithmBuilder::GetMaxContributionsPerPartition().value_or(1);
100104
ASSIGN_OR_RETURN(approx_bounds_,
101105
typename ApproxBounds<T>::Builder()
102106
.SetEpsilon(bounds_epsilon)
103107
.SetLaplaceMechanism(std::move(mech_builder))
108+
.SetMaxPartitionsContributed(
109+
max_partitions_contributed)
110+
.SetMaxContributionsPerPartition(
111+
max_contributions_per_partition)
104112
.Build());
105113
}
106114
return absl::OkStatus();

cc/algorithms/bounded-sum.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,9 @@ class BoundedSumWithApproxBounds : public BoundedSum<T> {
325325
return memory;
326326
}
327327

328+
// Returns the ApproxBounds for testing. Does not transfer ownership.
329+
ApproxBounds<T>* GetApproxBoundsForTesting() { return approx_bounds_.get(); }
330+
328331
protected:
329332
base::StatusOr<Output> GenerateResult(double privacy_budget,
330333
double noise_interval_level) override {

cc/algorithms/bounded-sum_test.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ namespace differential_privacy {
3636
namespace {
3737

3838
using ::differential_privacy::test_utils::ZeroNoiseMechanism;
39+
using ::testing::DoubleEq;
3940
using ::testing::Eq;
41+
using ::testing::NotNull;
4042
using ::differential_privacy::base::testing::EqualsProto;
4143
using ::testing::HasSubstr;
4244
using ::differential_privacy::base::testing::IsOkAndHolds;
@@ -804,5 +806,37 @@ TYPED_TEST(BoundedSumTest, SplitsEpsilonWithAutomaticBounds) {
804806
EXPECT_LT(sum_with_approx_bounds->GetAggregationEpsilon(), epsilon);
805807
}
806808

809+
TEST(BoundedSumTest, ApproxBoundsMechanismHasExpectedVariance) {
810+
const double epsilon = 1.0;
811+
const int max_partitions_contributed = 2;
812+
const int max_contributions_per_partition = 3;
813+
814+
base::StatusOr<std::unique_ptr<BoundedSum<double>>> bs =
815+
BoundedSum<double>::Builder()
816+
.SetEpsilon(epsilon)
817+
.SetMaxPartitionsContributed(max_partitions_contributed)
818+
.SetMaxContributionsPerPartition(max_contributions_per_partition)
819+
.Build();
820+
ASSERT_OK(bs);
821+
822+
auto *bs_with_approx_bounds =
823+
static_cast<BoundedSumWithApproxBounds<double>*>(bs.value().get());
824+
ASSERT_THAT(bs_with_approx_bounds, NotNull());
825+
826+
const double expected_variance =
827+
LaplaceMechanism::Builder()
828+
.SetEpsilon(epsilon)
829+
.SetL0Sensitivity(max_partitions_contributed)
830+
.SetLInfSensitivity(max_contributions_per_partition)
831+
.Build()
832+
.value()
833+
->GetVariance();
834+
835+
EXPECT_THAT(bs_with_approx_bounds->GetApproxBoundsForTesting()
836+
->GetMechanismForTesting()
837+
->GetVariance(),
838+
DoubleEq(expected_variance));
839+
}
840+
807841
} // namespace
808842
} // namespace differential_privacy

0 commit comments

Comments
 (0)