/*
* Copyright 2007 Flaptor (flaptor.com)
*
* 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.flaptor.hist4j;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
import junit.framework.TestCase;
/**
* Unit test for the AdaptiveHistogram class
*
* @author Jorge Handl
*/
public class AdaptiveHistogramTest extends TestCase {
Random rnd = new Random(new Date().getTime());
/**
* Check the histogram values to see if the expected properties hold.
*
* @param h
* the histogram to check.
* @param min
* the minimum value in the data set used to build the histogram.
* @param max
* the maximum value in the data set used to build the histogram.
* @param totalCount
* the total number of data points used to build the histogram.
* @param skew
* true if the data is skewed, false otherwise.
*/
private void checkValues(AdaptiveHistogram h, float min, float max, long totalCount, boolean skew) {
float middle = (max + min) / 2f;
float alittle = (max - min) / 10f;
boolean flat = false;
if (0 == alittle) {
alittle = Math.abs((0 == min) ? 0.1f : min);
flat = true;
}
assertTrue("AccumCount for a value lower than the minimum is not zero", h.getAccumCount(min - alittle) == 0);
assertTrue("AccumCount for a value higher than the maximum is not the whole data set",
h.getAccumCount(max + alittle) == totalCount);
if (!flat)
assertTrue("AccumCount for the middle of the data set returns the whole data set",
h.getAccumCount(middle) < totalCount);
if (skew)
assertTrue("AccumCount for the middle of a skewed data set doesn't reflect the skew",
h.getAccumCount(middle) > 1.1f * (totalCount / 2));
long lastCount = -1;
for (int i = 0; i < 11; i++) {
float val = (i / 10f) * (max - min) + min;
long count = h.getAccumCount(val);
assertTrue("The cumulative count decreases for increasing values", count >= lastCount);
lastCount = count;
}
assertTrue("The value for percentile 0 is not close to the begining of the data set",
h.getValueForPercentile(0) < min + alittle);
assertTrue("The value for percentile 100 is not close to the end of the data set",
h.getValueForPercentile(100) > max - alittle);
if (skew)
assertTrue("The value for percentile 50 of a skewed data set doesn't reflect the skew",
h.getValueForPercentile(50) < middle - alittle);
float lastValue = min - alittle;
for (int i = 0; i < 11; i++) {
int per = i * 10;
float value = h.getValueForPercentile(per);
assertTrue("The value for an increasing percentile decreases", value >= lastValue);
lastValue = value;
}
}
/**
* Verify that the histogram features work as expected with the given data.
*
* @param data
* an array of numbers to pass on to the histogram.
* @param min
* minimun value in the data set.
* @param max
* maximun value in the data set.
* @param skew
* true if the data is skewed, false otherwise.
*/
private void verify(float[] data, float min, float max, boolean skew) throws Exception {
AdaptiveHistogram h = new AdaptiveHistogram();
int size = data.length;
for (int i = 0; i < size; i++) {
h.addValue(data[i]);
}
checkValues(h, min, max, size, skew);
Arrays.sort(data);
int step = size / 100;
if (0 == step)
step = 1;
for (int i = 0; i < size; i += step) {
int per = (i * 100) / size;
float value = h.getValueForPercentile(per);
float reference = data[i];
assertTrue("The percentile value does not match the real data", Math.abs(value - reference) < 0.05f);
}
if (min < max) {
int tests = 50;
for (int i = 0; i < tests; i++) {
do {
min = 1000f * rnd.nextFloat() - 500f;
max = 1000f * rnd.nextFloat() - 500f;
} while (min >= max);
h.normalize(min, max);
checkValues(h, min, max, size, skew);
}
}
}
/**
* Test storing random values, skewed so the histogram adaptability feature can be tested.
*
* @param tests
* the number of data points.
*/
private void tryRandomValues(int tests) throws Exception {
float[] data = new float[tests];
for (int i = 0; i < tests; i++) {
float val = rnd.nextFloat();
val = val * val * val; // skew the data set so that most points fall at the begining of the [0-1] range.
data[i] = val;
}
verify(data, 0f, 1f, true);
}
/**
* Test storing a fixed value
*
* @param tests
* the number of data points.
* @param val
* the fixed value to use in the test.
*/
private void tryFixedValue(int tests, float val) throws Exception {
float[] data = new float[tests];
for (int i = 0; i < tests; i++) {
data[i] = val;
}
verify(data, val, val, false);
}
/**
* Test various normal and extreme cases.
*/
public void testHistogram() throws Exception {
tryRandomValues(10000);
tryFixedValue(10, 0f);
tryFixedValue(10, 1E10f);
tryFixedValue(10, -1E10f);
tryFixedValue(10, 1E-10f);
tryFixedValue(10, -1E-10f);
}
}