blob: e97d691501117831c1e1f56cb8b87a2972730794 [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 "algorithms/internal/gaussian-stddev-calculator.h"
#include <cmath>
#include <limits>
namespace differential_privacy {
namespace internal {
namespace {
// The relative accuracy at which to stop the binary search to find the tightest
// sigma such that Gaussian noise satisfies (epsilon, delta)-differential
// privacy given the sensitivities.
constexpr double kGaussianSigmaAccuracy = 1e-3;
// Cdf for the Gaussian distribution with stddev = 1.
double StandardGaussianCDF(double x) {
return std::erfc(-x / (std::sqrt(2.0))) / 2.0;
}
} // namespace
// Calculates the standard deviation by first using an exponential search (via
// CalculateBounds) and then doing a binary search until bounds are tight
// enough. The returned standard deviation might be slightly higher than the
// required standard deviation to be on the safe side.
double CalculateGaussianStddev(double epsilon, double delta,
double l2_sensitivity) {
BoundsForGaussianStddev bounds =
CalculateBoundsForGaussianStddev(epsilon, delta, l2_sensitivity);
while (bounds.upper - bounds.lower > kGaussianSigmaAccuracy * bounds.lower) {
const double middle = (bounds.lower + bounds.upper) / 2.0;
if (CalculateDeltaForGaussianStddev(epsilon, l2_sensitivity,
/* stddev= */ middle) > delta) {
bounds.lower = middle;
} else {
bounds.upper = middle;
}
}
return bounds.upper;
}
// This implementation uses Theorem 8 of https://arxiv.org/pdf/1805.06530v2.pdf
// to calculate the delta for a given standard deviation.
double CalculateDeltaForGaussianStddev(double epsilon, double l2_sensitivity,
double stddev) {
const double a = l2_sensitivity / (2 * stddev);
const double b = epsilon * stddev / l2_sensitivity;
const double c = std::exp(epsilon);
if (std::isnan(b)) {
// If either l2_sensitivity goes to 0 or e^epsilon goes to infinity,
// delta goes to 0.
return 0;
}
return StandardGaussianCDF(a - b) - c * StandardGaussianCDF(-a - b);
}
// Calculates upper and lower bounds for the standard deviation by using an
// exponential search.
BoundsForGaussianStddev CalculateBoundsForGaussianStddev(
double epsilon, double delta, double l2_sensitivity) {
BoundsForGaussianStddev bounds;
bounds.lower = std::numeric_limits<double>::min();
bounds.upper = l2_sensitivity;
while (CalculateDeltaForGaussianStddev(epsilon, l2_sensitivity,
/* stddev= */ bounds.upper) > delta) {
bounds.lower = bounds.upper;
bounds.upper = bounds.upper * 2.0;
}
return bounds;
}
} // namespace internal
} // namespace differential_privacy