| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 accord.burn.random; |
| |
| import accord.utils.Gen; |
| import accord.utils.Gens; |
| import accord.utils.RandomSource; |
| import org.agrona.collections.Long2LongHashMap; |
| import org.assertj.core.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| |
| import java.util.Arrays; |
| import java.util.stream.LongStream; |
| |
| import static accord.utils.Property.qt; |
| |
| class FrequentLargeRangeTest |
| { |
| enum Type |
| { |
| IntRange, |
| RandomWalkRange |
| } |
| |
| private static final class TestCase |
| { |
| private final int minSmall, maxSmall, minLarge, maxLarge, largeRatio; |
| private final Type type; |
| |
| private TestCase(int minSmall, int maxSmall, int minLarge, int maxLarge, int largeRatio, Type type) |
| { |
| this.minSmall = minSmall; |
| this.maxSmall = maxSmall; |
| this.minLarge = minLarge; |
| this.maxLarge = maxLarge; |
| this.largeRatio = largeRatio; |
| this.type = type; |
| } |
| |
| Gen.LongGen min(RandomSource random) |
| { |
| return create(random, minSmall, maxSmall); |
| } |
| |
| Gen.LongGen max(RandomSource random) |
| { |
| return create(random, minLarge, maxLarge); |
| } |
| |
| private Gen.LongGen create(RandomSource random, int min, int max) |
| { |
| switch (type) |
| { |
| case IntRange: return new IntRange(min, max); |
| case RandomWalkRange: return new RandomWalkRange(random, min, max); |
| default: throw new UnsupportedOperationException("type " + type); |
| } |
| } |
| |
| double ratio() |
| { |
| return 1 / (double) largeRatio; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "TestCase{" + |
| "minSmall=" + minSmall + |
| ", maxSmall=" + maxSmall + |
| ", minLarge=" + minLarge + |
| ", maxLarge=" + maxLarge + |
| ", largeRatio=" + largeRatio + |
| ", type=" + type + |
| '}'; |
| } |
| } |
| |
| @Test |
| void disjoint() |
| { |
| Gen<Integer> range = Gens.ints().between(100, 100000000); |
| Gen<Integer> ratio = Gens.ints().between(6, 20); |
| Gen<Type> typeGen = Gens.enums().all(Type.class); |
| int[] ints = new int[3]; |
| Gen<TestCase> test = rs -> { |
| ints[0] = range.next(rs); |
| ints[1] = range.next(rs); |
| ints[2] = range.next(rs); |
| Arrays.sort(ints); |
| int largeRatio = ratio.next(rs); |
| return new TestCase(0, ints[0], ints[1], ints[2], largeRatio, typeGen.next(rs)); |
| }; |
| qt().forAll(Gens.random(), test).check(FrequentLargeRangeTest::test); |
| } |
| |
| private static void test(RandomSource rs, TestCase tc) |
| { |
| double ratio = tc.ratio(); |
| FrequentLargeRange period = new FrequentLargeRange(tc.min(rs), tc.max(rs), ratio, tc.largeRatio); |
| int numSamples = 1000; |
| int maxResamples = 1000; |
| |
| double target = ratio * 100.0D; |
| double upperBounds = target * 1.2; |
| double lowerBounds = target * .8; |
| |
| int largeSeq = 0; |
| int largeCount = 0; |
| Long2LongHashMap largeSeqCounts = new Long2LongHashMap(0); |
| int resamples = 0; |
| for (int i = 0; i < numSamples; i++) |
| { |
| long size = period.nextLong(rs); |
| if (size > tc.maxSmall) |
| { |
| largeCount++; |
| largeSeq++; |
| } |
| else |
| { |
| largeSeqCounts.compute(largeSeq, (ignore, accm) -> accm + 1); |
| largeSeq = 0; |
| } |
| if (i == numSamples - 1) |
| { |
| // keep going if ratio is off... |
| double actual = (double) largeCount / numSamples * 100.0; |
| if (actual < lowerBounds || actual > upperBounds) |
| { |
| if (resamples == maxResamples) |
| throw new AssertionError(String.format("Unable to match target rate in %d re-samples; actual=%f, target=%f, bounds=(%f, %f)", resamples, actual, target, lowerBounds, upperBounds)); |
| numSamples += 100; |
| resamples++; |
| } |
| } |
| } |
| |
| checkSequences(largeSeqCounts); |
| assertRatio(numSamples, upperBounds, lowerBounds, largeCount); |
| } |
| |
| private static void checkSequences(Long2LongHashMap largeSeqCounts) |
| { |
| long[] keys = new long[largeSeqCounts.size()]; |
| Long2LongHashMap.KeyIterator it = largeSeqCounts.keySet().iterator(); |
| int idx = 0; |
| while (it.hasNext()) |
| keys[idx++] = it.nextValue(); |
| if (LongStream.of(keys).anyMatch(seq -> seq > 5)) |
| return; |
| StringBuilder sb = new StringBuilder("No large sequences detected; saw\n"); |
| Arrays.sort(keys); |
| for (long key : keys) |
| sb.append('\t').append(key).append('\t').append(largeSeqCounts.get(key)).append('\n'); |
| throw new AssertionError(sb.toString()); |
| } |
| |
| private static void assertRatio(int numSamples, double upperBounds, double lowerBounds, long largeCount) |
| { |
| double largePercent = largeCount / (double) numSamples * 100.0; |
| Assertions.assertThat(largePercent) |
| .describedAs("Expected ratio was not respected") |
| .isBetween(lowerBounds, upperBounds); |
| } |
| } |