From 1ac116b34d790aff4d3ab7deb1d839285fb7ed3b Mon Sep 17 00:00:00 2001 From: Differential Privacy Team Date: Thu, 10 Nov 2022 13:17:04 -0800 Subject: [PATCH] Improvements in Accounting, refactoring in C++ & Go Accounting: - Support noise_multiplier=0 and sampling_probability=0 in (Poisson subsampled) Gaussian and Laplace DpEvent - Fix behavior of RdpAccountant when sampling_probability=0 and noise_multiplier=0 in Poisson subsampled Gaussian DpEvent - Add [0, 1] as default lower-endpoint-and-guess bracket interval. Remove redundant check of bracket interval, since brentq also raises when interval is bad C++: - Refactoring of partition selection Go: - Add go.sum Change-Id: I1f6219a51f43218693aa9b32c51b585469a1eb28 GitOrigin-RevId: 6bf7243904c5d31ad6670832912da2c9c19ebc38 --- cc/algorithms/BUILD | 4 + cc/algorithms/partition-selection.h | 198 ++++------ cc/algorithms/partition-selection_test.cc | 366 ++++++++---------- cc/algorithms/util.cc | 2 +- cc/algorithms/util.h | 1 - go/go.sum | 29 ++ java/dp_java_deps_preload.bzl | 1 - python/dp_accounting/mechanism_calibration.py | 33 +- .../mechanism_calibration_test.py | 4 +- .../pld/pld_privacy_accountant.py | 56 ++- .../pld/pld_privacy_accountant_test.py | 22 ++ .../rdp/rdp_privacy_accountant.py | 6 +- .../rdp/rdp_privacy_accountant_test.py | 8 +- 13 files changed, 357 insertions(+), 373 deletions(-) create mode 100644 go/go.sum diff --git a/cc/algorithms/BUILD b/cc/algorithms/BUILD index 79b7d8c4..b34f208e 100644 --- a/cc/algorithms/BUILD +++ b/cc/algorithms/BUILD @@ -600,6 +600,7 @@ cc_library( ":numerical-mechanisms", ":rand", ":util", + "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_cc_differential_privacy//base:status", ], @@ -610,8 +611,11 @@ cc_test( srcs = ["partition-selection_test.cc"], shard_count = 2, deps = [ + ":numerical-mechanisms", ":numerical-mechanisms-testing", ":partition-selection", + "//base/testing:status_matchers", + "@com_google_absl//absl/memory", "@com_google_absl//absl/status:statusor", "@com_google_googletest//:gtest_main", ], diff --git a/cc/algorithms/partition-selection.h b/cc/algorithms/partition-selection.h index 469ddddd..e98287ff 100644 --- a/cc/algorithms/partition-selection.h +++ b/cc/algorithms/partition-selection.h @@ -24,6 +24,7 @@ #include #include +#include "absl/status/status.h" #include "absl/status/statusor.h" #include "algorithms/distributions.h" #include "algorithms/numerical-mechanisms.h" @@ -33,65 +34,13 @@ namespace differential_privacy { -// Provides a common abstraction for PartitionSelectionStrategy. Each partition -// selection strategy class has a builder with which it can be instantiated, and -// calling ShouldKeep will return true if a partition with the given number of -// users should be kept based on the values the partition selection strategy -// was instantiated with (while ShouldKeep will return false if the partition -// should have been dropped). +// Provides a common abstraction for PartitionSelectionStrategy. Calling +// ShouldKeep will return true if a partition with the given number of users +// should be kept based on the values the partition selection strategy was +// instantiated with (while ShouldKeep will return false if the partition should +// have been dropped). class PartitionSelectionStrategy { public: - // Builder base class - class Builder { - public: - virtual ~Builder() = default; - - Builder& SetEpsilon(double epsilon) { - epsilon_ = epsilon; - return *this; - } - - Builder& SetDelta(double delta) { - delta_ = delta; - return *this; - } - - Builder& SetMaxPartitionsContributed(int64_t max_partitions_contributed) { - max_partitions_contributed_ = max_partitions_contributed; - return *this; - } - - virtual absl::StatusOr> - Build() = 0; - - protected: - // Convenience methods to check if the Builder variables are set & valid - absl::Status EpsilonIsSetAndValid() { - return PartitionSelectionStrategy::EpsilonIsSetAndValid(epsilon_); - } - absl::Status DeltaIsSetAndValid() { - return PartitionSelectionStrategy::DeltaIsSetAndValid(delta_); - } - absl::Status MaxPartitionsContributedIsSetAndValid() { - return PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed_); - } - - absl::optional GetEpsilon() { return epsilon_; } - - absl::optional GetDelta() { return delta_; } - - absl::optional GetMaxPartitionsContributed() { - return max_partitions_contributed_; - } - - absl::optional delta_; - - private: - absl::optional epsilon_; - absl::optional max_partitions_contributed_; - }; - virtual ~PartitionSelectionStrategy() = default; double GetEpsilon() const { return epsilon_; } @@ -102,6 +51,9 @@ class PartitionSelectionStrategy { return max_partitions_contributed_; } + // This is set with the results from `CalculateAdjustedDelta`. + double GetAdjustedDelta() const { return adjusted_delta_; } + // ShouldKeep returns true when a partition with a given number of users // should be kept and false otherwise. virtual bool ShouldKeep(double num_users) = 0; @@ -117,29 +69,6 @@ class PartitionSelectionStrategy { max_partitions_contributed_(max_partitions_contributed), adjusted_delta_(adjusted_delta) {} - // Checks if epsilon is set and valid. - static absl::Status EpsilonIsSetAndValid(absl::optional epsilon) { - RETURN_IF_ERROR(ValidateIsFiniteAndPositive(epsilon, "Epsilon")); - return absl::OkStatus(); - } - - // Checks if delta is set and valid. - static absl::Status DeltaIsSetAndValid(absl::optional delta) { - RETURN_IF_ERROR(ValidateIsInInclusiveInterval(delta, 0, 1, "Delta")); - return absl::OkStatus(); - } - - // Checks if the max number of partitions contributed to is set and valid. - static absl::Status MaxPartitionsContributedIsSetAndValid( - absl::optional max_partitions_contributed) { - RETURN_IF_ERROR(ValidateIsPositive( - max_partitions_contributed, - "Max number of partitions a user can contribute to")); - return absl::OkStatus(); - } - - double GetAdjustedDelta() const { return adjusted_delta_; } - // We must derive an adjusted delta, to be used as the probability of keeping // a single partition with one user, from delta, the probability we keep any // of the partitions contributed to by a single user. Since the probability @@ -148,10 +77,9 @@ class PartitionSelectionStrategy { // contribute to will get us delta, we can solve to get the following formula. static absl::StatusOr CalculateAdjustedDelta( double delta, int64_t max_partitions_contributed) { - RETURN_IF_ERROR(PartitionSelectionStrategy::DeltaIsSetAndValid(delta)); + RETURN_IF_ERROR(ValidateDelta(delta)); RETURN_IF_ERROR( - PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed)); + ValidateMaxPartitionsContributed(max_partitions_contributed)); // Numerically stable equivalent of // 1- pow(1 - delta, 1 / max_partitions_contributed). @@ -164,11 +92,9 @@ class PartitionSelectionStrategy { // Inverse of CalculateAdjustedDelta() static absl::StatusOr CalculateUnadjustedDelta( double adjusted_delta, int64_t max_partitions_contributed) { + RETURN_IF_ERROR(ValidateDelta(adjusted_delta)); RETURN_IF_ERROR( - PartitionSelectionStrategy::DeltaIsSetAndValid(adjusted_delta)); - RETURN_IF_ERROR( - PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed)); + ValidateMaxPartitionsContributed(max_partitions_contributed)); // Numerically stable equivalent of // 1 - pow(1 - adjusted_delta, max_partitions_contributed). @@ -185,6 +111,46 @@ class PartitionSelectionStrategy { double adjusted_delta_; }; +// Provides a common abstraction for PartitionSelectionStrategy builders. Each +// partition selection strategy builder inherits from this builder. +class PartitionSelectionStrategyBuilder { + public: + virtual ~PartitionSelectionStrategyBuilder() = default; + + PartitionSelectionStrategyBuilder& SetEpsilon(double epsilon) { + epsilon_ = epsilon; + return *this; + } + + PartitionSelectionStrategyBuilder& SetDelta(double delta) { + delta_ = delta; + return *this; + } + + PartitionSelectionStrategyBuilder& SetMaxPartitionsContributed( + int64_t max_partitions_contributed) { + max_partitions_contributed_ = max_partitions_contributed; + return *this; + } + + virtual absl::StatusOr> + Build() = 0; + + protected: + std::optional GetEpsilon() { return epsilon_; } + + std::optional GetDelta() { return delta_; } + + std::optional GetMaxPartitionsContributed() { + return max_partitions_contributed_; + } + + private: + std::optional epsilon_; + std::optional delta_; + std::optional max_partitions_contributed_; +}; + // NearTruncatedGeometricPartitionSelection implements magic partition selection // - instead of calculating a specific threshold to determine whether or not a // partition should be kept, magic partition selection uses a formula derived @@ -198,13 +164,14 @@ class NearTruncatedGeometricPartitionSelection : public PartitionSelectionStrategy { public: // Builder for NearTruncatedGeometricPartitionSelection - class Builder : public PartitionSelectionStrategy::Builder { + class Builder : public PartitionSelectionStrategyBuilder { public: absl::StatusOr> Build() override { - RETURN_IF_ERROR(EpsilonIsSetAndValid()); - RETURN_IF_ERROR(DeltaIsSetAndValid()); - RETURN_IF_ERROR(MaxPartitionsContributedIsSetAndValid()); + RETURN_IF_ERROR(ValidateEpsilon(GetEpsilon())); + RETURN_IF_ERROR(ValidateDelta(GetDelta())); + RETURN_IF_ERROR( + ValidateMaxPartitionsContributed(GetMaxPartitionsContributed())); ASSIGN_OR_RETURN( double adjusted_delta, @@ -278,7 +245,6 @@ class NearTruncatedGeometricPartitionSelection double adjusted_epsilon_; double crossover_1_; double crossover_2_; - }; // PreaggPartitionSelection is the deprecated name for @@ -292,7 +258,7 @@ using PreaggPartitionSelection = NearTruncatedGeometricPartitionSelection; class LaplacePartitionSelection : public PartitionSelectionStrategy { public: // Builder for LaplacePartitionSelection - class Builder : public PartitionSelectionStrategy::Builder { + class Builder : public PartitionSelectionStrategyBuilder { public: Builder& SetLaplaceMechanism( std::unique_ptr laplace_builder) { @@ -302,9 +268,11 @@ class LaplacePartitionSelection : public PartitionSelectionStrategy { absl::StatusOr> Build() override { - RETURN_IF_ERROR(EpsilonIsSetAndValid()); - RETURN_IF_ERROR(DeltaIsSetAndValid()); - RETURN_IF_ERROR(MaxPartitionsContributedIsSetAndValid()); + RETURN_IF_ERROR(ValidateEpsilon(GetEpsilon())); + RETURN_IF_ERROR(ValidateDelta(GetDelta())); + RETURN_IF_ERROR( + ValidateMaxPartitionsContributed(GetMaxPartitionsContributed())); + if (laplace_builder_ == nullptr) { laplace_builder_ = absl::make_unique(); } @@ -357,10 +325,9 @@ class LaplacePartitionSelection : public PartitionSelectionStrategy { static absl::StatusOr CalculateDelta( double epsilon, double threshold, int64_t max_partitions_contributed) { - RETURN_IF_ERROR(PartitionSelectionStrategy::EpsilonIsSetAndValid(epsilon)); + RETURN_IF_ERROR(ValidateEpsilon(epsilon)); RETURN_IF_ERROR( - PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed)); + ValidateMaxPartitionsContributed(max_partitions_contributed)); if (threshold < 1) { return CalculateUnadjustedDelta( @@ -380,11 +347,10 @@ class LaplacePartitionSelection : public PartitionSelectionStrategy { static absl::StatusOr CalculateThreshold( double epsilon, double delta, int64_t max_partitions_contributed) { - RETURN_IF_ERROR(PartitionSelectionStrategy::EpsilonIsSetAndValid(epsilon)); - RETURN_IF_ERROR(PartitionSelectionStrategy::DeltaIsSetAndValid(delta)); + RETURN_IF_ERROR(ValidateEpsilon(epsilon)); + RETURN_IF_ERROR(ValidateDelta(delta)); RETURN_IF_ERROR( - PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed)); + ValidateMaxPartitionsContributed(max_partitions_contributed)); ASSIGN_OR_RETURN(double adjusted_delta, CalculateAdjustedDelta(delta, max_partitions_contributed)); @@ -435,7 +401,7 @@ class LaplacePartitionSelection : public PartitionSelectionStrategy { class GaussianPartitionSelection : public PartitionSelectionStrategy { public: // Builder for GaussianPartitionSelection - class Builder : public PartitionSelectionStrategy::Builder { + class Builder : public PartitionSelectionStrategyBuilder { public: Builder& SetGaussianMechanism( std::unique_ptr gaussian_builder) { @@ -445,9 +411,10 @@ class GaussianPartitionSelection : public PartitionSelectionStrategy { absl::StatusOr> Build() override { - RETURN_IF_ERROR(EpsilonIsSetAndValid()); - RETURN_IF_ERROR(DeltaIsSetAndValid()); - RETURN_IF_ERROR(MaxPartitionsContributedIsSetAndValid()); + RETURN_IF_ERROR(ValidateEpsilon(GetEpsilon())); + RETURN_IF_ERROR(ValidateDelta(GetDelta())); + RETURN_IF_ERROR( + ValidateMaxPartitionsContributed(GetMaxPartitionsContributed())); if (gaussian_builder_ == nullptr) { gaussian_builder_ = absl::make_unique(); } @@ -512,11 +479,10 @@ class GaussianPartitionSelection : public PartitionSelectionStrategy { static absl::StatusOr CalculateThresholdDelta( double epsilon, double noise_delta, double threshold, int64_t max_partitions_contributed) { - RETURN_IF_ERROR(PartitionSelectionStrategy::EpsilonIsSetAndValid(epsilon)); - RETURN_IF_ERROR(DeltaIsSetAndValid(noise_delta)); + RETURN_IF_ERROR(ValidateEpsilon(epsilon)); + RETURN_IF_ERROR(ValidateDelta(noise_delta)); RETURN_IF_ERROR( - PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed)); + ValidateMaxPartitionsContributed(max_partitions_contributed)); double sigma = GaussianMechanism::CalculateStddev( epsilon, noise_delta, max_partitions_contributed); @@ -539,12 +505,11 @@ class GaussianPartitionSelection : public PartitionSelectionStrategy { static absl::StatusOr CalculateThreshold( double epsilon, double noise_delta, double threshold_delta, int64_t max_partitions_contributed) { - RETURN_IF_ERROR(PartitionSelectionStrategy::EpsilonIsSetAndValid(epsilon)); - RETURN_IF_ERROR(DeltaIsSetAndValid(noise_delta)); - RETURN_IF_ERROR(DeltaIsSetAndValid(threshold_delta)); + RETURN_IF_ERROR(ValidateEpsilon(epsilon)); + RETURN_IF_ERROR(ValidateDelta(noise_delta)); + RETURN_IF_ERROR(ValidateDelta(threshold_delta)); RETURN_IF_ERROR( - PartitionSelectionStrategy::MaxPartitionsContributedIsSetAndValid( - max_partitions_contributed)); + ValidateMaxPartitionsContributed(max_partitions_contributed)); double sigma = GaussianMechanism::CalculateStddev( epsilon, noise_delta, max_partitions_contributed); @@ -580,7 +545,6 @@ class GaussianPartitionSelection : public PartitionSelectionStrategy { double threshold_; std::unique_ptr mechanism_; }; - } // namespace differential_privacy #endif // DIFFERENTIAL_PRIVACY_CPP_ALGORITHMS_PARTITION_SELECTION_H_ diff --git a/cc/algorithms/partition-selection_test.cc b/cc/algorithms/partition-selection_test.cc index f4c7a106..000e485b 100644 --- a/cc/algorithms/partition-selection_test.cc +++ b/cc/algorithms/partition-selection_test.cc @@ -19,10 +19,12 @@ #include #include +#include "base/testing/status_matchers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/memory/memory.h" #include "absl/status/statusor.h" -#include "algorithms/numerical-mechanisms-testing.h" +#include "algorithms/numerical-mechanisms.h" namespace differential_privacy { namespace { @@ -30,7 +32,8 @@ namespace { using ::testing::DoubleEq; using ::testing::DoubleNear; using ::testing::Eq; -using ::testing::MatchesRegex; +using ::testing::HasSubstr; +using ::differential_privacy::base::testing::StatusIs; constexpr int kNumSamples = 10000000; constexpr int kSmallNumSamples = 1000000; @@ -52,143 +55,122 @@ constexpr double kCalcThresholdTestDefaultTolerance = 0.05; TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionUnsetEpsilon) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = - test_builder.SetDelta(0.1).SetMaxPartitionsContributed(2).Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Epsilon must be set.*")); + EXPECT_THAT(test_builder.SetDelta(0.1).SetMaxPartitionsContributed(2).Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Epsilon must be set"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionNanEpsilon) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(NAN) - .SetDelta(0.3) - .SetMaxPartitionsContributed(4) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Epsilon must be a valid numeric value.*")); + EXPECT_THAT(test_builder.SetEpsilon(NAN) + .SetDelta(0.3) + .SetMaxPartitionsContributed(4) + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Epsilon must be a valid numeric value"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionNotFiniteEpsilon) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = - test_builder.SetEpsilon(std::numeric_limits::infinity()) - .SetDelta(0.3) - .SetMaxPartitionsContributed(4) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite.*")); + EXPECT_THAT(test_builder.SetEpsilon(std::numeric_limits::infinity()) + .SetDelta(0.3) + .SetMaxPartitionsContributed(4) + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Epsilon must be finite"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionNegativeEpsilon) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(-5.0) - .SetDelta(0.6) - .SetMaxPartitionsContributed(7) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Epsilon must be finite and positive.*")); + EXPECT_THAT(test_builder.SetEpsilon(-5.0) + .SetDelta(0.6) + .SetMaxPartitionsContributed(7) + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Epsilon must be finite and positive"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionUnsetDelta) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = - test_builder.SetEpsilon(8.0).SetMaxPartitionsContributed(9).Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Delta must be set.*")); + EXPECT_THAT( + test_builder.SetEpsilon(8.0).SetMaxPartitionsContributed(9).Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be set."))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionNanDelta) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(1.2) - .SetDelta(NAN) - .SetMaxPartitionsContributed(3) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Delta must be a valid numeric value.*")); + EXPECT_THAT(test_builder.SetEpsilon(1.2) + .SetDelta(NAN) + .SetMaxPartitionsContributed(3) + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be a valid numeric value"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionNotFiniteDelta) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(1.2) - .SetDelta(std::numeric_limits::infinity()) - .SetMaxPartitionsContributed(3) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + EXPECT_THAT(test_builder.SetEpsilon(1.2) + .SetDelta(std::numeric_limits::infinity()) + .SetMaxPartitionsContributed(3) + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionInvalidDelta) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(4.5) - .SetDelta(6.0) - .SetMaxPartitionsContributed(7) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + EXPECT_THAT(test_builder.SetEpsilon(4.5) + .SetDelta(6.0) + .SetMaxPartitionsContributed(7) + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionUnsetMaxPartitionsContributed) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(0.8).SetDelta(0.9).Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be set.*")); + EXPECT_THAT( + test_builder.SetEpsilon(0.8).SetDelta(0.9).Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be set"))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionNegativeMaxPartitionsContributed) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(0.1) - .SetDelta(0.2) - .SetMaxPartitionsContributed(-3) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be positive.*")); + EXPECT_THAT( + test_builder.SetEpsilon(0.1) + .SetDelta(0.2) + .SetMaxPartitionsContributed(-3) + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be positive, but is -3."))); } TEST(PartitionSelectionTest, NearTruncatedGeometricPartitionSelectionZeroMaxPartitionsContributed) { NearTruncatedGeometricPartitionSelection::Builder test_builder; - auto failed_build = test_builder.SetEpsilon(0.1) - .SetDelta(0.2) - .SetMaxPartitionsContributed(0) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be positive.*")); + EXPECT_THAT( + test_builder.SetEpsilon(0.1) + .SetDelta(0.2) + .SetMaxPartitionsContributed(0) + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be positive, but is 0."))); } // We expect the probability of keeping a partition with one user @@ -365,142 +347,124 @@ TEST(PartitionSelectionTest, TEST(PartitionSelectionTest, LaplacePartitionSelectionUnsetMaxPartitionsContributed) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetDelta(0.1) .SetEpsilon(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be set.*")); + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be set"))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionNegativeMaxPartitionsContributed) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetDelta(0.1) .SetEpsilon(2) .SetMaxPartitionsContributed(-3) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be positive.*")); + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be positive, but is -3."))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionZeroMaxPartitionsContributed) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetDelta(0.1) .SetEpsilon(2) .SetMaxPartitionsContributed(0) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be positive.*")); + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be positive, but is 0."))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionUnsetEpsilon) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetDelta(0.1) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Epsilon must be set.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Epsilon must be set."))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionUnsetDelta) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Delta must be set.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be set"))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionNanDelta) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(NAN) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Delta must be a valid numeric value.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be a valid numeric value"))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionNotFiniteDelta) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(std::numeric_limits::infinity()) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionInvalidPositiveDelta) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(5.2) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, LaplacePartitionSelectionInvalidNegativeDelta) { LaplacePartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetLaplaceMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(-0.1) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } // We expect the probability of keeping a partition with one user @@ -1124,142 +1088,124 @@ TEST(PartitionSelectionTest, RoundTripDeltaTests) { TEST(PartitionSelectionTest, GaussianPartitionSelectionUnsetMaxPartitionsContributed) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetDelta(0.1) .SetEpsilon(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be set.*")); + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be set"))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionNegativeMaxPartitionsContributed) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetDelta(0.1) .SetEpsilon(2) .SetMaxPartitionsContributed(-3) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be positive.*")); + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be positive"))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionZeroMaxPartitionsContributed) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetDelta(0.1) .SetEpsilon(2) .SetMaxPartitionsContributed(0) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Max number of partitions a user can" - " contribute to must be positive.*")); + .Build(), + StatusIs( + absl::StatusCode::kInvalidArgument, + HasSubstr("Maximum number of partitions that can be contributed to " + "(i.e., L0 sensitivity) must be positive, but is 0."))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionUnsetEpsilon) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetDelta(0.1) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Epsilon must be set.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Epsilon must be set."))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionUnsetDelta) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Delta must be set.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be set."))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionNanDelta) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(NAN) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, MatchesRegex("^Delta must be a valid numeric value.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be a valid numeric value"))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionNotFiniteDelta) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(std::numeric_limits::infinity()) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionInvalidPositiveDelta) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(5.2) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, GaussianPartitionSelectionInvalidNegativeDelta) { GaussianPartitionSelection::Builder test_builder; - auto failed_build = + EXPECT_THAT( test_builder .SetGaussianMechanism(absl::make_unique()) .SetEpsilon(0.1) .SetDelta(-0.1) .SetMaxPartitionsContributed(2) - .Build(); - EXPECT_THAT(failed_build.status().code(), - Eq(absl::StatusCode::kInvalidArgument)); - std::string message(std::string(failed_build.status().message())); - EXPECT_THAT(message, - MatchesRegex("^Delta must be in the inclusive interval.*")); + .Build(), + StatusIs(absl::StatusCode::kInvalidArgument, + HasSubstr("Delta must be in the inclusive interval"))); } TEST(PartitionSelectionTest, CalculateGaussianThresholdTests) { diff --git a/cc/algorithms/util.cc b/cc/algorithms/util.cc index fb6dfe90..6d3fae0a 100644 --- a/cc/algorithms/util.cc +++ b/cc/algorithms/util.cc @@ -18,6 +18,7 @@ #include #include +#include #include #include "absl/status/status.h" @@ -323,5 +324,4 @@ absl::Status ValidateBranchingFactor(absl::optional branching_factor) { return ValidateIsGreaterThanOrEqualTo(branching_factor, /*lower_bound=*/2, "Branching Factor"); } - } // namespace differential_privacy diff --git a/cc/algorithms/util.h b/cc/algorithms/util.h index 9aeb72cc..ceb99fc2 100644 --- a/cc/algorithms/util.h +++ b/cc/algorithms/util.h @@ -546,7 +546,6 @@ absl::Status ValidateBounds(absl::optional lower, absl::optional upper) { } return absl::OkStatus(); } - } // namespace differential_privacy #endif // DIFFERENTIAL_PRIVACY_ALGORITHMS_UTIL_H_ diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 00000000..d88af245 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,29 @@ +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/grd/stat v0.0.0-20130623202159-138af3fd5012 h1:TVY1GBBIAAph4RWO9Y3p1wU+7n6khY1jxPKjDphzznA= +github.com/grd/stat v0.0.0-20130623202159-138af3fd5012/go.mod h1:hHyH5N67TF4tD4PBbqMlyuIu5Lq5QwKSgNyyG31trzY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= +gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= \ No newline at end of file diff --git a/java/dp_java_deps_preload.bzl b/java/dp_java_deps_preload.bzl index 1a80d579..20e21bf4 100644 --- a/java/dp_java_deps_preload.bzl +++ b/java/dp_java_deps_preload.bzl @@ -23,4 +23,3 @@ def dp_java_deps_prework(): sha256 = BAZEL_COMMON_SHA, strip_prefix = "bazel-common-%s" % BAZEL_COMMON_TAG, ) - diff --git a/python/dp_accounting/mechanism_calibration.py b/python/dp_accounting/mechanism_calibration.py index 93dafba0..055788c9 100644 --- a/python/dp_accounting/mechanism_calibration.py +++ b/python/dp_accounting/mechanism_calibration.py @@ -106,10 +106,9 @@ def calibrate_dp_mechanism( Callable[[int], dp_event.DpEvent]], target_epsilon: float, target_delta: float, - bracket_interval: BracketInterval, + bracket_interval: Optional[BracketInterval] = None, discrete: bool = False, - tol: Optional[float] = None -) -> Union[float, int]: + tol: Optional[float] = None) -> Union[float, int]: """Searches for optimal mechanism parameter value within privacy budget. The procedure searches over the space of parameters by creating, for each @@ -131,7 +130,8 @@ def calibrate_dp_mechanism( target_epsilon: The target epsilon value. target_delta: The target delta value. bracket_interval: A BracketInterval used to determine the upper and lower - endpoints of the interval within which Brent's method will search. + endpoints of the interval within which Brent's method will search. If + None, searches for a non-negative bracket starting from [0, 1]. discrete: A bool determining whether the parameter is continuous or discrete valued. If True, the parameter is assumed to take only integer values. Concretely, `discrete=True` has three effects. 1) ints, not floats are @@ -173,6 +173,9 @@ def calibrate_dp_mechanism( raise ValueError(f'target_delta must be in range [0, 1]. Found ' f'{target_delta}.') + if bracket_interval is None: + bracket_interval = LowerEndpointAndGuess(0, 1) + if tol is None: tol = 0.5 if discrete else 1e-6 elif discrete: @@ -196,19 +199,17 @@ def epsilon_gap(x: float) -> float: raise TypeError(f'Unrecognized bracket_interval type: ' f'{type(bracket_interval)}') - value_1 = epsilon_gap(bracket_interval.endpoint_1) - value_2 = epsilon_gap(bracket_interval.endpoint_2) - if value_1 * value_2 > 0: + try: + root, result = optimize.brentq( + epsilon_gap, + bracket_interval.endpoint_1, + bracket_interval.endpoint_2, + xtol=tol, + full_output=True) + except ValueError as err: raise ValueError( - f'Bracket endpoints do not bracket target_epsilon={target_epsilon}: ' - f'endpoint 1 {bracket_interval.endpoint_1} with epsilon=' - f'{value_1 + target_epsilon}, and endpoint 2 ' - f'{bracket_interval.endpoint_2} with epsilon={value_2 + target_epsilon}' - ) - - root, result = optimize.brentq(epsilon_gap, bracket_interval.endpoint_1, - bracket_interval.endpoint_2, xtol=tol, - full_output=True) + '`brentq` raised ValueError. This often means the supplied bracket ' + f'interval {bracket_interval} did not bracket a solution.') from err if not result.converged: raise NoOptimumFoundError( diff --git a/python/dp_accounting/mechanism_calibration_test.py b/python/dp_accounting/mechanism_calibration_test.py index 5975d3e0..ad4b86da 100644 --- a/python/dp_accounting/mechanism_calibration_test.py +++ b/python/dp_accounting/mechanism_calibration_test.py @@ -167,12 +167,12 @@ def test_raises_target_delta_out_of_range(self): mechanism_calibration.ExplicitBracketInterval(0, 5)) def test_bad_bracket_interval(self): - with self.assertRaisesRegex(ValueError, 'Bracket endpoints'): + with self.assertRaisesRegex(ValueError, 'did not bracket a solution'): mechanism_calibration.calibrate_dp_mechanism( lambda: MockAccountant(lambda x: x), MockEvent, 1.0, 0.0, mechanism_calibration.ExplicitBracketInterval(2, 5)) - with self.assertRaisesRegex(ValueError, 'Bracket endpoints'): + with self.assertRaisesRegex(ValueError, 'did not bracket a solution'): mechanism_calibration.calibrate_dp_mechanism( lambda: MockAccountant(lambda x: x), MockEvent, 1.0, 0.0, mechanism_calibration.ExplicitBracketInterval(-2, 0)) diff --git a/python/dp_accounting/pld/pld_privacy_accountant.py b/python/dp_accounting/pld/pld_privacy_accountant.py index 91d620c9..81afbfc1 100644 --- a/python/dp_accounting/pld/pld_privacy_accountant.py +++ b/python/dp_accounting/pld/pld_privacy_accountant.py @@ -57,18 +57,24 @@ def _maybe_compose(self, event: dp_event.DpEvent, count: int, return None elif isinstance(event, dp_event.GaussianDpEvent): if do_compose: - gaussian_pld = PLD.from_gaussian_mechanism( - standard_deviation=event.noise_multiplier / math.sqrt(count), - value_discretization_interval=self._value_discretization_interval) - self._pld = self._pld.compose(gaussian_pld) + if event.noise_multiplier == 0: + self._contains_non_dp_event = True + else: + gaussian_pld = PLD.from_gaussian_mechanism( + standard_deviation=event.noise_multiplier / math.sqrt(count), + value_discretization_interval=self._value_discretization_interval) + self._pld = self._pld.compose(gaussian_pld) return None elif isinstance(event, dp_event.LaplaceDpEvent): if do_compose: - laplace_pld = PLD.from_laplace_mechanism( - parameter=event.noise_multiplier, - value_discretization_interval=self._value_discretization_interval - ).self_compose(count) - self._pld = self._pld.compose(laplace_pld) + if event.noise_multiplier == 0: + self._contains_non_dp_event = True + else: + laplace_pld = PLD.from_laplace_mechanism( + parameter=event.noise_multiplier, + value_discretization_interval=self._value_discretization_interval + ).self_compose(count) + self._pld = self._pld.compose(laplace_pld) return None elif isinstance(event, dp_event.PoissonSampledDpEvent): if self.neighboring_relation != NeighborRel.ADD_OR_REMOVE_ONE: @@ -79,19 +85,31 @@ def _maybe_compose(self, event: dp_event.DpEvent, count: int, invalid_event=event, error_message=error_msg) if isinstance(event.event, dp_event.GaussianDpEvent): if do_compose: - subsampled_gaussian_pld = PLD.from_gaussian_mechanism( - standard_deviation=event.event.noise_multiplier, - value_discretization_interval=self._value_discretization_interval, - sampling_prob=event.sampling_probability).self_compose(count) - self._pld = self._pld.compose(subsampled_gaussian_pld) + if event.sampling_probability == 0: + pass + elif event.event.noise_multiplier == 0: + self._contains_non_dp_event = True + else: + subsampled_gaussian_pld = PLD.from_gaussian_mechanism( + standard_deviation=event.event.noise_multiplier, + value_discretization_interval=self + ._value_discretization_interval, + sampling_prob=event.sampling_probability).self_compose(count) + self._pld = self._pld.compose(subsampled_gaussian_pld) return None elif isinstance(event.event, dp_event.LaplaceDpEvent): if do_compose: - subsampled_laplace_pld = PLD.from_laplace_mechanism( - parameter=event.event.noise_multiplier, - value_discretization_interval=self._value_discretization_interval, - sampling_prob=event.sampling_probability).self_compose(count) - self._pld = self._pld.compose(subsampled_laplace_pld) + if event.sampling_probability == 0: + pass + elif event.event.noise_multiplier == 0: + self._contains_non_dp_event = True + else: + subsampled_laplace_pld = PLD.from_laplace_mechanism( + parameter=event.event.noise_multiplier, + value_discretization_interval=self + ._value_discretization_interval, + sampling_prob=event.sampling_probability).self_compose(count) + self._pld = self._pld.compose(subsampled_laplace_pld) return None else: return CompositionErrorDetails( diff --git a/python/dp_accounting/pld/pld_privacy_accountant_test.py b/python/dp_accounting/pld/pld_privacy_accountant_test.py index 784f02bd..9cee09a3 100644 --- a/python/dp_accounting/pld/pld_privacy_accountant_test.py +++ b/python/dp_accounting/pld/pld_privacy_accountant_test.py @@ -51,6 +51,28 @@ def test_non_positive_composition_value_error(self, count): with self.assertRaises(ValueError): accountant.compose(event, count) + @parameterized.parameters( + dp_event.GaussianDpEvent(0), + dp_event.LaplaceDpEvent(0), + dp_event.PoissonSampledDpEvent(0.1, dp_event.GaussianDpEvent(0)), + dp_event.PoissonSampledDpEvent(0.1, dp_event.LaplaceDpEvent(0))) + def test_additive_noise_mechanisms_with_zero_noise_multiplier(self, event): + accountant = pld_privacy_accountant.PLDAccountant() + accountant.compose(event) + self.assertEqual(accountant.get_delta(1.0), 1) + self.assertEqual(accountant.get_epsilon(0.01), math.inf) + + @parameterized.parameters( + dp_event.PoissonSampledDpEvent(0, dp_event.GaussianDpEvent(1)), + dp_event.PoissonSampledDpEvent(0, dp_event.LaplaceDpEvent(1)), + dp_event.PoissonSampledDpEvent(0, dp_event.GaussianDpEvent(0)), + dp_event.PoissonSampledDpEvent(0, dp_event.LaplaceDpEvent(0))) + def test_poisson_subsampling_with_zero_probability(self, event): + accountant = pld_privacy_accountant.PLDAccountant() + accountant.compose(event) + self.assertEqual(accountant.get_delta(0), 0) + self.assertEqual(accountant.get_epsilon(0), 0) + def test_gaussian_basic(self): gaussian_event = dp_event.GaussianDpEvent(noise_multiplier=math.sqrt(3)) accountant = pld_privacy_accountant.PLDAccountant() diff --git a/python/dp_accounting/rdp/rdp_privacy_accountant.py b/python/dp_accounting/rdp/rdp_privacy_accountant.py index f1825fbb..16335fe3 100644 --- a/python/dp_accounting/rdp/rdp_privacy_accountant.py +++ b/python/dp_accounting/rdp/rdp_privacy_accountant.py @@ -347,12 +347,12 @@ def _compute_rdp_poisson_subsampled_gaussian( """ def compute_one_order(q, alpha): - if np.isinf(alpha) or noise_multiplier == 0: - return np.inf - if q == 0: return 0 + if np.isinf(alpha) or noise_multiplier == 0: + return np.inf + if q == 1.: return alpha / (2 * noise_multiplier**2) diff --git a/python/dp_accounting/rdp/rdp_privacy_accountant_test.py b/python/dp_accounting/rdp/rdp_privacy_accountant_test.py index b6291b16..1e60ea8d 100644 --- a/python/dp_accounting/rdp/rdp_privacy_accountant_test.py +++ b/python/dp_accounting/rdp/rdp_privacy_accountant_test.py @@ -171,10 +171,12 @@ def test_rdp_composition(self): self.assertAlmostEqual(rdp_with_heterogeneous_compose, base_rdp + base_rdp_2) - def test_zero_poisson_sample(self): + @parameterized.parameters( + dp_event.PoissonSampledDpEvent(0, dp_event.GaussianDpEvent(1.0)), + dp_event.PoissonSampledDpEvent(0, dp_event.GaussianDpEvent(0.0))) + def test_zero_poisson_sample(self, event): accountant = rdp_privacy_accountant.RdpAccountant([3.14159]) - accountant.compose( - dp_event.PoissonSampledDpEvent(0, dp_event.GaussianDpEvent(1.0))) + accountant.compose(event) self.assertEqual(accountant.get_epsilon(1e-10), 0) self.assertEqual(accountant.get_delta(1e-10), 0)