blob: 4d8bb9e7a5ac1673594c71b44b9d05416dcc7538 [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 LaplaceNoiseQuantileTest {
private static final double TOLERANCE = 1E-7;
private static final LaplaceNoise NOISE = new LaplaceNoise();
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_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 = {
-5.6567801, -3.5608768, -0.63092975, 0.0, 0.63092975, 3.5608768, 5.6567801
};
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= */ null);
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 = {
802.36064, 804.45655, 807.38649, 808.01742, 808.64835, 811.57830, 813.67420
};
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,
/* delta= */ null);
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 = {
-813.67420, -811.57830, -808.64835, -808.01742, -807.38649, -804.45655, -802.36064
};
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,
/* delta= */ null);
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 = {
-16.970340, -10.682630, -1.8927893, 0.0, 1.8927893, 10.682630, 16.970340
};
for (int i = 0; i < RANKS.length; i++) {
double actualQuantile =
NOISE.computeQuantile(
RANKS[i],
DEFAULT_X,
/* l0Sensitivity= */ 3,
DEFAULT_L_INF_SENSITIVITY,
DEFAULT_EPSILON,
/* delta= */ null);
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 = {
-4.1011656, -2.5816357, -0.45742407, 0.0, 0.45742407, 2.5816357, 4.1011656
};
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,
/* delta= */ null);
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 = {
-621.46081, -391.20230, -69.314718, 0.0, 69.314718, 391.20230, 621.46081
};
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,
/* delta= */ null);
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 = {
-2.8283900, -1.7804384, -0.31546488, 0.0, 0.31546488, 1.7804384, 2.8283900
};
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,
/* delta= */ null);
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,
/* delta= */ null));
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,
/* delta= */ null));
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),
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
assertThat(e).hasMessageThat().startsWith("epsilon must be");
}
@Test
public void computeQuantile_deltaNonnul_throwsException() {
Exception e =
assertThrows(
IllegalArgumentException.class,
() ->
NOISE.computeQuantile(
DEFAULT_RANK,
DEFAULT_X,
DEFAULT_L_0_SENSITIVITY,
DEFAULT_L_INF_SENSITIVITY,
/* epsilon= */ DEFAULT_EPSILON,
/* delta= */ 0.1));
assertThat(e).hasMessageThat().startsWith("delta should not be set");
}
@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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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,
/* delta= */ null));
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;
}
}