• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.math;
18 
19 import static com.google.common.math.Quantiles.median;
20 import static com.google.common.math.Quantiles.percentiles;
21 import static com.google.common.math.Quantiles.quartiles;
22 import static com.google.common.truth.Truth.assertThat;
23 import static java.lang.Double.NEGATIVE_INFINITY;
24 import static java.lang.Double.NaN;
25 import static java.lang.Double.POSITIVE_INFINITY;
26 import static java.math.RoundingMode.CEILING;
27 import static java.math.RoundingMode.FLOOR;
28 import static java.math.RoundingMode.UNNECESSARY;
29 
30 import com.google.common.collect.ImmutableList;
31 import com.google.common.collect.ImmutableMap;
32 import com.google.common.collect.Ordering;
33 import com.google.common.math.Quantiles.ScaleAndIndexes;
34 import com.google.common.primitives.Doubles;
35 import com.google.common.primitives.Ints;
36 import com.google.common.primitives.Longs;
37 import com.google.common.truth.Correspondence;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.Random;
42 import junit.framework.TestCase;
43 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
44 
45 /**
46  * Tests for {@link Quantiles}.
47  *
48  * @author Pete Gillin
49  */
50 public class QuantilesTest extends TestCase {
51 
52   /*
53    * Since Quantiles provides a fluent-style API, each test covers a chain of methods resulting in
54    * the computation of one or more quantiles (or in an error) rather than individual methods. The
55    * tests are divided into three sections:
56    * 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10);
57    * 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10);
58    * 3. Tests on a mechanically generated dataset for chains starting with percentiles();
59    * 4. Tests of illegal usages of the API.
60    */
61 
62   /*
63    * Covering every combination would lead to an explosion in the number of tests. So we cover only:
64    * - median with compute taking a double-collection and with computeInPlace;
65    * - quartiles with index and with indexes taking int-varargs, and with compute taking a
66    *   double-collection and with computeInPlace;
67    * - scale with index and with indexes taking int-varargs, and with all overloads of compute
68    *   taking a double-collection and with computeInPlace;
69    * - scale with indexes taking integer-collection with compute taking a double-collection and with
70    *   computeInPlace;
71    * - (except that, for non-finite values, we don't do all combinations exhaustively);
72    * - percentiles with index and with indexes taking int-varargs, and with compute taking a
73    *   double-collection and with computeInPlace.
74    */
75 
76   private static final double ALLOWED_ERROR = 1.0e-10;
77 
78   /**
79    * A {@link Correspondence} which accepts finite values within {@link #ALLOWED_ERROR} of each
80    * other.
81    */
82   private static final Correspondence<Number, Number> FINITE_QUANTILE_CORRESPONDENCE =
83       Correspondence.tolerance(ALLOWED_ERROR);
84 
85   /**
86    * A {@link Correspondence} which accepts either finite values within {@link #ALLOWED_ERROR} of
87    * each other or identical non-finite values.
88    */
89   private static final Correspondence<Double, Double> QUANTILE_CORRESPONDENCE =
90       new Correspondence<Double, Double>() {
91 
92         @Override
93         public boolean compare(@NullableDecl Double actual, @NullableDecl Double expected) {
94           // Test for equality to allow non-finite values to match; otherwise, use the finite test.
95           return actual.equals(expected)
96               || FINITE_QUANTILE_CORRESPONDENCE.compare(actual, expected);
97         }
98 
99         @Override
100         public String toString() {
101           return "is identical to or " + FINITE_QUANTILE_CORRESPONDENCE;
102         }
103       };
104 
105   // 1. Tests on a hardcoded dataset for chains starting with median(), quartiles(), and scale(10):
106 
107   /** The squares of the 16 integers from 0 to 15, in an arbitrary order. */
108   private static final ImmutableList<Double> SIXTEEN_SQUARES_DOUBLES =
109       ImmutableList.of(
110           25.0, 100.0, 0.0, 144.0, 9.0, 121.0, 4.0, 225.0, 169.0, 64.0, 49.0, 16.0, 36.0, 1.0, 81.0,
111           196.0);
112 
113   private static final ImmutableList<Long> SIXTEEN_SQUARES_LONGS =
114       ImmutableList.of(
115           25L, 100L, 0L, 144L, 9L, 121L, 4L, 225L, 169L, 64L, 49L, 16L, 36L, 1L, 81L, 196L);
116   private static final ImmutableList<Integer> SIXTEEN_SQUARES_INTEGERS =
117       ImmutableList.of(25, 100, 0, 144, 9, 121, 4, 225, 169, 64, 49, 16, 36, 1, 81, 196);
118   private static final double SIXTEEN_SQUARES_MIN = 0.0;
119   private static final double SIXTEEN_SQUARES_DECILE_1 = 0.5 * (1.0 + 4.0);
120   private static final double SIXTEEN_SQUARES_QUARTILE_1 = 0.25 * 9.0 + 0.75 * 16.0;
121   private static final double SIXTEEN_SQUARES_MEDIAN = 0.5 * (49.0 + 64.0);
122   private static final double SIXTEEN_SQUARES_QUARTILE_3 = 0.75 * 121.0 + 0.25 * 144.0;
123   private static final double SIXTEEN_SQUARES_DECILE_8 = 144.0;
124   private static final double SIXTEEN_SQUARES_MAX = 225.0;
125 
testMedian_compute_doubleCollection()126   public void testMedian_compute_doubleCollection() {
127     assertThat(median().compute(SIXTEEN_SQUARES_DOUBLES))
128         .isWithin(ALLOWED_ERROR)
129         .of(SIXTEEN_SQUARES_MEDIAN);
130   }
131 
testMedian_computeInPlace()132   public void testMedian_computeInPlace() {
133     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
134     assertThat(median().computeInPlace(dataset)).isWithin(ALLOWED_ERROR).of(SIXTEEN_SQUARES_MEDIAN);
135     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
136   }
137 
testQuartiles_index_compute_doubleCollection()138   public void testQuartiles_index_compute_doubleCollection() {
139     assertThat(quartiles().index(1).compute(SIXTEEN_SQUARES_DOUBLES))
140         .isWithin(ALLOWED_ERROR)
141         .of(SIXTEEN_SQUARES_QUARTILE_1);
142   }
143 
testQuartiles_index_computeInPlace()144   public void testQuartiles_index_computeInPlace() {
145     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
146     assertThat(quartiles().index(1).computeInPlace(dataset))
147         .isWithin(ALLOWED_ERROR)
148         .of(SIXTEEN_SQUARES_QUARTILE_1);
149     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
150   }
151 
testQuartiles_indexes_varargs_compute_doubleCollection()152   public void testQuartiles_indexes_varargs_compute_doubleCollection() {
153     assertThat(quartiles().indexes(1, 3).compute(SIXTEEN_SQUARES_DOUBLES))
154         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
155         .containsExactly(1, SIXTEEN_SQUARES_QUARTILE_1, 3, SIXTEEN_SQUARES_QUARTILE_3);
156   }
157 
testQuartiles_indexes_varargs_computeInPlace()158   public void testQuartiles_indexes_varargs_computeInPlace() {
159     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
160     assertThat(quartiles().indexes(1, 3).computeInPlace(dataset))
161         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
162         .containsExactly(
163             1, SIXTEEN_SQUARES_QUARTILE_1,
164             3, SIXTEEN_SQUARES_QUARTILE_3);
165     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
166   }
167 
testScale_index_compute_doubleCollection()168   public void testScale_index_compute_doubleCollection() {
169     assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_DOUBLES))
170         .isWithin(ALLOWED_ERROR)
171         .of(SIXTEEN_SQUARES_DECILE_1);
172   }
173 
testScale_index_compute_longCollection()174   public void testScale_index_compute_longCollection() {
175     assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_LONGS))
176         .isWithin(ALLOWED_ERROR)
177         .of(SIXTEEN_SQUARES_DECILE_1);
178   }
179 
testScale_index_compute_integerCollection()180   public void testScale_index_compute_integerCollection() {
181     assertThat(Quantiles.scale(10).index(1).compute(SIXTEEN_SQUARES_INTEGERS))
182         .isWithin(ALLOWED_ERROR)
183         .of(SIXTEEN_SQUARES_DECILE_1);
184   }
185 
testScale_index_compute_doubleVarargs()186   public void testScale_index_compute_doubleVarargs() {
187     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
188     assertThat(Quantiles.scale(10).index(1).compute(dataset))
189         .isWithin(ALLOWED_ERROR)
190         .of(SIXTEEN_SQUARES_DECILE_1);
191     assertThat(dataset)
192         .usingExactEquality()
193         .containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES)
194         .inOrder();
195   }
196 
testScale_index_compute_longVarargs()197   public void testScale_index_compute_longVarargs() {
198     long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS);
199     assertThat(Quantiles.scale(10).index(1).compute(dataset))
200         .isWithin(ALLOWED_ERROR)
201         .of(SIXTEEN_SQUARES_DECILE_1);
202     assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS);
203   }
204 
testScale_index_compute_intVarargs()205   public void testScale_index_compute_intVarargs() {
206     int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS);
207     assertThat(Quantiles.scale(10).index(1).compute(dataset))
208         .isWithin(ALLOWED_ERROR)
209         .of(SIXTEEN_SQUARES_DECILE_1);
210     assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS);
211   }
212 
testScale_index_computeInPlace()213   public void testScale_index_computeInPlace() {
214     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
215     assertThat(Quantiles.scale(10).index(1).computeInPlace(dataset))
216         .isWithin(ALLOWED_ERROR)
217         .of(SIXTEEN_SQUARES_DECILE_1);
218     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
219   }
220 
testScale_index_computeInPlace_explicitVarargs()221   public void testScale_index_computeInPlace_explicitVarargs() {
222     assertThat(Quantiles.scale(10).index(5).computeInPlace(78.9, 12.3, 45.6))
223         .isWithin(ALLOWED_ERROR)
224         .of(45.6);
225   }
226 
testScale_indexes_varargs_compute_doubleCollection()227   public void testScale_indexes_varargs_compute_doubleCollection() {
228     // Note that we specify index 1 twice, which by the method contract should be ignored.
229     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_DOUBLES))
230         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
231         .containsExactly(
232             0, SIXTEEN_SQUARES_MIN,
233             10, SIXTEEN_SQUARES_MAX,
234             5, SIXTEEN_SQUARES_MEDIAN,
235             1, SIXTEEN_SQUARES_DECILE_1,
236             8, SIXTEEN_SQUARES_DECILE_8);
237   }
238 
testScale_indexes_varargs_compute_doubleCollection_snapshotsIndexes()239   public void testScale_indexes_varargs_compute_doubleCollection_snapshotsIndexes() {
240     // This test is the same as testScale_indexes_varargs_compute_doubleCollection except that the
241     // array of indexes to be calculated is modified between the calls to indexes and compute: since
242     // the contract is that it is snapshotted, this shouldn't make any difference to the result.
243     int[] indexes = {0, 10, 5, 1, 8, 10};
244     ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(indexes);
245     indexes[0] = 3;
246     assertThat(intermediate.compute(SIXTEEN_SQUARES_DOUBLES))
247         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
248         .containsExactly(
249             0, SIXTEEN_SQUARES_MIN,
250             10, SIXTEEN_SQUARES_MAX,
251             5, SIXTEEN_SQUARES_MEDIAN,
252             1, SIXTEEN_SQUARES_DECILE_1,
253             8, SIXTEEN_SQUARES_DECILE_8);
254   }
255 
testScale_indexes_largeVarargs_compute_doubleCollection()256   public void testScale_indexes_largeVarargs_compute_doubleCollection() {
257     int scale = Integer.MAX_VALUE;
258     int otherIndex = (Integer.MAX_VALUE - 1) / 3; // this divides exactly
259     // For the otherIndex calculation, we have q=Integer.MAX_VALUE, k=(Integer.MAX_VALUE-1)/3, and
260     // N=16. Therefore k*(N-1)/q = 5-5/Integer.MAX_VALUE, which has floor 4 and fractional part
261     // (1-5/Integer.MAX_VALUE).
262     double otherValue = 16.0 * 5.0 / Integer.MAX_VALUE + 25.0 * (1.0 - 5.0 / Integer.MAX_VALUE);
263     assertThat(
264             Quantiles.scale(scale).indexes(0, scale, otherIndex).compute(SIXTEEN_SQUARES_DOUBLES))
265         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
266         .containsExactly(
267             0, SIXTEEN_SQUARES_MIN, scale, SIXTEEN_SQUARES_MAX, otherIndex, otherValue);
268   }
269 
testScale_indexes_varargs_compute_longCollection()270   public void testScale_indexes_varargs_compute_longCollection() {
271     // Note that we specify index 1 twice, which by the method contract should be ignored.
272     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_LONGS))
273         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
274         .containsExactly(
275             0, SIXTEEN_SQUARES_MIN,
276             10, SIXTEEN_SQUARES_MAX,
277             5, SIXTEEN_SQUARES_MEDIAN,
278             1, SIXTEEN_SQUARES_DECILE_1,
279             8, SIXTEEN_SQUARES_DECILE_8);
280   }
281 
testScale_indexes_varargs_compute_integerCollection()282   public void testScale_indexes_varargs_compute_integerCollection() {
283     // Note that we specify index 1 twice, which by the method contract should be ignored.
284     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(SIXTEEN_SQUARES_INTEGERS))
285         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
286         .containsExactly(
287             0, SIXTEEN_SQUARES_MIN,
288             10, SIXTEEN_SQUARES_MAX,
289             5, SIXTEEN_SQUARES_MEDIAN,
290             1, SIXTEEN_SQUARES_DECILE_1,
291             8, SIXTEEN_SQUARES_DECILE_8);
292   }
293 
testScale_indexes_varargs_compute_doubleVarargs()294   public void testScale_indexes_varargs_compute_doubleVarargs() {
295     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
296     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset))
297         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
298         .containsExactly(
299             0, SIXTEEN_SQUARES_MIN,
300             10, SIXTEEN_SQUARES_MAX,
301             5, SIXTEEN_SQUARES_MEDIAN,
302             1, SIXTEEN_SQUARES_DECILE_1,
303             8, SIXTEEN_SQUARES_DECILE_8);
304     assertThat(dataset)
305         .usingExactEquality()
306         .containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES)
307         .inOrder();
308   }
309 
testScale_indexes_varargs_compute_longVarargs()310   public void testScale_indexes_varargs_compute_longVarargs() {
311     long[] dataset = Longs.toArray(SIXTEEN_SQUARES_LONGS);
312     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset))
313         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
314         .containsExactly(
315             0, SIXTEEN_SQUARES_MIN,
316             10, SIXTEEN_SQUARES_MAX,
317             5, SIXTEEN_SQUARES_MEDIAN,
318             1, SIXTEEN_SQUARES_DECILE_1,
319             8, SIXTEEN_SQUARES_DECILE_8);
320     assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_LONGS);
321   }
322 
testScale_indexes_varargs_compute_intVarargs()323   public void testScale_indexes_varargs_compute_intVarargs() {
324     int[] dataset = Ints.toArray(SIXTEEN_SQUARES_INTEGERS);
325     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).compute(dataset))
326         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
327         .containsExactly(
328             0, SIXTEEN_SQUARES_MIN,
329             10, SIXTEEN_SQUARES_MAX,
330             5, SIXTEEN_SQUARES_MEDIAN,
331             1, SIXTEEN_SQUARES_DECILE_1,
332             8, SIXTEEN_SQUARES_DECILE_8);
333     assertThat(dataset).asList().isEqualTo(SIXTEEN_SQUARES_INTEGERS);
334   }
335 
testScale_indexes_varargs_computeInPlace()336   public void testScale_indexes_varargs_computeInPlace() {
337     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
338     assertThat(Quantiles.scale(10).indexes(0, 10, 5, 1, 8, 1).computeInPlace(dataset))
339         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
340         .containsExactly(
341             0, SIXTEEN_SQUARES_MIN,
342             10, SIXTEEN_SQUARES_MAX,
343             5, SIXTEEN_SQUARES_MEDIAN,
344             1, SIXTEEN_SQUARES_DECILE_1,
345             8, SIXTEEN_SQUARES_DECILE_8);
346     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
347   }
348 
testScale_indexes_varargs_computeInPlace_explicitVarargs()349   public void testScale_indexes_varargs_computeInPlace_explicitVarargs() {
350     assertThat(Quantiles.scale(10).indexes(0, 10).computeInPlace(78.9, 12.3, 45.6))
351         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
352         .containsExactly(
353             0, 12.3,
354             10, 78.9);
355   }
356 
testScale_indexes_collection_compute_doubleCollection()357   public void testScale_indexes_collection_compute_doubleCollection() {
358     // Note that we specify index 1 twice, which by the method contract should be ignored.
359     assertThat(
360             Quantiles.scale(10)
361                 .indexes(ImmutableList.of(0, 10, 5, 1, 8, 1))
362                 .compute(SIXTEEN_SQUARES_DOUBLES))
363         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
364         .containsExactly(
365             0, SIXTEEN_SQUARES_MIN,
366             10, SIXTEEN_SQUARES_MAX,
367             5, SIXTEEN_SQUARES_MEDIAN,
368             1, SIXTEEN_SQUARES_DECILE_1,
369             8, SIXTEEN_SQUARES_DECILE_8);
370   }
371 
testScale_indexes_collection_computeInPlace()372   public void testScale_indexes_collection_computeInPlace() {
373     double[] dataset = Doubles.toArray(SIXTEEN_SQUARES_DOUBLES);
374     assertThat(
375             Quantiles.scale(10)
376                 .indexes(ImmutableList.of(0, 10, 5, 1, 8, 1))
377                 .computeInPlace(dataset))
378         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
379         .containsExactly(
380             0, SIXTEEN_SQUARES_MIN,
381             10, SIXTEEN_SQUARES_MAX,
382             5, SIXTEEN_SQUARES_MEDIAN,
383             1, SIXTEEN_SQUARES_DECILE_1,
384             8, SIXTEEN_SQUARES_DECILE_8);
385     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(SIXTEEN_SQUARES_DOUBLES);
386   }
387 
388   // 2. Tests on hardcoded datasets include non-finite values for chains starting with scale(10):
389 
390   private static final ImmutableList<Double> ONE_TO_FIVE_AND_POSITIVE_INFINITY =
391       ImmutableList.of(3.0, 5.0, POSITIVE_INFINITY, 1.0, 4.0, 2.0);
392   private static final ImmutableList<Double> ONE_TO_FIVE_AND_NEGATIVE_INFINITY =
393       ImmutableList.of(3.0, 5.0, NEGATIVE_INFINITY, 1.0, 4.0, 2.0);
394   private static final ImmutableList<Double> NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES =
395       ImmutableList.of(
396           POSITIVE_INFINITY,
397           POSITIVE_INFINITY,
398           NEGATIVE_INFINITY,
399           POSITIVE_INFINITY,
400           POSITIVE_INFINITY,
401           POSITIVE_INFINITY);
402   private static final ImmutableList<Double> ONE_TO_FIVE_AND_NAN =
403       ImmutableList.of(3.0, 5.0, NaN, 1.0, 4.0, 2.0);
404 
testScale_indexes_varargs_compute_doubleCollection_positiveInfinity()405   public void testScale_indexes_varargs_compute_doubleCollection_positiveInfinity() {
406     assertThat(
407             Quantiles.scale(10)
408                 .indexes(0, 1, 2, 8, 9, 10)
409                 .compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY))
410         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
411         .containsExactly(
412             0, 1.0,
413             1, 1.5,
414             2, 2.0,
415             8, 5.0,
416             9, POSITIVE_INFINITY, // interpolating between 5.0 and POSITIVE_INFNINITY
417             10, POSITIVE_INFINITY);
418   }
419 
testScale_index_compute_doubleCollection_positiveInfinity()420   public void testScale_index_compute_doubleCollection_positiveInfinity() {
421     // interpolating between 5.0 and POSITIVE_INFNINITY
422     assertThat(Quantiles.scale(10).index(9).compute(ONE_TO_FIVE_AND_POSITIVE_INFINITY))
423         .isPositiveInfinity();
424   }
425 
testScale_indexes_varargs_compute_doubleCollection_negativeInfinity()426   public void testScale_indexes_varargs_compute_doubleCollection_negativeInfinity() {
427     assertThat(
428             Quantiles.scale(10)
429                 .indexes(0, 1, 2, 8, 9, 10)
430                 .compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY))
431         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
432         .containsExactly(
433             0, NEGATIVE_INFINITY,
434             1, NEGATIVE_INFINITY, // interpolating between NEGATIVE_INFNINITY and 1.0
435             2, 1.0,
436             8, 4.0,
437             9, 4.5,
438             10, 5.0);
439   }
440 
testScale_index_compute_doubleCollection_negativeInfinity()441   public void testScale_index_compute_doubleCollection_negativeInfinity() {
442     // interpolating between NEGATIVE_INFNINITY and 1.0
443     assertThat(Quantiles.scale(10).index(1).compute(ONE_TO_FIVE_AND_NEGATIVE_INFINITY))
444         .isNegativeInfinity();
445   }
446 
testScale_indexes_varargs_compute_doubleCollection_bothInfinities()447   public void testScale_indexes_varargs_compute_doubleCollection_bothInfinities() {
448     assertThat(
449             Quantiles.scale(10)
450                 .indexes(0, 1, 2, 8, 9, 10)
451                 .compute(NEGATIVE_INFINITY_AND_FIVE_POSITIVE_INFINITIES))
452         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
453         .containsExactly(
454             0, NEGATIVE_INFINITY,
455             1, NaN, // interpolating between NEGATIVE_ and POSITIVE_INFINITY values
456             2, POSITIVE_INFINITY,
457             8, POSITIVE_INFINITY,
458             9, POSITIVE_INFINITY, // interpolating between two POSITIVE_INFINITY values
459             10, POSITIVE_INFINITY);
460   }
461 
testScale_indexes_varargs_compute_doubleCollection_nan()462   public void testScale_indexes_varargs_compute_doubleCollection_nan() {
463     assertThat(Quantiles.scale(10).indexes(0, 1, 2, 8, 9, 10).compute(ONE_TO_FIVE_AND_NAN))
464         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
465         .containsExactly(
466             0, NaN,
467             1, NaN,
468             2, NaN,
469             8, NaN,
470             9, NaN,
471             10, NaN);
472   }
473 
testScale_index_compute_doubleCollection_nan()474   public void testScale_index_compute_doubleCollection_nan() {
475     assertThat(Quantiles.scale(10).index(5).compute(ONE_TO_FIVE_AND_NAN)).isNaN();
476   }
477 
478   // 3. Tests on a mechanically generated dataset for chains starting with percentiles():
479 
480   private static final int PSEUDORANDOM_DATASET_SIZE = 9951;
481   private static final ImmutableList<Double> PSEUDORANDOM_DATASET = generatePseudorandomDataset();
482   private static final ImmutableList<Double> PSEUDORANDOM_DATASET_SORTED =
483       Ordering.natural().immutableSortedCopy(PSEUDORANDOM_DATASET);
484 
generatePseudorandomDataset()485   private static ImmutableList<Double> generatePseudorandomDataset() {
486     Random random = new Random(2211275185798966364L);
487     ImmutableList.Builder<Double> largeDatasetBuilder = ImmutableList.builder();
488     for (int i = 0; i < PSEUDORANDOM_DATASET_SIZE; i++) {
489       largeDatasetBuilder.add(random.nextGaussian());
490     }
491     return largeDatasetBuilder.build();
492   }
493 
expectedLargeDatasetPercentile(int index)494   private static double expectedLargeDatasetPercentile(int index) {
495     // We have q=100, k=index, and N=9951. Therefore k*(N-1)/q is 99.5*index. If index is even, that
496     // is an integer 199*index/2. If index is odd, that is halfway between floor(199*index/2) and
497     // ceil(199*index/2).
498     if (index % 2 == 0) {
499       int position = IntMath.divide(199 * index, 2, UNNECESSARY);
500       return PSEUDORANDOM_DATASET_SORTED.get(position);
501     } else {
502       int positionFloor = IntMath.divide(199 * index, 2, FLOOR);
503       int positionCeil = IntMath.divide(199 * index, 2, CEILING);
504       double lowerValue = PSEUDORANDOM_DATASET_SORTED.get(positionFloor);
505       double upperValue = PSEUDORANDOM_DATASET_SORTED.get(positionCeil);
506       return (lowerValue + upperValue) / 2.0;
507     }
508   }
509 
testPercentiles_index_compute_doubleCollection()510   public void testPercentiles_index_compute_doubleCollection() {
511     for (int index = 0; index <= 100; index++) {
512       assertThat(percentiles().index(index).compute(PSEUDORANDOM_DATASET))
513           .named("quantile at index " + index)
514           .isWithin(ALLOWED_ERROR)
515           .of(expectedLargeDatasetPercentile(index));
516     }
517   }
518 
519   @AndroidIncompatible // slow
testPercentiles_index_computeInPlace()520   public void testPercentiles_index_computeInPlace() {
521     // Assert that the computation gives the correct result for all possible percentiles.
522     for (int index = 0; index <= 100; index++) {
523       double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET);
524       assertThat(percentiles().index(index).computeInPlace(dataset))
525           .named("quantile at index " + index)
526           .isWithin(ALLOWED_ERROR)
527           .of(expectedLargeDatasetPercentile(index));
528     }
529 
530     // Assert that the dataset contains the same elements after the in-place computation (although
531     // they may be reordered). We only do this for one index rather than for all indexes, as it is
532     // quite expensives (quadratic in the size of PSEUDORANDOM_DATASET).
533     double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET);
534     @SuppressWarnings("unused")
535     double actual = percentiles().index(33).computeInPlace(dataset);
536     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET);
537   }
538 
testPercentiles_indexes_varargsPairs_compute_doubleCollection()539   public void testPercentiles_indexes_varargsPairs_compute_doubleCollection() {
540     for (int index1 = 0; index1 <= 100; index1++) {
541       for (int index2 = 0; index2 <= 100; index2++) {
542         ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder();
543         expectedBuilder.put(index1, expectedLargeDatasetPercentile(index1));
544         if (index2 != index1) {
545           expectedBuilder.put(index2, expectedLargeDatasetPercentile(index2));
546         }
547         assertThat(percentiles().indexes(index1, index2).compute(PSEUDORANDOM_DATASET))
548             .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
549             .containsExactlyEntriesIn(expectedBuilder.build());
550       }
551     }
552   }
553 
testPercentiles_indexes_varargsAll_compute_doubleCollection()554   public void testPercentiles_indexes_varargsAll_compute_doubleCollection() {
555     ArrayList<Integer> indexes = new ArrayList<>();
556     ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder();
557     for (int index = 0; index <= 100; index++) {
558       indexes.add(index);
559       expectedBuilder.put(index, expectedLargeDatasetPercentile(index));
560     }
561     Random random = new Random(770683168895677741L);
562     Collections.shuffle(indexes, random);
563     assertThat(percentiles().indexes(Ints.toArray(indexes)).compute(PSEUDORANDOM_DATASET))
564         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
565         .containsExactlyEntriesIn(expectedBuilder.build());
566   }
567 
568   @AndroidIncompatible // slow
testPercentiles_indexes_varargsAll_computeInPlace()569   public void testPercentiles_indexes_varargsAll_computeInPlace() {
570     double[] dataset = Doubles.toArray(PSEUDORANDOM_DATASET);
571     List<Integer> indexes = new ArrayList<>();
572     ImmutableMap.Builder<Integer, Double> expectedBuilder = ImmutableMap.builder();
573     for (int index = 0; index <= 100; index++) {
574       indexes.add(index);
575       expectedBuilder.put(index, expectedLargeDatasetPercentile(index));
576     }
577     Random random = new Random(770683168895677741L);
578     Collections.shuffle(indexes, random);
579     assertThat(percentiles().indexes(Ints.toArray(indexes)).computeInPlace(dataset))
580         .comparingValuesUsing(QUANTILE_CORRESPONDENCE)
581         .containsExactlyEntriesIn(expectedBuilder.build());
582     assertThat(dataset).usingExactEquality().containsExactlyElementsIn(PSEUDORANDOM_DATASET);
583   }
584 
585   // 4. Tests of illegal usages of the API:
586 
587   private static final ImmutableList<Double> EMPTY_DATASET = ImmutableList.of();
588 
testScale_zero()589   public void testScale_zero() {
590     try {
591       Quantiles.scale(0);
592       fail("Expected IllegalArgumentException");
593     } catch (IllegalArgumentException expected) {
594     }
595   }
596 
testScale_negative()597   public void testScale_negative() {
598     try {
599       Quantiles.scale(-4);
600       fail("Expected IllegalArgumentException");
601     } catch (IllegalArgumentException expected) {
602     }
603   }
604 
testScale_index_negative()605   public void testScale_index_negative() {
606     Quantiles.Scale intermediate = Quantiles.scale(10);
607     try {
608       intermediate.index(-1);
609       fail("Expected IllegalArgumentException");
610     } catch (IllegalArgumentException expected) {
611     }
612   }
613 
testScale_index_tooHigh()614   public void testScale_index_tooHigh() {
615     Quantiles.Scale intermediate = Quantiles.scale(10);
616     try {
617       intermediate.index(11);
618       fail("Expected IllegalArgumentException");
619     } catch (IllegalArgumentException expected) {
620     }
621   }
622 
testScale_indexes_varargs_negative()623   public void testScale_indexes_varargs_negative() {
624     Quantiles.Scale intermediate = Quantiles.scale(10);
625     try {
626       intermediate.indexes(1, -1, 3);
627       fail("Expected IllegalArgumentException");
628     } catch (IllegalArgumentException expected) {
629     }
630   }
631 
testScale_indexes_varargs_tooHigh()632   public void testScale_indexes_varargs_tooHigh() {
633     Quantiles.Scale intermediate = Quantiles.scale(10);
634     try {
635       intermediate.indexes(1, 11, 3);
636       fail("Expected IllegalArgumentException");
637     } catch (IllegalArgumentException expected) {
638     }
639   }
640 
testScale_indexes_collection_negative()641   public void testScale_indexes_collection_negative() {
642     Quantiles.Scale intermediate = Quantiles.scale(10);
643     try {
644       intermediate.indexes(ImmutableList.of(1, -1, 3));
645       fail("Expected IllegalArgumentException");
646     } catch (IllegalArgumentException expected) {
647     }
648   }
649 
testScale_indexes_collection_tooHigh()650   public void testScale_indexes_collection_tooHigh() {
651     Quantiles.Scale intermediate = Quantiles.scale(10);
652     try {
653       intermediate.indexes(ImmutableList.of(1, 11, 3));
654       fail("Expected IllegalArgumentException");
655     } catch (IllegalArgumentException expected) {
656     }
657   }
658 
testScale_index_compute_doubleCollection_empty()659   public void testScale_index_compute_doubleCollection_empty() {
660     Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
661     try {
662       intermediate.compute(EMPTY_DATASET);
663       fail("Expected IllegalArgumentException");
664     } catch (IllegalArgumentException expected) {
665     }
666   }
667 
testScale_index_compute_doubleVarargs_empty()668   public void testScale_index_compute_doubleVarargs_empty() {
669     Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
670     try {
671       intermediate.compute(new double[] {});
672       fail("Expected IllegalArgumentException");
673     } catch (IllegalArgumentException expected) {
674     }
675   }
676 
testScale_index_compute_longVarargs_empty()677   public void testScale_index_compute_longVarargs_empty() {
678     Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
679     try {
680       intermediate.compute(new long[] {});
681       fail("Expected IllegalArgumentException");
682     } catch (IllegalArgumentException expected) {
683     }
684   }
685 
testScale_index_compute_intVarargs_empty()686   public void testScale_index_compute_intVarargs_empty() {
687     Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
688     try {
689       intermediate.compute(new int[] {});
690       fail("Expected IllegalArgumentException");
691     } catch (IllegalArgumentException expected) {
692     }
693   }
694 
testScale_index_computeInPlace_empty()695   public void testScale_index_computeInPlace_empty() {
696     Quantiles.ScaleAndIndex intermediate = Quantiles.scale(10).index(3);
697     try {
698       intermediate.computeInPlace(new double[] {});
699       fail("Expected IllegalArgumentException");
700     } catch (IllegalArgumentException expected) {
701     }
702   }
703 
testScale_indexes_varargs_compute_doubleCollection_empty()704   public void testScale_indexes_varargs_compute_doubleCollection_empty() {
705     Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
706     try {
707       intermediate.compute(EMPTY_DATASET);
708       fail("Expected IllegalArgumentException");
709     } catch (IllegalArgumentException expected) {
710     }
711   }
712 
testScale_indexes_varargs_compute_doubleVarargs_empty()713   public void testScale_indexes_varargs_compute_doubleVarargs_empty() {
714     Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
715     try {
716       intermediate.compute(new double[] {});
717       fail("Expected IllegalArgumentException");
718     } catch (IllegalArgumentException expected) {
719     }
720   }
721 
testScale_indexes_varargs_compute_longVarargs_empty()722   public void testScale_indexes_varargs_compute_longVarargs_empty() {
723     Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
724     try {
725       intermediate.compute(new long[] {});
726       fail("Expected IllegalArgumentException");
727     } catch (IllegalArgumentException expected) {
728     }
729   }
730 
testScale_indexes_varargs_compute_intVarargs_empty()731   public void testScale_indexes_varargs_compute_intVarargs_empty() {
732     Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
733     try {
734       intermediate.compute(new int[] {});
735       fail("Expected IllegalArgumentException");
736     } catch (IllegalArgumentException expected) {
737     }
738   }
739 
testScale_indexes_varargs_computeInPlace_empty()740   public void testScale_indexes_varargs_computeInPlace_empty() {
741     Quantiles.ScaleAndIndexes intermediate = Quantiles.scale(10).indexes(1, 3, 5);
742     try {
743       intermediate.computeInPlace(new double[] {});
744       fail("Expected IllegalArgumentException");
745     } catch (IllegalArgumentException expected) {
746     }
747   }
748 }
749