From 615242e95c4fa7822ecefd35fe77ff26daebbc48 Mon Sep 17 00:00:00 2001 From: Albert Hsu <45705038+iverson52000@users.noreply.github.com> Date: Mon, 12 Jun 2023 12:05:24 -0700 Subject: [PATCH] Add NONE DirectNoiseMechanism. (#1040) * Add NONE directNoiseMechanism for testing purpose * Change direct measurement noise mechanism name to `directNoiseMechanism` to differentiate from MPC `noiseMechanism` * Some improvements to GaussianNoiser --- .../common/InProcessEdpSimulator.kt | 6 +-- .../loadtest/dataprovider/EdpSimulator.kt | 41 ++++++++++++------- .../dataprovider/EdpSimulatorFlags.kt | 4 +- .../dataprovider/EdpSimulatorRunner.kt | 2 +- .../loadtest/dataprovider/GaussianNoiser.kt | 18 +++++--- .../loadtest/dataprovider/EdpSimulatorTest.kt | 14 +++---- 6 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/main/kotlin/org/wfanet/measurement/integration/common/InProcessEdpSimulator.kt b/src/main/kotlin/org/wfanet/measurement/integration/common/InProcessEdpSimulator.kt index c658170c249..e156750926f 100644 --- a/src/main/kotlin/org/wfanet/measurement/integration/common/InProcessEdpSimulator.kt +++ b/src/main/kotlin/org/wfanet/measurement/integration/common/InProcessEdpSimulator.kt @@ -42,9 +42,9 @@ import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.InMemory import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.PrivacyBucketFilter import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.PrivacyBudgetManager import org.wfanet.measurement.eventdataprovider.privacybudgetmanagement.testing.TestPrivacyBucketMapper +import org.wfanet.measurement.loadtest.dataprovider.DirectNoiseMechanism import org.wfanet.measurement.loadtest.dataprovider.EdpData import org.wfanet.measurement.loadtest.dataprovider.EdpSimulator -import org.wfanet.measurement.loadtest.dataprovider.NoiseMechanism import org.wfanet.measurement.loadtest.dataprovider.RandomEventQuery import org.wfanet.measurement.loadtest.dataprovider.SketchGenerationParams import org.wfanet.measurement.loadtest.storage.SketchStore @@ -102,7 +102,7 @@ class InProcessEdpSimulator( ), trustedCertificates = trustedCertificates, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) private lateinit var edpJob: Job @@ -130,6 +130,6 @@ class InProcessEdpSimulator( private val logger: Logger = Logger.getLogger(this::class.java.name) private const val RANDOM_SEED: Long = 1 private val random = Random(RANDOM_SEED) - private val NOISE_MECHANISM = NoiseMechanism.LAPLACE + private val DIRECT_NOISE_MECHANISM = DirectNoiseMechanism.LAPLACE } } diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt index 9ae62843c99..e0e88a94fa5 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import org.apache.commons.math3.distribution.ConstantRealDistribution import org.wfanet.anysketch.AnySketch import org.wfanet.anysketch.Sketch import org.wfanet.anysketch.SketchConfig @@ -139,12 +140,14 @@ data class EdpData( ) /** - * Mechanism for generating noise for direct measurements. + * Noise mechanism for generating publisher noise for direct measurements. * - * TODO(@iverson52000): Move this to public API if EDP needs to report back the noise mechanism for - * PBM tracking. + * TODO(@iverson52000): Move this to public API if EDP needs to report back the direct noise + * mechanism for PBM tracking. NONE mechanism is testing only and should not move to public API. */ -enum class NoiseMechanism { +enum class DirectNoiseMechanism { + /** NONE mechanism is testing only. */ + NONE, LAPLACE, GAUSSIAN, } @@ -166,7 +169,7 @@ class EdpSimulator( private val privacyBudgetManager: PrivacyBudgetManager, private val trustedCertificates: Map, private val random: Random, - private val noiseMechanism: NoiseMechanism + private val directNoiseMechanism: DirectNoiseMechanism ) { /** A sequence of operations done in the simulator. */ @@ -748,6 +751,20 @@ class EdpSimulator( fulfillDirectMeasurement(requisition, requisitionSpec, measurementSpec, measurementResult) } + private fun getPublisherNoiser( + privacyParams: DifferentialPrivacyParams, + directNoiseMechanism: DirectNoiseMechanism, + random: Random + ): AbstractNoiser = + when (directNoiseMechanism) { + DirectNoiseMechanism.NONE -> + object : AbstractNoiser() { + override val distribution = ConstantRealDistribution(0.0) + } + DirectNoiseMechanism.LAPLACE -> LaplaceNoiser(privacyParams, random) + DirectNoiseMechanism.GAUSSIAN -> GaussianNoiser(privacyParams, random) + } + /** * Add publisher noise to calculated direct reach. * @@ -760,10 +777,7 @@ class EdpSimulator( privacyParams: DifferentialPrivacyParams ): Long { val reachNoiser: AbstractNoiser = - when (noiseMechanism) { - NoiseMechanism.LAPLACE -> LaplaceNoiser(privacyParams, random) - NoiseMechanism.GAUSSIAN -> GaussianNoiser(privacyParams, random) - } + getPublisherNoiser(privacyParams, directNoiseMechanism, random) return reachValue + reachNoiser.sample().toInt() } @@ -782,10 +796,7 @@ class EdpSimulator( privacyParams: DifferentialPrivacyParams, ): Map { val frequencyNoiser: AbstractNoiser = - when (noiseMechanism) { - NoiseMechanism.LAPLACE -> LaplaceNoiser(privacyParams, random) - NoiseMechanism.GAUSSIAN -> GaussianNoiser(privacyParams, random) - } + getPublisherNoiser(privacyParams, directNoiseMechanism, random) return frequencyMap.mapValues { (_, percentage) -> (percentage * reachValue.toDouble() + frequencyNoiser.sample()) / reachValue.toDouble() @@ -809,7 +820,7 @@ class EdpSimulator( val sampledReachValue = calculateDirectReach(vidList) val frequencyMap = calculateDirectFrequency(vidList, sampledReachValue) - logger.info("Adding $noiseMechanism publisher noise to direct reach and frequency...") + logger.info("Adding $directNoiseMechanism publisher noise to direct reach and frequency...") val sampledNoisedReachValue = addReachPublisherNoise( sampledReachValue, @@ -836,7 +847,7 @@ class EdpSimulator( } MeasurementSpec.MeasurementTypeCase.REACH -> { val sampledReachValue = calculateDirectReach(vidList) - logger.info("Adding $noiseMechanism publisher noise to direct reach...") + logger.info("Adding $directNoiseMechanism publisher noise to direct reach...") val sampledNoisedReachValue = addReachPublisherNoise(sampledReachValue, measurementSpec.reach.privacyParams) val scaledNoisedReachValue = diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorFlags.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorFlags.kt index 3b52091a800..ea86d0e3b53 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorFlags.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorFlags.kt @@ -101,10 +101,10 @@ class EdpSimulatorFlags { private set @CommandLine.Option( - names = ["--noise-mechanism"], + names = ["--direct-noise-mechanism"], description = ["Differential privacy noise mechanism for direct measurements"], defaultValue = "LAPLACE", ) - lateinit var noiseMechanism: NoiseMechanism + lateinit var directNoiseMechanism: DirectNoiseMechanism private set } diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorRunner.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorRunner.kt index 989611f78e4..f4c8f1677ba 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorRunner.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorRunner.kt @@ -104,7 +104,7 @@ abstract class EdpSimulatorRunner : Runnable { createNoOpPrivacyBudgetManager(), clientCerts.trustedCertificates, random, - flags.noiseMechanism + flags.directNoiseMechanism ) runBlocking { edpSimulator.createEventGroup() diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/GaussianNoiser.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/GaussianNoiser.kt index 415ca1ae663..6f7f93398c7 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/GaussianNoiser.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/GaussianNoiser.kt @@ -37,11 +37,10 @@ class GaussianNoiser(privacyParams: DifferentialPrivacyParams, random: Random) : * N(0,1) */ private fun solveDelta(epsilon: Double, sigma: Double): Double { - val normalDistribution = NormalDistribution(0.0, 1.0) val x = sigma * epsilon + 1 / (2 * sigma) - return (1 - normalDistribution.cumulativeProbability(x - 1 / sigma)) - - exp(epsilon) * (1 - normalDistribution.cumulativeProbability(x)) + return (1 - standardNormalDistribution.cumulativeProbability(x - 1 / sigma)) - + exp(epsilon) * (1 - standardNormalDistribution.cumulativeProbability(x)) } /** * Assuming sensitivity = 1, solve std given epsilon and delta. @@ -51,15 +50,17 @@ class GaussianNoiser(privacyParams: DifferentialPrivacyParams, random: Random) : * find an upper bound of sigma and then apply the bisection search. */ private fun solveSigma(epsilon: Double, delta: Double, startingSigma: Double = 1e-3): Double { - var sigma = startingSigma - require(solveDelta(epsilon, sigma) >= delta) { "startingSigma $startingSigma is too large" } + require(solveDelta(epsilon, startingSigma) >= delta) { + "startingSigma $startingSigma is too large" + } + var sigma = startingSigma while (solveDelta(epsilon, sigma) > delta) { sigma *= 2 } return BisectionSolver() - .solve(10000, { x: Double -> solveDelta(epsilon, x) - delta }, sigma / 2, sigma) + .solve(MAX_EVAL, { x: Double -> solveDelta(epsilon, x) - delta }, sigma / 2, sigma) } private fun getNormalDistribution( epsilon: Double, @@ -70,4 +71,9 @@ class GaussianNoiser(privacyParams: DifferentialPrivacyParams, random: Random) : return NormalDistribution(RandomGeneratorFactory.createRandomGenerator(random), 0.0, sigma) } + + companion object { + private val standardNormalDistribution = NormalDistribution(0.0, 1.0) + private const val MAX_EVAL = 10000 + } } diff --git a/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt b/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt index 444d31f2d4d..6c38980d5bd 100644 --- a/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt @@ -237,7 +237,7 @@ private val REQUISITION_ONE_SPEC = requisitionSpec { } private const val DUCHY_ID = "worker1" private const val RANDOM_SEED: Long = 1 -private val NOISE_MECHANISM = NoiseMechanism.LAPLACE +private val DIRECT_NOISE_MECHANISM = DirectNoiseMechanism.LAPLACE @RunWith(JUnit4::class) class EdpSimulatorTest { @@ -411,7 +411,7 @@ class EdpSimulatorTest { privacyBudgetManager, TRUSTED_CERTIFICATES, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) edpSimulator.createEventGroup() edpSimulator.executeRequisitionFulfillingWorkflow() @@ -504,7 +504,7 @@ class EdpSimulatorTest { privacyBudgetManager, TRUSTED_CERTIFICATES, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) val requisition = REQUISITION_ONE.copy { @@ -568,7 +568,7 @@ class EdpSimulatorTest { privacyBudgetManager, TRUSTED_CERTIFICATES, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) val vidSamplingIntervalWidth = 1f @@ -643,7 +643,7 @@ class EdpSimulatorTest { privacyBudgetManager, TRUSTED_CERTIFICATES, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) val vidSamplingIntervalWidth = 1f @@ -712,7 +712,7 @@ class EdpSimulatorTest { privacyBudgetManager, TRUSTED_CERTIFICATES, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) val vidSamplingIntervalWidth = 0.1f @@ -787,7 +787,7 @@ class EdpSimulatorTest { privacyBudgetManager, TRUSTED_CERTIFICATES, random, - NOISE_MECHANISM + DIRECT_NOISE_MECHANISM ) val vidSamplingIntervalWidth = 0.1f