blob: 2d13b9892b7d30e3c7328f2552cb077f1ba2a94c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package privacy
import (
"config"
"fmt"
"math"
)
type ErrorCalculator struct {
ParamsCalc PrivacyEncodingParamsCalculator
}
// Public factory method for creating an ErrorCalculator given a
// PrivacyEncodingParamsCalculator.
func NewErrorCalculator(paramsCalc PrivacyEncodingParamsCalculator) *ErrorCalculator {
return &ErrorCalculator{paramsCalc}
}
// Public factory method for creating an ErrorCalculator given the file path
// of the PrivacyEncodingParams.
func NewErrorCalculatorFromPrivacyParams(privacyParamsPath string) (*ErrorCalculator, error) {
paramsCalculator, err := NewPrivacyEncodingParamsCalculator(privacyParamsPath)
if err != nil {
return nil, err
}
errorCalculator := NewErrorCalculator(*paramsCalculator)
if err != nil {
return nil, err
}
return errorCalculator, nil
}
// Given a |metric|, |report|, and |params|, estimates the report row error.
//
// TODO(jaredweinstein): implement estimates for other report types.
func (e *ErrorCalculator) Estimate(metric *config.MetricDefinition, report *config.ReportDefinition, epsilon float64, population uint64) (estimate float64, err error) {
reportType := report.GetReportType()
sparsity, err := getSparsityForReport(metric, report)
if err != nil {
return -1, err
}
populationConstant := e.ParamsCalc.Constants.population
privacyEncodingParams, err := e.ParamsCalc.GetPrivacyEncodingParams(epsilon, populationConstant, sparsity)
if err != nil {
return -1, err
}
var errorEstimate float64
switch reportType {
case config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS:
fallthrough // Calculate RMSE Error for each bucket.
case config.ReportDefinition_UNIQUE_DEVICE_COUNTS:
errorEstimate = SingleContributionRapporRMSE(population, privacyEncodingParams)
case config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS:
// (24 * population) pseudo-users to account for hourly values.
errorEstimate = SingleContributionRapporRMSE(24*population, privacyEncodingParams)
case config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS:
errorEstimate = MultipleContributionRapporRMSE(population, privacyEncodingParams, report.MaxCount)
case config.ReportDefinition_FLEETWIDE_HISTOGRAMS:
errorEstimate = MultipleContributionRapporRMSE(population, privacyEncodingParams, 1)
default:
reportType := config.ReportDefinition_ReportType_name[int32(reportType)]
return -1, fmt.Errorf("Error estimation is not supported for reports of type %s", reportType)
}
return errorEstimate, nil
}
// Compute the 2D-RAPPOR RMSE for reports with single contributions.
//
// See Proposition 1 and 2 of go/histogram-aggregation-privacy-guarantee.
func SingleContributionRapporRMSE(population uint64, privacyParams PrivacyEncodingParams) float64 {
probBitFlip := privacyParams.ProbBitFlip
return math.Sqrt(float64(population)*probBitFlip*(1-probBitFlip)) / (1 - 2*probBitFlip)
}
// Compute 2D-RAPPOR RMSE for reports with multiple or real-number contributions.
//
// See Proposition 1, 2, and 3 of go/histogram-aggregation-privacy-guarantee.
func MultipleContributionRapporRMSE(population uint64, privacyParams PrivacyEncodingParams, maxUserContribution uint64) float64 {
var n = float64(population)
var p = privacyParams.ProbBitFlip
var m = float64(maxUserContribution)
var r = float64(privacyParams.NumIndexPoints)
var estimate = float64(n) * p * (1 - p) / math.Pow((1-2*p), 2)
estimate = estimate * (2*math.Pow(r, 3) + 3*math.Pow(r, 2) + r) / 6
estimate = estimate + (n / 4)
estimate = m / r * math.Sqrt(estimate)
return estimate
}