blob: de919fef727aa43a3f5876c06a84a50bb9cd8e83 [file] [log] [blame]
//
// Copyright 2020 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.
//
package com.google.privacy.differentialprivacy;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.lang.Double.NaN;
import static java.lang.Math.max;
import static org.junit.Assert.assertThrows;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class GaussianNoiseQuantileTest {
// Sigma computation is accurate to the 3 most significant digits, TOLERANCE is set accordingly.
private static final double TOLERANCE = 1E-3;
private static final GaussianNoise NOISE = new GaussianNoise();
private static final double LN_3 = Math.log(3);
private static final double DEFAULT_X = 0.0;
private static final double DEFAULT_EPSILON = LN_3;
private static final double DEFAULT_DELTA = 0.00001;
private static final double DEFAULT_RANK = 1E-5;
private static final int DEFAULT_L_0_SENSITIVITY = 1;
private static final double DEFAULT_L_INF_SENSITIVITY = 1.0;
private static final double[] RANKS = {0.001, 0.01, 0.25, 0.5, 0.75, 0.99, 0.999};
@Test
public void computeQuantile_defaultParameters_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-10.583002, -7.9669561, -2.3098997, 0.0, 2.3098997, 7.9669561, 10.583002
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_positiveX_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
797.43442, 800.05047, 805.70752, 808.01742, 810.32732, 815.98438, 818.60043
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
/* x= */ 808.017424,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_negativeX_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-818.60043, -815.98438, -810.32732, -808.01742, -805.70752, -800.05047, -797.43442
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
/* x= */ -808.017424,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_differentL0Sensitivity_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-18.330298, -13.799173, -4.0008636, 0.0, 4.0008636, 13.799173, 18.330298
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
/* l0Sensitivity= */ 3,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_differentLInfSensitivity_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-7.6726767, -5.7760432, -1.6746773, 0.0, 1.6746773, 5.7760432, 7.6726767
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
/* lInfSensitivity= */ 0.725,
DEFAULT_EPSILON,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_smallEpsilon_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-753.35364, -567.12973, -164.43078, 0.0, 164.43078, 567.12973, 753.35364
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ 0.01,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_largeEpsilon_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-5.6644981, -4.2642727, -1.2363620, 0.0, 1.2363620, 4.2642727, 5.6644981
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
2 * DEFAULT_EPSILON,
DEFAULT_DELTA);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_smallDelta_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-16.568238, -12.472682, -3.6162674, 0.0, 3.6162674, 12.472682, 16.568238
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ 1E-10);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_largeDelta_returnsExactResult() {
// Exact quantiles rounded to the 8 most significant digits.
double[] expectedQuantiles = {
-7.3530635, -5.5354362, -1.6049169, 0.0, 1.6049169, 5.5354362, 7.3530635
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ 1E-3);
assertWithMessage(
"Quantile of rank %s is not precise: actual = %s, expected = %s",
RANKS[i], actualQuantile, expectedQuantiles[i])
.that(approxEqual(actualQuantile, expectedQuantiles[i]))
.isTrue();
}
}
@Test
public void computeQuantile_epsilonNegative_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ -0.1,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("epsilon must be");
}
@Test
public void computeQuantile_epsilonZero_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ 0.0,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("epsilon must be");
}
@Test
public void computeQuantile_epsilonTooSmall_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ 1.0 / (1L << 51),
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("epsilon must be");
}
@Test
public void computeQuantile_epsilonPosInfinity_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ Double.POSITIVE_INFINITY,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("epsilon must be");
}
@Test
public void computeQuantile_epsilonNan_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ Double.NaN,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("epsilon must be");
}
@Test
public void computeQuantile_deltaNull_throwsException() {
assertThrows(
NullPointerException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ null));
}
@Test
public void computeQuantile_deltaNegative_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ -0.1));
assertThat(e).hasMessageThat().startsWith("delta must be");
}
@Test
public void computeQuantile_deltaZero_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ 0.0));
assertThat(e).hasMessageThat().startsWith("delta must be");
}
@Test
public void computeQuantile_deltaOne_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ 1.0));
assertThat(e).hasMessageThat().startsWith("delta must be");
}
@Test
public void computeQuantile_deltaGreaterThanOne_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ 2.0));
assertThat(e).hasMessageThat().startsWith("delta must be");
}
@Test
public void computeQuantile_deltaNaN_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ NaN));
assertThat(e).hasMessageThat().startsWith("delta must be");
}
@Test
public void computeQuantile_rankNegative_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
/* rank= */ -1.0,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("rank must be");
}
@Test
public void computeQuantile_rankZero_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
/* rank= */ 0.0,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("rank must be");
}
@Test
public void computeQuantile_rankOne_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
/* rank= */ 1.0,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("rank must be");
}
@Test
public void computeQuantile_rankGreaterThanOne_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
/* rank= */ 2.0,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("rank must be");
}
@Test
public void computeQuantile_rankNaN_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
/* rank= */ NaN,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("rank must be");
}
@Test
public void computeQuantile_lInfSensitivityNan_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
/* lInfSensitivity= */ Double.NaN,
/* epsilon= */ 1.0,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("lInfSensitivity must be");
}
@Test
public void computeQuantile_lInfSensitivityNegative_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
/* lInfSensitivity= */ -1.0,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("lInfSensitivity must be");
}
@Test
public void computeQuantile_lInfSensitivityZero_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
/* lInfSensitivity= */ 0.0,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("lInfSensitivity must be");
}
@Test
public void computeQuantile_l0SensitivityNegative_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
/* l0Sensitivity= */ -1,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("l0Sensitivity must be");
}
@Test
public void computeQuantile_l0SensitivityZero_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
/* l0Sensitivity= */ 0,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
DEFAULT_DELTA));
assertThat(e).hasMessageThat().startsWith("l0Sensitivity must be");
}
private static boolean approxEqual(double a, double b) {
double maxMagnitude = max(Math.abs(a), Math.abs(b));
return Math.abs(a - b) <= TOLERANCE * maxMagnitude;
}
}