/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.magma.math.stat;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import org.junit.Test;
import org.obiba.magma.math.stat.IntervalFrequency.Interval;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
public class IntervalFrequencyTest {
@Test
public void test_sumOfFrequenciesAccountsAllValues() {
IntervalFrequency freqs = newRandomDistribution(150000);
long n = 0;
for(Interval freq : freqs.intervals()) {
n += freq.getFreq();
}
assertThat(n).isEqualTo(150000l);
}
@Test
public void test_densityPct_areaSumsToOne() {
IntervalFrequency freqs = newRandomDistribution(100);
// Compute the area of each interval and sum
BigDecimal totalArea = BigDecimal.ZERO;
for(Interval freq : freqs.intervals()) {
BigDecimal width = BigDecimal.valueOf(freq.getUpper()).subtract(BigDecimal.valueOf(freq.getLower()));
totalArea = totalArea.add(width.multiply(BigDecimal.valueOf(freq.getDensityPct())));
}
// Round the result to 2 decimal places: 0.998 => 1.00
totalArea = totalArea.setScale(2, RoundingMode.HALF_EVEN).stripTrailingZeros();
assertThat(totalArea).isEqualTo(BigDecimal.ONE);
}
@Test
public void test_add_limitCaseLower() {
IntervalFrequency freqs = new IntervalFrequency(1.6, 4.6, 10);
freqs.add(1.6);
}
@Test
public void test_add_limitCaseRoundedLower() {
// 12345.67 is rounded to 6 significant digits
IntervalFrequency freqs = new IntervalFrequency(12345.67, 765432.1, 10);
assertThat(freqs.intervals().first().getLower() < 12345.67).isTrue();
}
@Test
public void test_add_limitCaseUpper() {
IntervalFrequency freqs = new IntervalFrequency(1.6, 4.6, 10);
freqs.add(4.6);
}
@Test
public void test_add_limitCaseRoundedUpper() {
// 12345.67 is rounded to 6 significant digits
IntervalFrequency freqs = new IntervalFrequency(0, 12345.67, 10);
assertThat(freqs.intervals().last().getUpper() > 12345.67).isTrue();
}
@Test
public void test_add_limitCaseNoAdd() {
IntervalFrequency freqs = newRandomDistribution(0);
long n = 0;
double density = 0;
double densityPct = 0;
for(Interval freq : freqs.intervals()) {
n += freq.getFreq();
density += freq.getDensity();
densityPct += freq.getDensityPct();
}
assertThat(n).isEqualTo(0l);
assertThat(density).isEqualTo(0d);
assertThat(densityPct).isEqualTo(0d);
}
@Test(expected = IllegalArgumentException.class)
public void test_add_outsideRangeThrowsIAE() {
IntervalFrequency freqs = new IntervalFrequency(0.0004, 0.175, 10);
freqs.add(1.5);
}
@Test(expected = IllegalArgumentException.class)
public void test_ctor_lowerEqUpperThrowsIAE() {
new IntervalFrequency(1, 1, 10);
}
@Test(expected = IllegalArgumentException.class)
public void test_ctor_lowerGtUpperThrowsIAE() {
new IntervalFrequency(2, 1, 10);
}
@Test(expected = IllegalArgumentException.class)
public void test_ctor_nonPositiveBinCountThrowsIAE() {
new IntervalFrequency(1, 2, 0);
}
@Test(expected = IllegalArgumentException.class)
public void test_ctor_negativeBinCountThrowsIAE() {
new IntervalFrequency(1, 2, -1);
}
@Test
@SuppressWarnings("ResultOfMethodCallIgnored")
public void test_ctor_integerRounding() {
IntervalFrequency freqs = new IntervalFrequency(40, 60, 8, true);
for(Interval freq : freqs.intervals()) {
double bound = freq.getLower();
try {
BigDecimal.valueOf(bound).toBigIntegerExact();
bound = freq.getUpper();
BigDecimal.valueOf(bound).toBigIntegerExact();
} catch(ArithmeticException e) {
fail("bound is not an exact integer value:" + bound);
}
}
}
@Test
public void test_ctor_integerRoundingWillNotMakeIntervalSizeSmallerThanOne() {
IntervalFrequency freqs = new IntervalFrequency(40, 41, 10, true);
Interval i = freqs.intervals().first();
assertThat(i.getUpper() - i.getLower()).isEqualTo(1d);
}
@Test
public void test_toString_returnsAString() {
IntervalFrequency freqs = newRandomDistribution(1000);
assertThat(freqs.toString()).isNotNull();
}
@Test
public void test_interval_contains_allCases() {
IntervalFrequency freqs = newRandomDistribution(1000);
Interval interval = freqs.intervals().first();
double min = interval.getLower();
double lowerThanMin = Math.nextAfter(min, Double.NEGATIVE_INFINITY);
double higherThanMin = Math.nextAfter(min, Double.POSITIVE_INFINITY);
double max = interval.getUpper();
double lowerThanMax = Math.nextAfter(max, Double.NEGATIVE_INFINITY);
double higherThanMax = Math.nextAfter(max, Double.POSITIVE_INFINITY);
assertThat(interval.contains(higherThanMin)).isTrue();
assertThat(interval.contains(lowerThanMin)).isFalse();
assertThat(interval.contains(lowerThanMax)).isTrue();
assertThat(interval.contains(higherThanMax)).isFalse();
}
@Test
public void test_interval_equalsAndHashCode() {
IntervalFrequency first = newRandomDistribution(2, 10, 4, 1000);
IntervalFrequency second = newRandomDistribution(2, 10, 4, 10000);
for(Interval interval : first.intervals()) {
Interval other = second.intervals().tailSet(interval).first();
// Tests equals
assertThat(other).isEqualTo(interval);
// Tests hashCode
assertThat(other.hashCode()).isEqualTo(interval.hashCode());
}
// Other equals tests
Interval equals = first.intervals().first();
//noinspection ObjectEqualsNull
assertThat(equals.equals(null)).isFalse();
assertThat(equals.equals(equals)).isTrue();
assertThat(equals.equals(new Object())).isFalse();
}
/**
* Creates a new IntervalFrequency instance with random lower and upper bounds, with random intervals between [1,15]
*
* @param observations the number of random observations to add
* @return
*/
private IntervalFrequency newRandomDistribution(int observations) {
Random prng = new Random();
int pow = prng.nextInt(10) * (prng.nextBoolean() ? -1 : 1);
double first = prng.nextDouble() * Math.pow(10, pow) * (prng.nextBoolean() ? -1 : 1);
double second = prng.nextDouble() * Math.pow(10, pow) * (prng.nextBoolean() ? -1 : 1);
int intervals = Math.abs(prng.nextInt(14)) + 1;
return newRandomDistribution(Math.min(first, second), Math.max(first, second), intervals, observations);
}
private IntervalFrequency newRandomDistribution(double min, double max, int intervals, int observations) {
return addRandomObservations(new IntervalFrequency(min, max, intervals), min, max, observations);
}
private IntervalFrequency addRandomObservations(IntervalFrequency freqs, double min, double max, int observations) {
Random prng = new Random();
double diff = max - min;
for(int i = 0; i < observations; i++) {
freqs.add(prng.nextDouble() * diff + min);
}
return freqs;
}
}