blob: 5b9ca44df1fbf241d81c522f1fa2b04b21147c7f [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_PRIVACY_BUDGET_MESA_DISTRIBUTION_H_
#define CHROME_BROWSER_PRIVACY_BUDGET_MESA_DISTRIBUTION_H_
#include <random>
#include <set>
#include <type_traits>
#include "base/containers/contains.h"
// Generates a set of integers drawn from a mesa shaped probability distribution
// with replacement.
//
// The PDF is:
//
// ⎧ 0 ... if x < 0
// ⎪
// ⎪ λ ... if 0 <= x < T
// P(x) = ⎨
// ⎪ λ (1 - γ)⁽ˣ⁻ᵀ⁾ ... otherwise
// ⎪
// ⎩
// where ...
//
// T = Value at which the PDF switches from a uniform to a geometric
// distribution. Referred to in code as the `pivot_point`.
//
// τ = Ratio of probability between linear region of the PDF. I.e. if τ = 0.9,
// then 90% of the probability space is in the linear region. The ratio is
// referred to in code as `dist_ratio`.
//
// τ
// λ = ───
// T
//
// λ τ
// γ = ───── = ───────────
// 1 - τ T * (1 - τ)
//
// In otherwords, the PDF is uniform up to T with a probability of λ, and then
// switches to a geometric distribution with parameter λ that extends to
// infinity.
//
// It looks like this in the form of a graph which should make a little bit more
// sense.
//
// P(x) ▲
// │
// probability λ│┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┬,
// density │ uniform ┊ L geometric
// │ distribution ┊ "._ distribution
// │ ┊ `--..______
// └────────────────┴──────────────────▶ x
// 0 T
//
// Why this odd combination of disjoint probability distributions?
//
// Such a distribution is useful when you want to select some set of elements
// uniformly up to a threshold, but want to allow for a tail distribution that
// extends arbitrarily past that range.
//
// The τ parameter establishes the balance between the linear region and the
// geometric region, while T establishes the scale. Typically we set τ to
// something close to 0.9 or so such that 0.1 of the probability space is
// reserved for the long tail.
//
// Parameters:
// pivot_point: T as described above. Arbitrary range.
// dist_ratio : τ as described above. Must be in (0,1).
template <typename ResultType,
std::enable_if_t<std::is_integral<ResultType>::value, int> = 0>
class MesaDistribution {
public:
MesaDistribution(ResultType pivot_point, double dist_ratio)
: pivot_point_(pivot_point),
uniform_distribution_(0, std::ceil(pivot_point / dist_ratio)),
geometric_distribution_(dist_ratio /
(pivot_point * (1.0l - dist_ratio))) {}
~MesaDistribution() = default;
// Draws a single value from the distribution.
//
// `Generator` must satisfy `UniformRandomBitGenerator`.
// https://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator
template <class Generator>
ResultType Get(Generator& g) {
ResultType v = uniform_distribution_(g);
if (v >= pivot_point_)
return pivot_point_ + geometric_distribution_(g);
return v;
}
// RandomNumberDistribution.
// https://en.cppreference.com/w/cpp/named_req/RandomNumberDistribution
//
// `Generator` must satisfy `UniformRandomBitGenerator`.
// https://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator
template <class Generator>
ResultType operator()(Generator g) {
return Get(g);
}
ResultType pivot_point() const { return pivot_point_; }
private:
const ResultType pivot_point_;
std::uniform_int_distribution<ResultType> uniform_distribution_;
std::geometric_distribution<ResultType> geometric_distribution_;
};
#endif // CHROME_BROWSER_PRIVACY_BUDGET_MESA_DISTRIBUTION_H_