blob: 63bef76891f0c20493d2a5883475731c2d7b0623 [file] [log] [blame]
//
// Copyright 2019 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.
//
#ifndef DIFFERENTIAL_PRIVACY_ALGORITHMS_BOUNDED_ALGORITHM_H_
#define DIFFERENTIAL_PRIVACY_ALGORITHMS_BOUNDED_ALGORITHM_H_
#include <memory>
#include <type_traits>
#include "absl/status/status.h"
#include "base/statusor.h"
#include "algorithms/algorithm.h"
#include "algorithms/approx-bounds.h"
#include "base/canonical_errors.h"
namespace differential_privacy {
// If the user does not manually specify an ApproxBounds object, we will use
// this fraction of the total epsilon to calculate them.
const double kDefaultBoundsBudgetFraction = 0.5;
// BoundedAlgorithmBuilder is used to build algorithms which need lower and
// upper input bounds to determine sensitivity and/or clamp inputs. It provides
// three ways to provide bounds for an algorithm built by this type of builder:
//
// 1. Manually set bounds using the SetLower and SetUpper functions.
// 2. Automatically determine bounds with manually set options. To do this,
// pass in a constructed ApproxBound into the SetApproxBounds function.
// 3. Automatically determine bounds with default options. If no manual bounds
// or ApproxBounds algorithm are passed in, then a default ApproxBounds
// may be constructed. Child builders can call BoundSettingSetup() upon
// Build to do this.
//
// Currently, all bounded algorithms use the Laplace mechanism.
template <typename T, class Algorithm, class Builder>
class BoundedAlgorithmBuilder : public AlgorithmBuilder<T, Algorithm, Builder> {
using AlgorithmBuilder =
differential_privacy::AlgorithmBuilder<T, Algorithm, Builder>;
public:
Builder& SetLower(T lower) {
lower_ = lower;
return *static_cast<Builder*>(this);
}
Builder& SetUpper(T upper) {
upper_ = upper;
return *static_cast<Builder*>(this);
}
// ClearBounds resets the builder. Erases bounds and bounding objects that
// were previously set.
Builder& ClearBounds() {
lower_.reset();
upper_.reset();
approx_bounds_ = nullptr;
return *static_cast<Builder*>(this);
}
// Setting ApproxBounds removes manually set bounds. If automatic bounds are
// desired, this field is optional.
Builder& SetApproxBounds(std::unique_ptr<ApproxBounds<T>> approx_bounds) {
ClearBounds();
approx_bounds_ = std::move(approx_bounds);
return *static_cast<Builder*>(this);
}
protected:
// This method needs to be overwritten by childs to build bounded algorithms.
virtual base::StatusOr<std::unique_ptr<Algorithm>>
BuildBoundedAlgorithm() = 0;
// Returns whether bounds have been set for this builder.
inline bool BoundsAreSet() {
return lower_.has_value() && upper_.has_value();
}
absl::Status BoundsSetup() {
// If either bound is not set and we do not have an ApproxBounds,
// construct the default one.
if (!BoundsAreSet() && !approx_bounds_) {
double bounds_epsilon =
AlgorithmBuilder::GetEpsilon().value() * kDefaultBoundsBudgetFraction;
remaining_epsilon_ =
AlgorithmBuilder::GetEpsilon().value() - bounds_epsilon;
auto mech_builder = AlgorithmBuilder::GetMechanismBuilderClone();
ASSIGN_OR_RETURN(approx_bounds_,
typename ApproxBounds<T>::Builder()
.SetEpsilon(bounds_epsilon)
.SetLaplaceMechanism(std::move(mech_builder))
.Build());
}
// Check if bounds are finite when a floating point type is used and bounds
// have been set manually.
if (BoundsAreSet() && std::is_floating_point<T>::value) {
if (!std::isfinite(static_cast<double>(lower_.value()))) {
return absl::InvalidArgumentError(absl::StrCat(
"Lower bound has to be finite but is ", lower_.value()));
}
if (!std::isfinite(static_cast<double>(upper_.value()))) {
return absl::InvalidArgumentError(absl::StrCat(
"Upper bound has to be finite but is ", upper_.value()));
}
}
return absl::OkStatus();
}
// Returns the epsilon allotted for calculating the aggregation. If bounds
// are set manually, or an ApproximateBounds object has been manually
// specified, this will be the full epsilon. If an ApproxBounds was created
// automatically this will be the full epsilon - epsilon spent on that
// ApproxBounds. If called before BoundsSetup this will always return the full
// epsilon.
absl::optional<double> GetRemainingEpsilon() {
if (remaining_epsilon_.has_value()) {
return remaining_epsilon_;
}
return AlgorithmBuilder::GetEpsilon();
}
std::unique_ptr<ApproxBounds<T>> MoveApproxBoundsPointer() {
return std::move(approx_bounds_);
}
absl::optional<T> GetLower() const { return lower_; }
absl::optional<T> GetUpper() const { return upper_; }
ApproxBounds<T>* GetApproxBounds() const { return approx_bounds_.get(); }
private:
// Bounds are optional and do not need to be set. If they are not set,
// automatic bounds will be determined.
absl::optional<T> lower_;
absl::optional<T> upper_;
// Epsilon left over after creating an ApproxBounds.
absl::optional<double> remaining_epsilon_;
// Used to automatically determine approximate mimimum and maximum to become
// lower and upper bounds, respectively.
std::unique_ptr<ApproxBounds<T>> approx_bounds_;
absl::Status CheckBoundsOrder() {
if (BoundsAreSet() && lower_.value() > upper_.value()) {
return absl::InvalidArgumentError(
"Lower bound cannot be greater than upper bound.");
}
return absl::OkStatus();
}
// Common initialization and checks for building bounded algorithms.
base::StatusOr<std::unique_ptr<Algorithm>> BuildAlgorithm() final {
RETURN_IF_ERROR(CheckBoundsOrder());
return BuildBoundedAlgorithm();
}
};
} // namespace differential_privacy
#endif // DIFFERENTIAL_PRIVACY_ALGORITHMS_BOUNDED_ALGORITHM_H_