├── fast-rng
├── gradle.properties
├── src
│ ├── test
│ │ └── java
│ │ │ └── biz
│ │ │ └── k11i
│ │ │ └── rng
│ │ │ ├── GaussianRNGTest.java
│ │ │ ├── UniformRNGUtilsTest.java
│ │ │ ├── ExponentialRNGTest.java
│ │ │ ├── GammaRNGTest.java
│ │ │ └── BetaRNGTest.java
│ └── main
│ │ └── java
│ │ └── biz
│ │ └── k11i
│ │ └── rng
│ │ ├── UniformRNGUtils.java
│ │ ├── ExponentialRNG.java
│ │ ├── BetaRNG.java
│ │ ├── GaussianRNG.java
│ │ └── GammaRNG.java
└── build.gradle
├── .travis.yml
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── Makefile
├── fast-rng-test
├── src
│ ├── main
│ │ ├── resources
│ │ │ └── simplelogger.properties
│ │ └── java
│ │ │ └── biz
│ │ │ └── k11i
│ │ │ └── rng
│ │ │ └── test
│ │ │ ├── util
│ │ │ ├── distribution
│ │ │ │ ├── DiscreteDistribution.java
│ │ │ │ ├── ContinuousDistribution.java
│ │ │ │ ├── ContinuousDistributionBase.java
│ │ │ │ └── ProbabilityDistributions.java
│ │ │ ├── inference
│ │ │ │ ├── MTest.java
│ │ │ │ └── AndersonDarlingTest.java
│ │ │ ├── SplittableRandomWrapper.java
│ │ │ └── ComputationAndSorting.java
│ │ │ ├── SecondLevelTest.java
│ │ │ └── gof
│ │ │ ├── GoodnessOfFitTest.java
│ │ │ ├── ContinuousGofTest.java
│ │ │ └── DiscreteGofTest.java
│ ├── test
│ │ └── java
│ │ │ └── biz
│ │ │ └── k11i
│ │ │ └── rng
│ │ │ └── test
│ │ │ ├── SecondLevelTestTest.java
│ │ │ └── util
│ │ │ ├── inference
│ │ │ ├── AndersonDarlingTestTest.java
│ │ │ └── MTestTest.java
│ │ │ └── distribution
│ │ │ ├── ProbabilityDistributionTestBase.java
│ │ │ ├── BetaTest.java
│ │ │ ├── GammaTest.java
│ │ │ └── GaussianTest.java
│ └── jmh
│ │ └── java
│ │ └── biz
│ │ └── k11i
│ │ └── rng
│ │ └── test
│ │ └── util
│ │ └── distribution
│ │ └── BetaDistributionBenchmark.java
└── build.gradle
├── NOTICE-commons-math3.txt
├── .github
└── workflows
│ └── test.yml
├── benchmark
├── build.gradle
└── src
│ └── jmh
│ └── java
│ └── biz
│ └── k11i
│ └── rng
│ ├── util
│ ├── ParameterPool.java
│ ├── UniformRandomSupplier.java
│ ├── MtRandom.java
│ └── ThreadLocalRandomGenerator.java
│ ├── ExponentialBenchmark.java
│ ├── GaussianBenchmark.java
│ ├── NextIntWithBoundBenchmark.java
│ ├── GammaBenchmark.java
│ └── BetaBenchmark.java
├── RELEASE-NOTES.md
├── .circleci
└── config.yml
├── LICENSE
├── gradlew.bat
├── README.md
└── gradlew
/fast-rng/gradle.properties:
--------------------------------------------------------------------------------
1 | release.useAutomaticVersion = true
2 | version = 0.2.1-SNAPSHOT
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | script: travis_wait 60 ./gradlew clean check
3 | jdk:
4 | - oraclejdk7
5 | - openjdk7
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle
2 | .gradle/
3 | build/
4 |
5 | # IntelliJ IDEA
6 | out/
7 | .idea/
8 | *.iml
9 |
10 | .envrc
11 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/komiya-atsushi/fast-rng-java/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'fast-rng-java'
2 | include 'fast-rng'
3 | include 'benchmark'
4 | include 'fast-rng-test'
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | GRADLE = ./gradlew
2 |
3 |
4 | .PHONY: test release
5 |
6 | test:
7 | $(GRADLE) test
8 |
9 | release:
10 | $(GRADLE) clean fast-rng:release --no-daemon -x test
11 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/resources/simplelogger.properties:
--------------------------------------------------------------------------------
1 | org.slf4j.simpleLogger.showDateTime=true
2 | org.slf4j.simpleLogger.logFile=System.out
3 | org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
4 | org.slf4j.simpleLogger.showShortLogName=true
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 18 00:17:42 JST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-all.zip
7 |
--------------------------------------------------------------------------------
/NOTICE-commons-math3.txt:
--------------------------------------------------------------------------------
1 | Apache Commons Math
2 | Copyright 2001-2016 The Apache Software Foundation
3 |
4 | This product includes software developed at
5 | The Apache Software Foundation (http://www.apache.org/).
6 |
7 | This product includes software developed for Orekit by
8 | CS Systèmes d'Information (http://www.c-s.fr/)
9 | Copyright 2010-2012 CS Systèmes d'Information
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on: push
3 |
4 | jobs:
5 | test:
6 | runs-on: ubuntu-latest
7 | strategy:
8 | matrix:
9 | java: [ '1.8', '11' ]
10 | steps:
11 | - uses: actions/checkout@master
12 | - uses: actions/setup-java@master
13 | with:
14 | java-version: ${{ matrix.java }}
15 | - name: Run tests
16 | run: ./gradlew test
17 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/distribution/DiscreteDistribution.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | /**
4 | * Discrete probability distribution.
5 | */
6 | public interface DiscreteDistribution {
7 | /**
8 | * Calculates cumulative distribution function.
9 | */
10 | double cdf(int x);
11 |
12 | /**
13 | * Calculates inverse distribution function.
14 | */
15 | int inverseCdf(double p);
16 | }
17 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/distribution/ContinuousDistribution.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | /**
4 | * Continuous probability distribution.
5 | */
6 | public interface ContinuousDistribution {
7 | /**
8 | * Calculates cumulative distribution function.
9 | */
10 | double cdf(double x);
11 |
12 | /**
13 | * Calculates inverse distribution function.
14 | */
15 | double inverseCdf(double p);
16 | }
17 |
--------------------------------------------------------------------------------
/benchmark/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'me.champeau.gradle.jmh' version '0.4.8'
3 | }
4 |
5 | dependencies {
6 | jmh group: 'org.apache.commons', name: 'commons-math3', version: '3.6'
7 | jmh project(':fast-rng')
8 | }
9 |
10 | jmh {
11 | fork = 3
12 | humanOutputFile = project.file("${project.buildDir}/reports/jmh/human.txt")
13 | resultsFile = project.file("${project.buildDir}/reports/jmh/results.txt")
14 | resultFormat = 'CSV'
15 | // threads = 100
16 | }
17 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/util/ParameterPool.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.util;
2 |
3 | import biz.k11i.rng.ExponentialRNG;
4 | import biz.k11i.rng.util.MtRandom;
5 |
6 | public class ParameterPool {
7 | private double[] parameters;
8 | private int index;
9 |
10 | public ParameterPool(int seed, int count, double theta) {
11 | parameters = new double[count];
12 | MtRandom r = new MtRandom(seed);
13 |
14 | for (int i = 0; i < count; i++) {
15 | parameters[i] = Math.nextUp(ExponentialRNG.FAST_RNG.generate(r, theta));
16 | }
17 | }
18 |
19 | public double next() {
20 | if (index >= parameters.length) {
21 | index = 0;
22 | }
23 |
24 | return parameters[index++];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/RELEASE-NOTES.md:
--------------------------------------------------------------------------------
1 | # Release notes
2 |
3 | ## 0.2.0
4 |
5 | - Fast-rng now requires Java 8 or later.
6 | - Introduces a new class [`UniformRNGUtils`](fast-rng/src/main/java/biz/k11i/rng/UniformRNGUtils.java).
7 | - The method `UniformRNGUtils#nextInt(java.util.Random random, int bound)` is faster than [`Random#nextInt(int bound)`](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#nextInt-int-).
8 |
9 | ## 0.1.5
10 |
11 | - Support beta random number generator
12 |
13 | ## 0.1.4
14 |
15 | - Improve speed performance of `GammaRNG`
16 |
17 | ## 0.1.3
18 |
19 | - Support exponential random number generator
20 |
21 | ## 0.1.2
22 |
23 | - Provide two implementations of Ziggurat algorithm for Gaussian RNG
24 | - `GaussianRNG.FAST_RNG` and `GaussianRNG.GENERAL_RNG`
25 |
26 | ## 0.1.1
27 |
28 | - Support gamma random number generator
29 |
30 |
31 | ## 0.1.0
32 |
33 | - Initial release
34 | - Support gaussian random number generator
35 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | executors:
4 | builder:
5 | docker:
6 | - image: circleci/openjdk:8-jdk
7 | working_directory:
8 | /tmp/workspace
9 |
10 | jobs:
11 | test:
12 | executor: builder
13 | steps:
14 | - checkout
15 | - restore_cache:
16 | key: gradle-cache
17 | - run:
18 | name: Run tests
19 | command: ./gradlew fast-rng:test
20 | - run:
21 | name: Save test results
22 | command: |
23 | mkdir -p ~/test-results/junit/
24 | find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \;
25 | when: always
26 | - store_test_results:
27 | path: ~/test-results
28 | - store_artifacts:
29 | path: ~/test-results/junit
30 | - save_cache:
31 | paths:
32 | - ~/.gradle
33 | key: gradle-cache
34 |
35 | workflows:
36 | test:
37 | jobs:
38 | - test
39 |
--------------------------------------------------------------------------------
/fast-rng-test/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java-library'
3 | id 'me.champeau.gradle.jmh' version '0.4.8'
4 | }
5 |
6 | repositories {
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | api platform(group: 'org.junit', name: 'junit-bom', version: '5.5.2')
12 | api group: 'org.junit.jupiter', name: 'junit-jupiter-api'
13 | api group: 'org.junit.jupiter', name: 'junit-jupiter-params'
14 | api group: 'org.junit.jupiter', name: 'junit-jupiter-engine'
15 | api group: 'org.assertj', name: 'assertj-core', version: '3.11.1'
16 | api group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'
17 | api group: 'net.jafama', name: 'jafama', version: '2.3.1'
18 | api group: 'org.slf4j', name: 'slf4j-api', version: '1.7.28'
19 | api group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.28'
20 | }
21 |
22 | test {
23 | useJUnitPlatform()
24 | }
25 |
26 | jmh {
27 | // fork = 1
28 | // warmup = '1s'
29 | // timeOnIteration = '1s'
30 | }
31 |
--------------------------------------------------------------------------------
/fast-rng/src/test/java/biz/k11i/rng/GaussianRNGTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
4 | import biz.k11i.rng.test.SecondLevelTest;
5 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
6 | import org.junit.jupiter.api.Test;
7 |
8 | class GaussianRNGTest {
9 | @Test
10 | void testFast() {
11 | test(GaussianRNG.FAST_RNG);
12 | }
13 |
14 | @Test
15 | void testGeneral() {
16 | test(GaussianRNG.GENERAL_RNG);
17 | }
18 |
19 | private void test(GaussianRNG rng) {
20 | GoodnessOfFitTest gofTest = GoodnessOfFitTest.continuous()
21 | .probabilityDistribution(ProbabilityDistributions.gaussian(0.0, 1.0))
22 | .randomNumberGenerator("Gaussian", rng::generate)
23 | .numRandomValues(2_000_000)
24 | .build();
25 |
26 | SecondLevelTest.builder()
27 | .numIterations(20)
28 | .build()
29 | .testAndVerify(gofTest);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/util/UniformRandomSupplier.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.util;
2 |
3 | import org.apache.commons.math3.random.MersenneTwister;
4 | import org.apache.commons.math3.random.RandomGenerator;
5 |
6 | import java.util.Random;
7 | import java.util.concurrent.ThreadLocalRandom;
8 |
9 | public enum UniformRandomSupplier {
10 | THREAD_LOCAL_RANDOM() {
11 | @Override
12 | Random newRandom() {
13 | return ThreadLocalRandom.current();
14 | }
15 |
16 | @Override
17 | RandomGenerator newRandomGenerator() {
18 | return new ThreadLocalRandomGenerator();
19 | }
20 | },
21 | MERSENNE_TWISTER() {
22 | @Override
23 | Random newRandom() {
24 | return new MtRandom();
25 | }
26 |
27 | @Override
28 | RandomGenerator newRandomGenerator() {
29 | return new MersenneTwister();
30 | }
31 | }
32 | ;
33 |
34 | abstract Random newRandom();
35 |
36 | abstract RandomGenerator newRandomGenerator();
37 | }
38 |
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/SecondLevelTestTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test;
2 |
3 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
4 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.util.Random;
8 |
9 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
10 |
11 | class SecondLevelTestTest {
12 | @Test
13 | void test() {
14 | GoodnessOfFitTest gofTest = GoodnessOfFitTest.continuous()
15 | .probabilityDistribution(ProbabilityDistributions.gaussian(0.0, 1.002 /* not 1.0 */))
16 | .numRandomValues(1_000_000)
17 | .randomNumberGenerator("buggy", Random::nextGaussian)
18 | .build();
19 |
20 | SecondLevelTest secondLevelTest = SecondLevelTest.builder()
21 | .numIterations(20)
22 | .build();
23 |
24 | assertThatThrownBy(() -> secondLevelTest.testAndVerify(gofTest))
25 | .isInstanceOf(AssertionError.class);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 KOMIYA Atsushi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/fast-rng/src/test/java/biz/k11i/rng/UniformRNGUtilsTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.test.SecondLevelTest;
4 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
5 | import org.apache.commons.math3.distribution.UniformIntegerDistribution;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 |
9 | import java.util.stream.Stream;
10 |
11 | class UniformRNGUtilsTest {
12 | static Stream bounds() {
13 | return Stream.of(3, 7, 997, 100_000, (1 << 30) + (1 << 29), Integer.MAX_VALUE - 1);
14 | }
15 |
16 | @ParameterizedTest
17 | @MethodSource("bounds")
18 | void test(int bound) {
19 | GoodnessOfFitTest gofTest = GoodnessOfFitTest.discrete()
20 | .probabilityDistribution(new UniformIntegerDistribution(0, bound - 1))
21 | .randomNumberGenerator(String.format("Nearly divisionless nextInt(%d)", bound), r -> UniformRNGUtils.nextInt(r, bound))
22 | .numRandomValues(2_000_000)
23 | .maxFrequencyBins(1000)
24 | .build();
25 |
26 | SecondLevelTest.builder()
27 | .numIterations(20)
28 | .build()
29 | .testAndVerify(gofTest);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/ExponentialBenchmark.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.util.ThreadLocalRandomGenerator;
4 | import org.apache.commons.math3.distribution.ExponentialDistribution;
5 | import org.openjdk.jmh.annotations.Benchmark;
6 | import org.openjdk.jmh.annotations.Scope;
7 | import org.openjdk.jmh.annotations.Setup;
8 | import org.openjdk.jmh.annotations.State;
9 |
10 | import java.util.concurrent.ThreadLocalRandom;
11 |
12 | @State(Scope.Benchmark)
13 | public class ExponentialBenchmark {
14 | private ExponentialDistribution exponentialDistribution;
15 |
16 | @Setup
17 | public void setUp() {
18 | exponentialDistribution = new ExponentialDistribution(
19 | new ThreadLocalRandomGenerator(),
20 | 1.0,
21 | ExponentialDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
22 | }
23 |
24 | @Benchmark
25 | public double commonsMath3_algorithmSA() {
26 | return exponentialDistribution.sample();
27 | }
28 |
29 | @Benchmark
30 | public double fastRng_fast() {
31 | return ExponentialRNG.FAST_RNG.generate(ThreadLocalRandom.current(), 1.0);
32 | }
33 |
34 | @Benchmark
35 | public double fastRng_general() {
36 | return ExponentialRNG.GENERAL_RNG.generate(ThreadLocalRandom.current(), 1.0);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/GaussianBenchmark.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import org.openjdk.jmh.annotations.Benchmark;
4 | import org.openjdk.jmh.annotations.Scope;
5 | import org.openjdk.jmh.annotations.State;
6 |
7 | import java.util.Random;
8 | import java.util.concurrent.ThreadLocalRandom;
9 |
10 | @State(Scope.Thread)
11 | public class GaussianBenchmark {
12 | private static Random javaUtilRandom = new Random();
13 |
14 | @Benchmark
15 | public double javaUtilRandom() {
16 | return javaUtilRandom.nextGaussian();
17 | }
18 |
19 | @Benchmark
20 | public double threadLocalRandom() {
21 | return ThreadLocalRandom.current().nextGaussian();
22 | }
23 |
24 | @Benchmark
25 | public double fastRngWithThreadLocalRandom() {
26 | return GaussianRNG.FAST_RNG.generate(ThreadLocalRandom.current());
27 | }
28 |
29 | @Benchmark
30 | public double fastRngWithJavaUtilRandom() {
31 | return GaussianRNG.FAST_RNG.generate(javaUtilRandom);
32 | }
33 |
34 | @Benchmark
35 | public double generalRngWithThreadLocalRandom() {
36 | return GaussianRNG.GENERAL_RNG.generate(ThreadLocalRandom.current());
37 | }
38 |
39 | @Benchmark
40 | public double generalRngWithJavaUtilRandom() {
41 | return GaussianRNG.GENERAL_RNG.generate(javaUtilRandom);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/fast-rng/src/test/java/biz/k11i/rng/ExponentialRNGTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.test.SecondLevelTest;
4 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
5 | import org.apache.commons.math3.distribution.ExponentialDistribution;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 |
9 | import java.util.stream.Stream;
10 |
11 | class ExponentialRNGTest {
12 | static Stream parameter() {
13 | return Stream.of(0.01, 1.0, 100.0);
14 | }
15 |
16 | @ParameterizedTest
17 | @MethodSource("parameter")
18 | void testFast(double theta) {
19 | test(ExponentialRNG.FAST_RNG, theta);
20 | }
21 |
22 | @ParameterizedTest
23 | @MethodSource("parameter")
24 | void testGeneral(double theta) {
25 | test(ExponentialRNG.GENERAL_RNG, theta);
26 | }
27 |
28 | private void test(ExponentialRNG rng, double theta) {
29 | GoodnessOfFitTest gofTest = GoodnessOfFitTest.continuous()
30 | .probabilityDistribution(new ExponentialDistribution(theta))
31 | .randomNumberGenerator(String.format("Exp(%f)", theta), r -> rng.generate(r, theta))
32 | .numRandomValues(2_000_000)
33 | .build();
34 |
35 | SecondLevelTest.builder()
36 | .numIterations(20)
37 | .build()
38 | .testAndVerify(gofTest);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/util/MtRandom.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.util;
2 |
3 | import org.apache.commons.math3.random.MersenneTwister;
4 |
5 | import java.util.Random;
6 |
7 | public class MtRandom extends Random {
8 | private final MersenneTwister mersenneTwister;
9 |
10 | public MtRandom() {
11 | mersenneTwister = new MersenneTwister();
12 | }
13 |
14 | public MtRandom(int seed) {
15 | mersenneTwister = new MersenneTwister(seed);
16 | }
17 |
18 | @Override
19 | public boolean nextBoolean() {
20 | return mersenneTwister.nextBoolean();
21 | }
22 |
23 | @Override
24 | public void nextBytes(byte[] bytes) {
25 | mersenneTwister.nextBytes(bytes);
26 | }
27 |
28 | @Override
29 | public double nextDouble() {
30 | return mersenneTwister.nextDouble();
31 | }
32 |
33 | @Override
34 | public float nextFloat() {
35 | return mersenneTwister.nextFloat();
36 | }
37 |
38 | @Override
39 | public double nextGaussian() {
40 | return mersenneTwister.nextGaussian();
41 | }
42 |
43 | @Override
44 | public int nextInt() {
45 | return mersenneTwister.nextInt();
46 | }
47 |
48 | @Override
49 | public int nextInt(int n) throws IllegalArgumentException {
50 | return mersenneTwister.nextInt(n);
51 | }
52 |
53 | @Override
54 | public long nextLong() {
55 | return mersenneTwister.nextLong();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/fast-rng/src/test/java/biz/k11i/rng/GammaRNGTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.test.SecondLevelTest;
4 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
5 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 |
9 | import java.util.stream.Stream;
10 |
11 | class GammaRNGTest {
12 | private static final double SCALE = 0.01;
13 |
14 | static Stream parameter() {
15 | return Stream.of(0.05, 0.1, 0.5, 0.9, 1.0, 1.1, 50.0, 10000.0);
16 | }
17 |
18 | @ParameterizedTest
19 | @MethodSource("parameter")
20 | void testFast(double shape) {
21 | test(GammaRNG.FAST_RNG, shape);
22 | }
23 |
24 | @ParameterizedTest
25 | @MethodSource("parameter")
26 | void testGeneral(double shape) {
27 | test(GammaRNG.GENERAL_RNG, shape);
28 | }
29 |
30 | private void test(GammaRNG rng, double shape) {
31 | GoodnessOfFitTest gofTest = GoodnessOfFitTest.continuous()
32 | .probabilityDistribution(ProbabilityDistributions.gamma(shape, SCALE))
33 | .randomNumberGenerator(String.format("Gamma(%f, %f)", shape, SCALE), r -> rng.generate(r, shape, SCALE))
34 | .numRandomValues(2_000_000)
35 | .build();
36 |
37 | SecondLevelTest.builder()
38 | .numIterations(20)
39 | .build()
40 | .testAndVerify(gofTest);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/util/inference/AndersonDarlingTestTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.inference;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.SplittableRandom;
6 | import java.util.stream.IntStream;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | class AndersonDarlingTestTest {
11 | @Test
12 | void testNonuniformityDetectin() {
13 | double[] x = new double[1000];
14 |
15 | assertThat(AndersonDarlingTest.andersonDarlingTest(x))
16 | .isGreaterThanOrEqualTo(0.0)
17 | .isLessThan(1e-5);
18 | }
19 |
20 | @Test
21 | void testUniformityDetection() {
22 | double[] x = new double[1000];
23 | for (int i = 0; i < x.length; i++) {
24 | x[i] = (i) / (double) (x.length);
25 | }
26 |
27 | assertThat(AndersonDarlingTest.andersonDarlingTest(x))
28 | .isGreaterThan(1.0 - 1e-5)
29 | .isLessThanOrEqualTo(1.0);
30 | }
31 |
32 | @Test
33 | void testRandom() {
34 | SplittableRandom r = new SplittableRandom(1);
35 | double[] pValues = IntStream.range(0, 100)
36 | .mapToDouble(ignore -> AndersonDarlingTest.andersonDarlingTest(r.doubles(100).sorted().toArray()))
37 | .peek(System.out::println)
38 | .sorted()
39 | .toArray();
40 |
41 | assertThat(AndersonDarlingTest.andersonDarlingTest(pValues))
42 | .isGreaterThan(1e-5)
43 | .isLessThan(1.0 - 1e-5);
44 | }
45 | }
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/util/distribution/ProbabilityDistributionTestBase.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.distribution.RealDistribution;
4 |
5 | import java.util.function.DoubleFunction;
6 | import java.util.function.IntToDoubleFunction;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 |
10 | class ProbabilityDistributionTestBase {
11 | void testCdf(
12 | ContinuousDistribution sut,
13 | RealDistribution referenceImpl,
14 | int n,
15 | IntToDoubleFunction f,
16 | String description,
17 | DoubleFunction descriptionArgsGenerator) {
18 |
19 | for (int i = 0; i <= n; i++) {
20 | double x = f.applyAsDouble(i);
21 | assertEquals(
22 | referenceImpl.cumulativeProbability(x),
23 | sut.cdf(x),
24 | () -> String.format(description, descriptionArgsGenerator.apply(x)));
25 | }
26 | }
27 |
28 | void testInverseCdf(
29 | ContinuousDistribution sut,
30 | RealDistribution referenceImpl,
31 | int n,
32 | String description,
33 | DoubleFunction descriptionArgsGenerator) {
34 |
35 | for (int i = 0; i <= n; i++) {
36 | double p = i / (double) n;
37 | assertEquals(
38 | referenceImpl.inverseCumulativeProbability(p),
39 | sut.inverseCdf(p),
40 | () -> String.format(description, descriptionArgsGenerator.apply(p)));
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/util/ThreadLocalRandomGenerator.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.util;
2 |
3 | import org.apache.commons.math3.random.RandomGenerator;
4 |
5 | import java.util.concurrent.ThreadLocalRandom;
6 |
7 | public class ThreadLocalRandomGenerator implements RandomGenerator {
8 | @Override
9 | public void setSeed(int seed) {
10 | ThreadLocalRandom.current().setSeed(seed);
11 | }
12 |
13 | @Override
14 | public void setSeed(int[] seed) {
15 | throw new UnsupportedOperationException();
16 | }
17 |
18 | @Override
19 | public void setSeed(long seed) {
20 | ThreadLocalRandom.current().setSeed(seed);
21 | }
22 |
23 | @Override
24 | public void nextBytes(byte[] bytes) {
25 | ThreadLocalRandom.current().nextBytes(bytes);
26 | }
27 |
28 | @Override
29 | public int nextInt() {
30 | return ThreadLocalRandom.current().nextInt();
31 | }
32 |
33 | @Override
34 | public int nextInt(int n) {
35 | return ThreadLocalRandom.current().nextInt(n);
36 | }
37 |
38 | @Override
39 | public long nextLong() {
40 | return ThreadLocalRandom.current().nextLong();
41 | }
42 |
43 | @Override
44 | public boolean nextBoolean() {
45 | return ThreadLocalRandom.current().nextBoolean();
46 | }
47 |
48 | @Override
49 | public float nextFloat() {
50 | return ThreadLocalRandom.current().nextFloat();
51 | }
52 |
53 | @Override
54 | public double nextDouble() {
55 | return ThreadLocalRandom.current().nextDouble();
56 | }
57 |
58 | @Override
59 | public double nextGaussian() {
60 | return ThreadLocalRandom.current().nextGaussian();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/util/inference/MTestTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.inference;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.Arrays;
6 | import java.util.SplittableRandom;
7 | import java.util.stream.IntStream;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | class MTestTest {
12 | @Test
13 | void testOutlierDetection() {
14 | final int N = 100;
15 |
16 | double[] probs = new double[N];
17 | Arrays.fill(probs, 1.0 / N);
18 |
19 | long[] freqs = new long[N];
20 | Arrays.fill(freqs, 100);
21 | freqs[0] = 40;
22 |
23 | assertThat(MTest.mTest(probs, freqs))
24 | .isGreaterThanOrEqualTo(0.0)
25 | .isLessThan(1e-5);
26 | }
27 |
28 | @Test
29 | void testRandom() {
30 | final int N = 100;
31 |
32 | double[] probs = new double[N];
33 | Arrays.fill(probs, 1.0 / N);
34 |
35 | SplittableRandom r = new SplittableRandom(1);
36 | double[] pValues = IntStream.range(0, 100)
37 | .mapToDouble(ignore -> {
38 | long[] freqs = new long[N];
39 | r.ints(1000, 0, N).forEach(x -> {
40 | freqs[x]++;
41 | });
42 | return MTest.mTest(probs, freqs);
43 | })
44 | .peek(System.out::println)
45 | .sorted()
46 | .toArray();
47 |
48 | // Test uniformity of p-values by Anderson-Darling test
49 | assertThat(AndersonDarlingTest.andersonDarlingTest(pValues))
50 | .isGreaterThan(1e-5)
51 | .isLessThan(1.0 - 1e-5);
52 | }
53 | }
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/util/distribution/BetaTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.distribution.BetaDistribution;
4 | import org.junit.jupiter.params.ParameterizedTest;
5 | import org.junit.jupiter.params.provider.Arguments;
6 | import org.junit.jupiter.params.provider.MethodSource;
7 |
8 | import java.util.Arrays;
9 | import java.util.stream.Stream;
10 |
11 | class BetaTest extends ProbabilityDistributionTestBase {
12 | private static final int N = 10000;
13 |
14 | private static Stream distributionParameters() {
15 | double[] values = {0.001, 0.1, 0.5, 0.999, 1.0, 1.1, 5.5, 16.0, 256.0, 1024.0};
16 |
17 | return Arrays.stream(values)
18 | .boxed()
19 | .flatMap(alpha -> Arrays.stream(values).mapToObj(beta -> Arguments.of(alpha, beta)));
20 | }
21 |
22 | @ParameterizedTest
23 | @MethodSource("distributionParameters")
24 | void testCdf(double alpha, double beta) {
25 | testCdf(
26 | ProbabilityDistributions.beta(alpha, beta),
27 | new BetaDistribution(alpha, beta),
28 | N,
29 | i -> i / (double) N,
30 | "Beta(%f, %f).cdf(%f)",
31 | x -> new Object[]{alpha, beta, x});
32 | }
33 |
34 | @ParameterizedTest
35 | @MethodSource("distributionParameters")
36 | void testInverseCdf(double alpha, double beta) {
37 | testInverseCdf(
38 | ProbabilityDistributions.beta(alpha, beta),
39 | new BetaDistribution(alpha, beta),
40 | N,
41 | "Beta(%f, %f).inverseCDF(%f)",
42 | p -> new Object[]{alpha, beta, p});
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/util/distribution/GammaTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.distribution.GammaDistribution;
4 | import org.apache.commons.math3.util.FastMath;
5 | import org.junit.jupiter.params.ParameterizedTest;
6 | import org.junit.jupiter.params.provider.Arguments;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 |
9 | import java.util.Arrays;
10 | import java.util.function.IntToDoubleFunction;
11 | import java.util.stream.Stream;
12 |
13 | class GammaTest extends ProbabilityDistributionTestBase {
14 | private static final int N = 10000;
15 |
16 | private static Stream distributionParameters() {
17 | double[] values = {0.001, 0.1, 0.5, 0.999, 1.0, 1.1, 5.5, 16.0, 256.0, 1024.0};
18 |
19 | return Arrays.stream(values)
20 | .boxed()
21 | .flatMap(shape -> Arrays.stream(values).mapToObj(scale -> Arguments.of(shape, scale)));
22 | }
23 |
24 | @ParameterizedTest
25 | @MethodSource("distributionParameters")
26 | void testCdf(double shape, double scale) {
27 | IntToDoubleFunction f = i -> (i == 0 ? 0.0 : FastMath.pow(10, (double) (i - 5000) / 100));
28 | testCdf(
29 | ProbabilityDistributions.gamma(shape, scale),
30 | new GammaDistribution(shape, scale),
31 | N,
32 | f,
33 | "Gamma(%f, %f).cdf(%f)",
34 | x -> new Object[]{shape, scale, x});
35 | }
36 |
37 | @ParameterizedTest
38 | @MethodSource("distributionParameters")
39 | void testInverseCdf(double shape, double scale) {
40 | testInverseCdf(
41 | ProbabilityDistributions.gamma(shape, scale),
42 | new GammaDistribution(shape, scale),
43 | N,
44 | "Gamma(%f, %f).inverseCdf(%f)",
45 | p -> new Object[]{shape, scale, p});
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/inference/MTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.inference;
2 |
3 | import biz.k11i.rng.test.util.distribution.ContinuousDistribution;
4 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
5 | import org.apache.commons.math3.exception.DimensionMismatchException;
6 |
7 | /**
8 | * Implements M-test to detect outliers in contingency table.
9 | *
10 | *
11 | * Fuchs, Camil, and Ron Kenett.
12 | * "A test for detecting outlying cells in the multinomial distribution and two-way contingency tables."
13 | * Journal of the American Statistical Association 75.370 (1980): 395-398.
14 | *
15 | */
16 | public class MTest {
17 | private static final ContinuousDistribution GAUSSIAN = ProbabilityDistributions.gaussian(0, 1);
18 |
19 | /**
20 | * Tests for detecting outliers in array.
21 | *
22 | * @param probs array of expected probabilities or expected frequency counts
23 | * @param freqs array of observed frequency counts
24 | * @return p-value
25 | */
26 | public static double mTest(final double[] probs, final long[] freqs) {
27 | if (probs.length != freqs.length) {
28 | throw new DimensionMismatchException(probs.length, freqs.length);
29 | }
30 |
31 | int k = probs.length;
32 | double pSum = 0;
33 | long fSum = 0;
34 |
35 | for (int i = 0; i < k; i++) {
36 | pSum += probs[i];
37 | fSum += freqs[i];
38 | }
39 |
40 | double maxZ = Double.NEGATIVE_INFINITY;
41 | for (int i = 0; i < k; i++) {
42 | double mean = fSum * (probs[i] / pSum);
43 | double var = mean * (1 - probs[i] / pSum);
44 |
45 | double z = Math.abs((freqs[i] - mean) / Math.sqrt(var));
46 | if (z > maxZ) {
47 | maxZ = z;
48 | }
49 | }
50 |
51 | return 1 - Math.pow((2 * GAUSSIAN.cdf(maxZ) - 1), k);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/NextIntWithBoundBenchmark.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import org.openjdk.jmh.annotations.Benchmark;
4 | import org.openjdk.jmh.annotations.Param;
5 | import org.openjdk.jmh.annotations.Scope;
6 | import org.openjdk.jmh.annotations.Setup;
7 | import org.openjdk.jmh.annotations.State;
8 |
9 | import java.util.SplittableRandom;
10 | import java.util.concurrent.ThreadLocalRandom;
11 | import java.util.stream.IntStream;
12 |
13 | public class NextIntWithBoundBenchmark {
14 | @State(Scope.Benchmark)
15 | public static class FixedBound {
16 | @Param({"1025", // 2^10 + 1
17 | "1048577", // 2^20 + 1
18 | "1073741828", // 2^30 + 4
19 | })
20 | private int bound;
21 |
22 | @Benchmark
23 | public int jdk() {
24 | return ThreadLocalRandom.current().nextInt(bound);
25 | }
26 |
27 | @Benchmark
28 | public int nearlyDivisionless() {
29 | return UniformRNGUtils.nextInt(ThreadLocalRandom.current(), bound);
30 | }
31 | }
32 |
33 | @State(Scope.Benchmark)
34 | public static class ArbitraryBounds {
35 | private static final int NUM_BOUNDS = 1 << 10;
36 | private int[] bounds;
37 | private int index;
38 |
39 | @Setup
40 | public void setUp() {
41 | SplittableRandom r = new SplittableRandom(12345);
42 | bounds = IntStream
43 | .generate(() -> r.nextInt(Integer.MAX_VALUE - 1) + 1)
44 | .limit(NUM_BOUNDS)
45 | .toArray();
46 | }
47 |
48 | @Benchmark
49 | public int jdk() {
50 | index = (index + 1) & (NUM_BOUNDS - 1);
51 | return ThreadLocalRandom.current().nextInt(bounds[index]);
52 | }
53 |
54 | @Benchmark
55 | public int nearlyDivisionless() {
56 | index = (index + 1) & (NUM_BOUNDS - 1);
57 | return UniformRNGUtils.nextInt(ThreadLocalRandom.current(), bounds[index]);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/fast-rng-test/src/test/java/biz/k11i/rng/test/util/distribution/GaussianTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.distribution.NormalDistribution;
4 | import org.apache.commons.math3.util.FastMath;
5 | import org.junit.jupiter.params.ParameterizedTest;
6 | import org.junit.jupiter.params.provider.Arguments;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 |
9 | import java.util.Arrays;
10 | import java.util.function.IntToDoubleFunction;
11 | import java.util.stream.Stream;
12 |
13 | class GaussianTest extends ProbabilityDistributionTestBase {
14 | private static final int N = 10000;
15 |
16 | private static Stream distributionParameters() {
17 | double[] means = {0.0, -1.5, 2.5, -1e10, 1e-10};
18 | double[] stdDevs = {1e-10, 0.3, 1.0, 3.4, 1e10};
19 |
20 | return Arrays.stream(means)
21 | .boxed()
22 | .flatMap(mean -> Arrays.stream(stdDevs).mapToObj(sd -> Arguments.of(mean, sd)));
23 | }
24 |
25 | @ParameterizedTest
26 | @MethodSource("distributionParameters")
27 | void testCdf(double mean, double sd) {
28 | IntToDoubleFunction f = value -> {
29 | double hi = (value / 100.0) - 50;
30 | double lo = (value % 100) - 50;
31 |
32 | return hi * FastMath.pow(10, lo / 2);
33 | };
34 |
35 | testCdf(
36 | ProbabilityDistributions.gaussian(mean, sd),
37 | new NormalDistribution(mean, sd),
38 | N,
39 | f,
40 | "Gaussian(%f, %f).cdf(%f)",
41 | x -> new Object[]{mean, sd, x});
42 | }
43 |
44 | @ParameterizedTest
45 | @MethodSource("distributionParameters")
46 | void testInverseCdf(double mean, double sd) {
47 | testInverseCdf(
48 | ProbabilityDistributions.gaussian(mean, sd),
49 | new NormalDistribution(mean, sd),
50 | N,
51 | "Gaussian(%f, %f).inverseCDF(%f)",
52 | p -> new Object[]{mean, sd, p});
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/fast-rng-test/src/jmh/java/biz/k11i/rng/test/util/distribution/BetaDistributionBenchmark.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.distribution.BetaDistribution;
4 | import org.openjdk.jmh.annotations.Benchmark;
5 | import org.openjdk.jmh.annotations.Level;
6 | import org.openjdk.jmh.annotations.OutputTimeUnit;
7 | import org.openjdk.jmh.annotations.Param;
8 | import org.openjdk.jmh.annotations.Scope;
9 | import org.openjdk.jmh.annotations.Setup;
10 | import org.openjdk.jmh.annotations.State;
11 |
12 | import java.util.concurrent.TimeUnit;
13 | import java.util.stream.IntStream;
14 |
15 | @State(Scope.Benchmark)
16 | @OutputTimeUnit(TimeUnit.MILLISECONDS)
17 | public class BetaDistributionBenchmark {
18 | @Param({"0.1", "1.0", "100.0"})
19 | private double alpha;
20 |
21 | @Param({"0.1", "1.0", "100.0"})
22 | private double beta;
23 |
24 | private ContinuousDistribution probDist;
25 | private BetaDistribution commonsMath3;
26 |
27 | @State(Scope.Benchmark)
28 | public static class X {
29 | private final double[] values;
30 | private int index;
31 | private double next;
32 |
33 | public X() {
34 | final int N = 100000;
35 | values = IntStream.rangeClosed(0, N)
36 | .mapToDouble(i -> i / (double) N)
37 | .toArray();
38 | }
39 |
40 | @Setup(Level.Iteration)
41 | public void init() {
42 | index = 0;
43 | }
44 |
45 | @Setup(Level.Invocation)
46 | public void next() {
47 | next = values[index];
48 |
49 | if (++index >= values.length) {
50 | index = 0;
51 | }
52 | }
53 | }
54 |
55 | @Setup(Level.Trial)
56 | public void setUp() {
57 | probDist = ProbabilityDistributions.beta(alpha, beta);
58 | commonsMath3 = new BetaDistribution(alpha, beta);
59 | }
60 |
61 | @Benchmark
62 | public double commonsMath3Cdf(X x) {
63 | return commonsMath3.cumulativeProbability(x.next);
64 | }
65 |
66 | @Benchmark
67 | public double commonsMath3InverseCdf(X x) {
68 | return commonsMath3.inverseCumulativeProbability(x.next);
69 | }
70 |
71 | @Benchmark
72 | public double cdf(X x) {
73 | return probDist.cdf(x.next);
74 | }
75 |
76 | @Benchmark
77 | public double inverseCdf(X x) {
78 | return probDist.inverseCdf(x.next);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/fast-rng/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'maven-publish'
3 | id 'net.researchgate.release' version '2.8.0'
4 | id 'com.jfrog.bintray' version '1.8.4'
5 | }
6 |
7 | repositories {
8 | maven {
9 | url 'http://simulation.tudelft.nl/maven/'
10 | }
11 | }
12 |
13 | dependencies {
14 | testImplementation project(':fast-rng-test')
15 | }
16 |
17 | test {
18 | useJUnitPlatform()
19 |
20 | testLogging {
21 | events 'PASSED', 'FAILED', 'SKIPPED'
22 | }
23 | afterSuite { desc, result ->
24 | if (!desc.parent) {
25 | println "\nTest result: ${result.resultType}"
26 | println "Test summary: ${result.testCount} tests, " +
27 | "${result.successfulTestCount} succeeded, " +
28 | "${result.failedTestCount} failed, " +
29 | "${result.skippedTestCount} skipped"
30 | }
31 | }
32 | }
33 |
34 | javadoc {
35 | options.locale = 'en_US'
36 | }
37 |
38 | task sourcesJar(type: Jar, dependsOn: classes) {
39 | archiveClassifier = 'sources'
40 | from sourceSets.main.allSource
41 | }
42 |
43 | task javadocJar(type: Jar, dependsOn: javadoc) {
44 | archiveClassifier = 'javadoc'
45 | from javadoc.destinationDir
46 | }
47 |
48 | artifacts {
49 | archives jar
50 | archives sourcesJar
51 | archives javadocJar
52 | }
53 |
54 | publishing {
55 | publications {
56 | mavenJava(MavenPublication) {
57 | from components.java
58 | artifact sourcesJar
59 | artifact javadocJar
60 | }
61 | }
62 | }
63 |
64 | bintray {
65 | user = project.hasProperty('bintrayUser') ? bintrayUser : ''
66 | key = project.hasProperty('bintrayKey') ? bintrayKey : ''
67 | publications = ['mavenJava']
68 |
69 | pkg {
70 | repo = 'maven'
71 | name = 'fast-rng'
72 | userOrg = 'komiya-atsushi'
73 | licenses = ['MIT']
74 |
75 | websiteUrl = 'https://github.com/komiya-atsushi/fast-rng-java'
76 | issueTrackerUrl = 'https://github.com/komiya-atsushi/fast-rng-java/issues'
77 | vcsUrl = 'https://github.com/komiya-atsushi/fast-rng-java.git'
78 |
79 | version {
80 | name = project.version
81 | desc = 'fast-rng: Fast random number generator for various distributions'
82 | }
83 | }
84 | }
85 |
86 | release {
87 | preTagCommitMessage = '[skip ci] [Gradle Release Plugin] - pre tag commit: '
88 | newVersionCommitMessage = '[skip ci] [Gradle Release Plugin] - new version commit: '
89 | }
90 |
91 | afterReleaseBuild.dependsOn bintrayUpload
92 |
--------------------------------------------------------------------------------
/benchmark/src/jmh/java/biz/k11i/rng/GammaBenchmark.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.util.MtRandom;
4 | import biz.k11i.rng.util.ParameterPool;
5 | import org.apache.commons.math3.distribution.GammaDistribution;
6 | import org.apache.commons.math3.random.MersenneTwister;
7 | import org.openjdk.jmh.annotations.Benchmark;
8 | import org.openjdk.jmh.annotations.Param;
9 | import org.openjdk.jmh.annotations.Scope;
10 | import org.openjdk.jmh.annotations.Setup;
11 | import org.openjdk.jmh.annotations.State;
12 |
13 | import java.util.Random;
14 |
15 | public class GammaBenchmark {
16 | @State(Scope.Benchmark)
17 | public static class FixedParameters {
18 | @Param({"0.05", "0.1", "0.2", "0.5", "0.9", "1.0", "1.1", "40.0", "10000.0"})
19 | public double shape;
20 | public double scale = 1.0;
21 |
22 | private Random random = new MtRandom();
23 | private GammaDistribution gammaDistribution;
24 |
25 | @Setup
26 | public void setUp() {
27 | gammaDistribution = new GammaDistribution(
28 | new MersenneTwister(),
29 | shape,
30 | scale,
31 | GammaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
32 | }
33 |
34 | @Benchmark
35 | public double commonsMath3() {
36 | return gammaDistribution.sample();
37 | }
38 |
39 | @Benchmark
40 | public double fastRng() {
41 | return GammaRNG.FAST_RNG.generate(random, shape, scale);
42 | }
43 |
44 | @Benchmark
45 | public double generalRng() {
46 | return GammaRNG.GENERAL_RNG.generate(random, shape, scale);
47 | }
48 | }
49 |
50 | @State(Scope.Benchmark)
51 | public static class ArbitraryParameters {
52 | private double scale = 1.0;
53 | private Random random = new MtRandom();
54 | private MersenneTwister mersenneTwister = new MersenneTwister();
55 | private ParameterPool parameters = new ParameterPool(12345, 10000, 10.0);
56 |
57 | @Benchmark
58 | public double commonsMath3() {
59 | return new GammaDistribution(mersenneTwister, parameters.next(), scale, GammaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY).sample();
60 | }
61 |
62 | @Benchmark
63 | public double fastRng() {
64 | return GammaRNG.FAST_RNG.generate(random, parameters.next(), scale);
65 | }
66 |
67 | @Benchmark
68 | public double generalRng() {
69 | return GammaRNG.GENERAL_RNG.generate(random, parameters.next(), scale);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/fast-rng/src/main/java/biz/k11i/rng/UniformRNGUtils.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import java.util.Random;
4 | import java.util.SplittableRandom;
5 |
6 | @SuppressWarnings("Duplicates")
7 | public class UniformRNGUtils {
8 | /**
9 | * Returns a random integer sampled from discrete uniform distribution {@code unif{0, bound - 1}}.
10 | *
11 | *
12 | * This implementation uses "nearly divisionless" algorithm by Lemire.
13 | *
14 | *
15 | * Lemire, Daniel.
16 | * "Fast random integer generation in an interval."
17 | * ACM Transactions on Modeling and Computer Simulation (TOMACS) 29.1 (2019): 3.
18 | *
19 | *
20 | * @param random random number generator ({@link Random} object)
21 | * @param bound the upper bound (exclusive)
22 | * @return sampled random integer between 0 (inclusive) and {@code bound} (exclusive)
23 | */
24 | public static int nextInt(Random random, int bound) {
25 | if (bound <= 0) {
26 | throw new IllegalArgumentException("bound must be positive");
27 | }
28 |
29 | long x = ((long) random.nextInt()) & 0xffff_ffffL;
30 | long m = x * bound;
31 | long l = m & 0xffff_ffffL;
32 |
33 | if (l < bound) {
34 | for (long t = 0x1_0000_0000L % bound; l < t; ) {
35 | x = ((long) random.nextInt()) & 0xffff_ffffL;
36 | m = x * bound;
37 | l = m & 0xffff_ffffL;
38 | }
39 | }
40 |
41 | return (int) (m >>> 32);
42 | }
43 |
44 | /**
45 | * Returns a random integer sampled from discrete uniform distribution {@code unif{0, bound - 1}}.
46 | *
47 | *
48 | * This implementation uses "nearly divisionless" algorithm by Lemire.
49 | *
50 | *
51 | * Lemire, Daniel.
52 | * "Fast random integer generation in an interval."
53 | * ACM Transactions on Modeling and Computer Simulation (TOMACS) 29.1 (2019): 3.
54 | *
55 | *
56 | * @param random random number generator ({@link SplittableRandom} object)
57 | * @param bound the upper bound (exclusive)
58 | * @return sampled random integer between 0 (inclusive) and {@code bound} (exclusive)
59 | */
60 | public static int nextInt(SplittableRandom random, int bound) {
61 | if (bound <= 0) {
62 | throw new IllegalArgumentException("bound must be positive");
63 | }
64 |
65 | long x = ((long) random.nextInt()) & 0xffff_ffffL;
66 | long m = x * bound;
67 | long l = m & 0xffff_ffffL;
68 |
69 | if (l < bound) {
70 | for (long t = 0x1_0000_0000L % bound; l < t; ) {
71 | x = ((long) random.nextInt()) & 0xffff_ffffL;
72 | m = x * bound;
73 | l = m & 0xffff_ffffL;
74 | }
75 | }
76 |
77 | return (int) (m >>> 32);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/inference/AndersonDarlingTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.inference;
2 |
3 | import static org.apache.commons.math3.util.FastMath.*;
4 |
5 | /**
6 | * Implements Anderson–Darling test
7 | * for uniformity.
8 | *
9 | * Marsaglia, George, and John Marsaglia.
10 | * "Evaluating the anderson-darling distribution."
11 | * Journal of Statistical Software 9.2 (2004): 1-5.
12 | *
13 | */
14 | public class AndersonDarlingTest {
15 | private static final double EPSILON = Math.ulp(1.0) / 2.0;
16 |
17 | /**
18 | * Tests uniformity of double values.
19 | *
20 | * @param x array of double values that must be sorted in ascending order.
21 | * @return p-value
22 | */
23 | public static double andersonDarlingTest(double[] x) {
24 | final int n = x.length;
25 | double z = 0;
26 | double prev = Double.NEGATIVE_INFINITY;
27 |
28 | for (int i = 0; i < n; i++) {
29 | if (prev > x[i]) {
30 | throw new IllegalArgumentException("Array of double values 'x' must be sorted in ascending order");
31 | }
32 |
33 | double u = x[i], v = x[n - 1 - i];
34 | if (u <= EPSILON) {
35 | u = EPSILON;
36 | } else if (v >= 1.0 - EPSILON) {
37 | v = 1.0 - EPSILON;
38 | }
39 |
40 | double t = u * (1.0 - v);
41 | z = z - (i + i + 1) * log(t);
42 |
43 | prev = x[i];
44 | }
45 |
46 | return 1 - Math.max(AD(n, -n + z / n), 0.0);
47 | }
48 |
49 | private static double AD(int n, double z) {
50 | double c, v, x;
51 | x = adinf(z);
52 | /* now x=adinf(z). Next, get v=errfix(n,x) and return x+v; */
53 | if (x > .8) {
54 | v = (-130.2137 + (745.2337 - (1705.091 - (1950.646 - (1116.360 - 255.7844 * x) * x) * x) * x) * x) / n;
55 | return x + v;
56 | }
57 | c = .01265 + .1757 / n;
58 | if (x < c) {
59 | v = x / c;
60 | v = sqrt(v) * (1. - v) * (49 * v - 102);
61 | return x + v * (.0037 / (n * n) + .00078 / n + .00006) / n;
62 | }
63 | v = (x - c) / (.8 - c);
64 | v = -.00022633 + (6.54034 - (14.6538 - (14.458 - (8.259 - 1.91864 * v) * v) * v) * v) * v;
65 | return x + v * (.04213 + .01365 / n) / n;
66 | }
67 |
68 | private static double adinf(double z) {
69 | if (z < 2.)
70 | return exp(-1.2337141 / z) / sqrt(z) * (2.00012 + (.247105 - (.0649821 - (.0347962 - (.011672 - .00168691 * z) * z) * z) * z) * z);
71 | /* max |error| < .000002 for z<2, (p=.90816...) */
72 | return exp(-exp(1.0776 - (2.30695 - (.43424 - (.082433 - (.008056 - .0003146 * z) * z) * z) * z) * z));
73 | /* max |error|<.0000008 for 4 {
10 | private final SplittableRandom splittableRandom;
11 |
12 | @Override
13 | public SplittableRandomWrapper split() {
14 | return new SplittableRandomWrapper(splittableRandom.split());
15 | }
16 |
17 | private SplittableRandomWrapper(SplittableRandom splittableRandom) {
18 | this.splittableRandom = splittableRandom;
19 | }
20 |
21 | public SplittableRandomWrapper(long seed) {
22 | this.splittableRandom = new SplittableRandom(seed);
23 | }
24 |
25 | @Override
26 | public int nextInt() {
27 | return splittableRandom.nextInt();
28 | }
29 |
30 | @Override
31 | public int nextInt(int bound) {
32 | return splittableRandom.nextInt(bound);
33 | }
34 |
35 | @Override
36 | public long nextLong() {
37 | return splittableRandom.nextLong();
38 | }
39 |
40 | @Override
41 | public double nextDouble() {
42 | return splittableRandom.nextDouble();
43 | }
44 |
45 | @Override
46 | public boolean nextBoolean() {
47 | return splittableRandom.nextBoolean();
48 | }
49 |
50 | @Override
51 | public IntStream ints(long streamSize) {
52 | return splittableRandom.ints(streamSize);
53 | }
54 |
55 | @Override
56 | public IntStream ints() {
57 | return splittableRandom.ints();
58 | }
59 |
60 | @Override
61 | public IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound) {
62 | return splittableRandom.ints(streamSize, randomNumberOrigin, randomNumberBound);
63 | }
64 |
65 | @Override
66 | public IntStream ints(int randomNumberOrigin, int randomNumberBound) {
67 | return splittableRandom.ints(randomNumberOrigin, randomNumberBound);
68 | }
69 |
70 | @Override
71 | public LongStream longs(long streamSize) {
72 | return splittableRandom.longs(streamSize);
73 | }
74 |
75 | @Override
76 | public LongStream longs() {
77 | return splittableRandom.longs();
78 | }
79 |
80 | @Override
81 | public LongStream longs(long streamSize, long randomNumberOrigin, long randomNumberBound) {
82 | return splittableRandom.longs(streamSize, randomNumberOrigin, randomNumberBound);
83 | }
84 |
85 | @Override
86 | public LongStream longs(long randomNumberOrigin, long randomNumberBound) {
87 | return splittableRandom.longs(randomNumberOrigin, randomNumberBound);
88 | }
89 |
90 | @Override
91 | public DoubleStream doubles(long streamSize) {
92 | return splittableRandom.doubles(streamSize);
93 | }
94 |
95 | @Override
96 | public DoubleStream doubles() {
97 | return splittableRandom.doubles();
98 | }
99 |
100 | @Override
101 | public DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) {
102 | return splittableRandom.doubles(streamSize, randomNumberOrigin, randomNumberBound);
103 | }
104 |
105 | @Override
106 | public DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) {
107 | return splittableRandom.doubles(randomNumberOrigin, randomNumberBound);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fast-rng: Fast random number generator for various distributions
2 |
3 | [ ](https://bintray.com/komiya-atsushi/maven/fast-rng/_latestVersion)
4 |
5 | # Getting started
6 |
7 | ## Resolving artifacts using Maven
8 |
9 | ```xml
10 |
11 |
12 | bintray-komiya-atsushi-maven
13 | http://dl.bintray.com/komiya-atsushi/maven
14 |
15 |
16 |
17 |
18 |
19 | biz.k11i
20 | fast-rng
21 | 0.2.0
22 |
23 |
24 | ```
25 |
26 | ## Resolving artifacts using Gradle
27 |
28 | ```groovy
29 | repositories {
30 | maven {
31 | url "http://dl.bintray.com/komiya-atsushi/maven"
32 | }
33 | }
34 |
35 | dependencies {
36 | compile group: 'biz.k11i', name: 'fast-rng', version: '0.2.0'
37 | }
38 | ```
39 |
40 | ## Generating random values
41 |
42 | ```java
43 | package biz.k11i.rng.demo;
44 |
45 | import java.util.Random;
46 |
47 | public class FastRngDemo {
48 | public static void main(String[] args) {
49 | // Fast-rng requires a java.util.Random instance to generate
50 | // uniformly distributed random values.
51 | Random random = new Random();
52 |
53 | System.out.println(
54 | // Generate a gaussian random value.
55 | GaussianRNG.FAST_RNG.generate(random)
56 | );
57 | }
58 | }
59 | ```
60 |
61 |
62 | # Supported distributions
63 |
64 | - [Gaussian distribution (Normal distribution)](https://en.wikipedia.org/wiki/Normal_distribution)
65 | - [Exponential distribution](https://en.wikipedia.org/wiki/Exponential_distribution)
66 | - [Gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution)
67 | - [Beta distribution](https://en.wikipedia.org/wiki/Beta_distribution)
68 |
69 |
70 | # License
71 |
72 | This software is licensed under the terms of the MIT license. See LICENSE.
73 |
74 |
75 | # Acknowledgments
76 |
77 | ## commons-math3
78 |
79 | This product includes software developed at
80 | The Apache Software Foundation (http://www.apache.org/).
81 | https://github.com/apache/commons-math
82 |
83 | ## Jafama
84 |
85 | This product includes software developed by Jeff Hain.
86 | https://github.com/jeffhain/jafama
87 |
88 |
89 | # References
90 |
91 | - Tesuaki Yotsuji.
92 | *計算機シミュレーションのための確率分布乱数生成法.*
93 | Pleiades Publishing Co.,Ltd. (2010)
94 | - Marsaglia, George, and Wai Wan Tsang.
95 | *The ziggurat method for generating random variables.*
96 | Journal of statistical software 5.8 (2000): 1-7.
97 | - Ahrens, Joachim H., and Ulrich Dieter.
98 | *Computer methods for sampling from gamma, beta, poisson and bionomial distributions.*
99 | Computing 12.3 (1974): 223-246.
100 | - Marsaglia, George, and Wai Wan Tsang.
101 | *A simple method for generating gamma variables.*
102 | ACM Transactions on Mathematical Software (TOMS) 26.3 (2000): 363-372.
103 | - Best, D. J.
104 | *A note on gamma variate generators with shape parameter less than unity.*
105 | Computing 30.2 (1983): 185-188.
106 | - Wilson, Edwin B., and Margaret M. Hilferty.
107 | *The distribution of chi-square.*
108 | Proceedings of the National Academy of Sciences 17.12 (1931): 684-688.
109 | - Jöhnk, M. D.
110 | *Erzeugung von betaverteilten und gammaverteilten Zufallszahlen.*
111 | Metrika 8.1 (1964): 5-15.
112 | - Sakasegawa, H.
113 | *Stratified rejection and squeeze method for generating beta random numbers.*
114 | Annals of the Institute of Statistical Mathematics 35.1 (1983): 291-302.
115 | - Lemire, Daniel.
116 | *Fast random integer generation in an interval.*
117 | ACM Transactions on Modeling and Computer Simulation (TOMACS) 29.1 (2019): 3.
118 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/SecondLevelTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test;
2 |
3 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
4 | import biz.k11i.rng.test.util.inference.AndersonDarlingTest;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.util.LinkedHashMap;
9 | import java.util.Map;
10 | import java.util.concurrent.ForkJoinPool;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.stream.Collector;
13 | import java.util.stream.IntStream;
14 |
15 | import static java.util.stream.Collectors.collectingAndThen;
16 | import static java.util.stream.Collectors.groupingBy;
17 | import static java.util.stream.Collectors.mapping;
18 | import static java.util.stream.Collectors.toList;
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | public class SecondLevelTest {
22 | public static class Builder {
23 | private int numIterations;
24 |
25 | public Builder numIterations(int numIterations) {
26 | this.numIterations = numIterations;
27 | return this;
28 | }
29 |
30 | public SecondLevelTest build() {
31 | return new SecondLevelTest(numIterations);
32 | }
33 | }
34 |
35 | private static final Logger LOGGER = LoggerFactory.getLogger(SecondLevelTest.class);
36 |
37 | private final int numIterations;
38 |
39 | private SecondLevelTest(int numIterations) {
40 | this.numIterations = numIterations;
41 | }
42 |
43 | public void testAndVerify(GoodnessOfFitTest gofTest) {
44 | boolean parallel = Runtime.getRuntime().availableProcessors() > 1;
45 |
46 | Map results = test(gofTest, parallel);
47 | assertThat(results).isNotEmpty();
48 |
49 | results.forEach((t, p) ->
50 | assertThat(p)
51 | .describedAs("At a significance level of %e, the second level Goodness-of-Fit test of [%s] should fail to reject null hypothesis.\n" +
52 | "This means that the random sequence generated by the random number generator [%s] fit the theoretical probability distribution.",
53 | gofTest.significanceLevel, t, gofTest.rngName)
54 | .isGreaterThanOrEqualTo(0.001));
55 | }
56 |
57 | private Map test(GoodnessOfFitTest gofTest, boolean parallel) {
58 | ForkJoinPool pool = parallel ? new ForkJoinPool() : null;
59 |
60 | try {
61 | Map result = IntStream.range(0, numIterations)
62 | .peek(i -> LOGGER.info("First level #{}", i + 1))
63 | .mapToObj(ignore -> parallel ? gofTest.testInParallel(pool) : gofTest.test())
64 | .flatMap(r -> r.entrySet().stream())
65 | .collect(groupingBy(
66 | Map.Entry::getKey,
67 | LinkedHashMap::new,
68 | mapping(Map.Entry::getValue, computeAndersonDarlingP())));
69 |
70 | result.forEach((key, value) -> LOGGER.info("[{}] Second level p-value: {}", key, value));
71 |
72 | return result;
73 |
74 | } finally {
75 | if (pool != null) {
76 | pool.shutdown();
77 | try {
78 | pool.awaitTermination(2, TimeUnit.SECONDS);
79 | } catch (InterruptedException ignore) {
80 | }
81 | }
82 | }
83 | }
84 |
85 | private static Collector toDoubleArray() {
86 | return collectingAndThen(toList(), l -> l.stream().mapToDouble(v -> v).sorted().toArray());
87 | }
88 |
89 | private static Collector computeAndersonDarlingP() {
90 | return collectingAndThen(
91 | toDoubleArray(),
92 | AndersonDarlingTest::andersonDarlingTest);
93 | }
94 |
95 | public static Builder builder() {
96 | return new Builder();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/fast-rng/src/test/java/biz/k11i/rng/BetaRNGTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import biz.k11i.rng.test.SecondLevelTest;
4 | import biz.k11i.rng.test.gof.GoodnessOfFitTest;
5 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.Arguments;
8 | import org.junit.jupiter.params.provider.MethodSource;
9 |
10 | import java.util.stream.Stream;
11 |
12 | class BetaRNGTest {
13 | static Stream parameterCase1() {
14 | return Stream.of(
15 | Arguments.of(0.1, 0.5),
16 | Arguments.of(0.1, 0.9),
17 | Arguments.of(0.45, 0.46),
18 | Arguments.of(0.46, 0.45),
19 | Arguments.of(0.5, 0.5),
20 | Arguments.of(0.5, 0.9),
21 | Arguments.of(0.9, 0.5),
22 | Arguments.of(0.99998, 0.99999),
23 | Arguments.of(0.99999, 0.99998));
24 | }
25 |
26 | @ParameterizedTest
27 | @MethodSource("parameterCase1")
28 | void testCase1_fast(double alpha, double beta) {
29 | test(BetaRNG.FAST_RNG, alpha, beta);
30 | }
31 |
32 | @ParameterizedTest
33 | @MethodSource("parameterCase1")
34 | void testCase1_general(double alpha, double beta) {
35 | test(BetaRNG.GENERAL_RNG, alpha, beta);
36 | }
37 |
38 | static Stream parameterCase2() {
39 | return Stream.of(
40 | Arguments.of(0.099, 10.0),
41 | Arguments.of(0.6, 1.2),
42 | Arguments.of(1.2, 0.6),
43 | Arguments.of(0.7, 5.0),
44 | Arguments.of(0.8, 20.0),
45 | Arguments.of(20.0, 0.8),
46 | Arguments.of(0.9, 80.0));
47 | }
48 |
49 | @ParameterizedTest
50 | @MethodSource("parameterCase2")
51 | void testCase2_fast(double alpha, double beta) {
52 | test(BetaRNG.FAST_RNG, alpha, beta);
53 | }
54 |
55 | @ParameterizedTest
56 | @MethodSource("parameterCase2")
57 | void testCase2_general(double alpha, double beta) {
58 | test(BetaRNG.GENERAL_RNG, alpha, beta);
59 | }
60 |
61 | static Stream parameterCase3() {
62 | return Stream.of(
63 | Arguments.of(1.5, 1.5),
64 | Arguments.of(1.5, 4.0),
65 | Arguments.of(4.0, 1.5),
66 | Arguments.of(4.0, 100.0),
67 | Arguments.of(100.0, 4.0));
68 | }
69 |
70 | @ParameterizedTest
71 | @MethodSource("parameterCase3")
72 | void testCase3_fast(double alpha, double beta) {
73 | test(BetaRNG.FAST_RNG, alpha, beta);
74 | }
75 |
76 | @ParameterizedTest
77 | @MethodSource("parameterCase3")
78 | void testCase3_general(double alpha, double beta) {
79 | test(BetaRNG.GENERAL_RNG, alpha, beta);
80 | }
81 |
82 | static Stream parameterSpecialCase() {
83 | return Stream.of(
84 | Arguments.of(1.0, 1.01),
85 | Arguments.of(1.0, 0.99),
86 | Arguments.of(1.0, 10.0),
87 | Arguments.of(1.01, 1.0),
88 | Arguments.of(0.99, 1.0),
89 | Arguments.of(10.0, 1.0),
90 | Arguments.of(1.0, 1.0));
91 | }
92 |
93 | @ParameterizedTest
94 | @MethodSource("parameterSpecialCase")
95 | void testSpecialCase_fast(double alpha, double beta) {
96 | test(BetaRNG.FAST_RNG, alpha, beta);
97 | }
98 |
99 | @ParameterizedTest
100 | @MethodSource("parameterSpecialCase")
101 | void testSpecialCase_general(double alpha, double beta) {
102 | test(BetaRNG.GENERAL_RNG, alpha, beta);
103 | }
104 |
105 | private void test(BetaRNG rng, double alpha, double beta) {
106 | GoodnessOfFitTest gofTest = GoodnessOfFitTest.continuous()
107 | .probabilityDistribution(ProbabilityDistributions.beta(alpha, beta))
108 | .randomNumberGenerator(String.format("Beta(%f, %f)", alpha, beta), r -> rng.generate(r, alpha, beta))
109 | .numRandomValues(1_000_000)
110 | .build();
111 |
112 | SecondLevelTest.builder()
113 | .numIterations(20)
114 | .build()
115 | .testAndVerify(gofTest);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/distribution/ContinuousDistributionBase.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.analysis.UnivariateFunction;
4 | import org.apache.commons.math3.analysis.solvers.UnivariateSolverUtils;
5 | import org.apache.commons.math3.distribution.RealDistribution;
6 | import org.apache.commons.math3.exception.OutOfRangeException;
7 | import org.apache.commons.math3.util.FastMath;
8 |
9 | /**
10 | * Base class of {@link ContinuousDistribution}.
11 | *
12 | *
13 | * This implementation includes a part of Apache Commons Math
14 | * that is licensed under the Apache License 2.0.
15 | *
16 | *
17 | * Licensed to the Apache Software Foundation (ASF) under one or more
18 | * contributor license agreements. See the NOTICE file distributed with
19 | * this work for additional information regarding copyright ownership.
20 | * The ASF licenses this file to You under the Apache License, Version 2.0
21 | * (the "License"); you may not use this file except in compliance with
22 | * the License. You may obtain a copy of the License at
23 | *
24 | * http://www.apache.org/licenses/LICENSE-2.0
25 | *
26 | * Unless required by applicable law or agreed to in writing, software
27 | * distributed under the License is distributed on an "AS IS" BASIS,
28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29 | * See the License for the specific language governing permissions and
30 | * limitations under the License.
31 | *
32 | */
33 | abstract class ContinuousDistributionBase implements ContinuousDistribution {
34 | private final double supportLowerBound;
35 | private final double supportUpperBound;
36 | private final double mu;
37 | private final double sig;
38 | private final boolean chebyshevApplies;
39 | private final boolean isSupportConnected;
40 | private final double solverAbsoluteAccuracy;
41 |
42 | ContinuousDistributionBase(RealDistribution distribution, double solverAbsoluteAccuracy) {
43 | this.supportLowerBound = distribution.getSupportLowerBound();
44 | this.supportUpperBound = distribution.getSupportUpperBound();
45 | this.mu = distribution.getNumericalMean();
46 | this.sig = FastMath.sqrt(distribution.getNumericalVariance());
47 | this.chebyshevApplies = !(Double.isInfinite(mu) || Double.isNaN(mu) ||
48 | Double.isInfinite(sig) || Double.isNaN(sig));
49 | this.isSupportConnected = distribution.isSupportConnected();
50 | this.solverAbsoluteAccuracy = solverAbsoluteAccuracy;
51 | }
52 |
53 | @Override
54 | public double inverseCdf(double p) {
55 | if (p < 0.0 || p > 1.0) {
56 | throw new OutOfRangeException(p, 0, 1);
57 | }
58 |
59 | if (p == 0.0) {
60 | return supportLowerBound;
61 | }
62 |
63 | if (p == 1.0) {
64 | return supportUpperBound;
65 | }
66 |
67 | double lowerBound = supportLowerBound;
68 | if (supportLowerBound == Double.NEGATIVE_INFINITY) {
69 | if (chebyshevApplies) {
70 | lowerBound = mu - sig * FastMath.sqrt((1. - p) / p);
71 | } else {
72 | lowerBound = -1.0;
73 | while (cdf(lowerBound) >= p) {
74 | lowerBound *= 2.0;
75 | }
76 | }
77 | }
78 |
79 | double upperBound = supportUpperBound;
80 | if (upperBound == Double.POSITIVE_INFINITY) {
81 | if (chebyshevApplies) {
82 | upperBound = mu + sig * FastMath.sqrt(p / (1. - p));
83 | } else {
84 | upperBound = 1.0;
85 | while (cdf(upperBound) < p) {
86 | upperBound *= 2.0;
87 | }
88 | }
89 | }
90 |
91 | final UnivariateFunction toSolve = new UnivariateFunction() {
92 | /** {@inheritDoc} */
93 | public double value(final double x) {
94 | return cdf(x) - p;
95 | }
96 | };
97 |
98 | double x = UnivariateSolverUtils.solve(toSolve,
99 | lowerBound,
100 | upperBound,
101 | solverAbsoluteAccuracy);
102 |
103 | if (!isSupportConnected) {
104 | /* Test for plateau. */
105 | if (x - solverAbsoluteAccuracy >= supportLowerBound) {
106 | double px = cdf(x);
107 | if (cdf(x - solverAbsoluteAccuracy) == px) {
108 | upperBound = x;
109 | while (upperBound - lowerBound > solverAbsoluteAccuracy) {
110 | final double midPoint = 0.5 * (lowerBound + upperBound);
111 | if (cdf(midPoint) < px) {
112 | lowerBound = midPoint;
113 | } else {
114 | upperBound = midPoint;
115 | }
116 | }
117 | return upperBound;
118 | }
119 | }
120 | }
121 | return x;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/fast-rng/src/main/java/biz/k11i/rng/ExponentialRNG.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import java.util.Random;
4 |
5 | import static biz.k11i.util.MathFunctions.exp;
6 | import static biz.k11i.util.MathFunctions.log;
7 | import static biz.k11i.util.MathFunctions.log1p;
8 |
9 | /**
10 | * Exponential random number generator.
11 | */
12 | public interface ExponentialRNG {
13 | ExponentialRNG FAST_RNG = ZigguratFast.Z_256;
14 | ExponentialRNG GENERAL_RNG = ZigguratGeneral.Z_256;
15 |
16 | /**
17 | * Generates a random value sampled from exponential distribution.
18 | *
19 | * @param random random number generator
20 | * @param theta mean of the distribution
21 | * @return a random value
22 | */
23 | double generate(Random random, double theta);
24 |
25 | abstract class ZigguratBase implements ExponentialRNG {
26 | final int N;
27 | final double R;
28 | final double V;
29 | final int INDEX_BIT_MASK;
30 | final int TAIL_INDEX;
31 |
32 | ZigguratBase(int nBits, double r, double v) {
33 | N = 1 << nBits;
34 | R = r;
35 | V = v;
36 |
37 | INDEX_BIT_MASK = (1 << nBits) - 1;
38 | TAIL_INDEX = (1 << nBits) - 1;
39 | }
40 |
41 | static double finv(double x, double v) {
42 | return -log(exp(-x) + v / x);
43 | }
44 |
45 | @Override
46 | public double generate(Random random, double theta) {
47 | return theta * generate(random, 0);
48 | }
49 |
50 | abstract double generate(Random random, int recursionCount);
51 | }
52 |
53 | /**
54 | * Implementation of Exponential random number generator using Ziggurat algorithm.
55 | *
56 | * Tesuaki Yotsuji. 計算機シミュレーションのための確率分布乱数生成法.
57 | * Pleiades PUBLISHING Co.,Ltd. (2010)
58 | *
59 | *
60 | * This implementation assumes that the values returned from {@link Random#nextLong()}
61 | * have the independence of each bit.
62 | *
63 | */
64 | class ZigguratFast extends ZigguratBase implements ExponentialRNG {
65 | private static final ZigguratFast Z_256 = new ZigguratFast(8, 7.697117470131, 0.00394965982258);
66 |
67 | private final int INDEX_BITS;
68 |
69 | private final long[] k;
70 | private final double[] w;
71 | private final double[] f;
72 |
73 | ZigguratFast(int nBits, double r, double v) {
74 | super(nBits, r, v);
75 |
76 | INDEX_BITS = nBits;
77 |
78 | w = new double[N];
79 | k = new long[N];
80 | f = new double[N];
81 |
82 | long b = 1L << (64 - nBits);
83 | w[N - 1] = v * exp(r) / b;
84 | w[N - 2] = r / b;
85 | k[N - 1] = (long) Math.floor(r / w[N - 1]);
86 | f[N - 1] = exp(-r);
87 |
88 | double x = r;
89 | for (int i = N - 2; i >= 1; i--) {
90 | x = finv(x, v);
91 | w[i - 1] = x / b;
92 | k[i] = (long) Math.floor(x / w[i]);
93 | f[i] = exp(-x);
94 | }
95 |
96 | k[0] = 0;
97 | f[0] = 1;
98 | }
99 |
100 | @Override
101 | double generate(Random random, int recursiveCount) {
102 | while (true) {
103 | long u = random.nextLong();
104 | int i = (int) (u & INDEX_BIT_MASK);
105 | u >>>= INDEX_BITS;
106 |
107 | if (u < k[i]) {
108 | return u * w[i];
109 | }
110 |
111 | if (i == TAIL_INDEX) {
112 | if (recursiveCount < 2) {
113 | return R + generate(random, recursiveCount + 1);
114 | }
115 | return R - log1p(-random.nextDouble());
116 | }
117 |
118 | double x = u * w[i];
119 | double fx = exp(-x);
120 | if (random.nextDouble() * (f[i] - f[i + 1]) <= fx - f[i + 1]) {
121 | return x;
122 | }
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Implementation of Exponential random number generator using Ziggurat algorithm.
129 | *
130 | * Tesuaki Yotsuji. 計算機シミュレーションのための確率分布乱数生成法.
131 | * Pleiades PUBLISHING Co.,Ltd. (2010)
132 | *
133 | *
134 | * This implementation is a bit slower than {@link ZigguratFast}
135 | * but it does not require the independence of each bit to the values returned from {@link Random#nextLong()}.
136 | *
137 | */
138 | class ZigguratGeneral extends ZigguratBase implements ExponentialRNG {
139 | private static final ZigguratGeneral Z_256 = new ZigguratGeneral(8, 7.697117470131, 0.00394965982258);
140 |
141 | private final double[] x;
142 | private final double[] t;
143 |
144 | ZigguratGeneral(int nBits, double r, double v) {
145 | super(nBits, r, v);
146 |
147 | x = new double[N + 1];
148 | t = new double[N];
149 |
150 | x[N] = v * exp(r);
151 | x[N - 1] = r;
152 | for (int i = N - 2; i >= 1; i--) {
153 | x[i] = finv(x[i + 1], v);
154 | }
155 | x[0] = 0;
156 |
157 | for (int i = 0; i < N; i++) {
158 | t[i] = x[i] / x[i + 1];
159 | }
160 | }
161 |
162 | @Override
163 | double generate(Random random, int recursiveCount) {
164 | while (true) {
165 | int i = (int) (random.nextLong() & INDEX_BIT_MASK);
166 | double u1 = random.nextDouble();
167 |
168 | if (u1 < t[i]) {
169 | return u1 * x[i + 1];
170 | }
171 |
172 | if (i == TAIL_INDEX) {
173 | if (recursiveCount < 2) {
174 | return R + generate(random, recursiveCount + 1);
175 | }
176 | return R - log1p(-random.nextDouble());
177 | }
178 |
179 | double y = u1 * x[i + 1];
180 | double gu = exp(-(x[i] - y));
181 | double gl = exp(-(x[i + 1] - y));
182 | if (random.nextDouble() * (gu - gl) <= 1 - gl) {
183 | return y;
184 | }
185 | }
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/ComputationAndSorting.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util;
2 |
3 | import java.util.Arrays;
4 | import java.util.concurrent.ForkJoinTask;
5 | import java.util.concurrent.RecursiveAction;
6 |
7 | public class ComputationAndSorting> {
8 | @FunctionalInterface
9 | public interface Computation> {
10 | double compute(S splittable, int index);
11 | }
12 |
13 | @FunctionalInterface
14 | public interface Splittable> {
15 | S split();
16 | }
17 |
18 | public static class NullSplittable implements Splittable {
19 | public static final NullSplittable INSTANCE = new NullSplittable();
20 |
21 | @Override
22 | public NullSplittable split() {
23 | return this;
24 | }
25 | }
26 |
27 | private final Computation computation;
28 | private final int splitThreshold;
29 | private final int parallelMergeThreshold;
30 |
31 | public ComputationAndSorting(int n, int numParallels, Computation computation) {
32 | this.computation = computation;
33 | int g = n / numParallels;
34 | this.splitThreshold = g;
35 | this.parallelMergeThreshold = g;
36 | }
37 |
38 | public ForkJoinTask newForkJoinTask(S splittable, double[] result, double[] work) {
39 | return new ComputationAction(0, result.length, result, work, splittable);
40 | }
41 |
42 | class ComputationAction extends RecursiveAction {
43 | private final int startInclusive;
44 | private final int endExclusive;
45 | private final double[] output;
46 | private final double[] work;
47 | private final S splittable;
48 |
49 | ComputationAction(int startInclusive, int endExclusive, double[] output, double[] work, S splittable) {
50 | this.startInclusive = startInclusive;
51 | this.endExclusive = endExclusive;
52 | this.output = output;
53 | this.work = work;
54 | this.splittable = splittable;
55 | }
56 |
57 | @Override
58 | protected void compute() {
59 | split(startInclusive, endExclusive, output, work);
60 | }
61 |
62 | private void split(int startInclusive, int endExclusive, double[] output, double[] work) {
63 | int n = endExclusive - startInclusive;
64 | if (n > splitThreshold) {
65 | int mid = startInclusive + (n >>> 1);
66 |
67 | ForkJoinTask h2 = new ComputationAction(mid, endExclusive, work, output, splittable.split()).fork();
68 | split(startInclusive, mid, work, output);
69 | h2.join();
70 |
71 | if (n > parallelMergeThreshold) {
72 | mergeInParallel(startInclusive, mid, endExclusive, output, work);
73 | } else {
74 | mergeSequentially(startInclusive, mid, endExclusive, output, work);
75 | }
76 |
77 | } else {
78 | computeSequentially(startInclusive, endExclusive, output);
79 | }
80 | }
81 |
82 | private void mergeInParallel(int startInclusive, int mid, int endExclusive, double[] output, double[] work) {
83 | int q1 = (startInclusive + mid) >>> 1;
84 | double split = work[q1];
85 |
86 | int left = mid;
87 | int right = endExclusive;
88 |
89 | while (left < right) {
90 | int searchMid = (left + right) >>> 1;
91 | if (work[searchMid] < split) {
92 | left = searchMid + 1;
93 | } else {
94 | right = searchMid;
95 | }
96 | }
97 |
98 | int s = (q1 + 1) + (right - mid);
99 | ForkJoinTask m2 = new MergeAction(work,
100 | q1 + 1, mid,
101 | right, endExclusive,
102 | output, s).fork();
103 | MergeAction.mergeSequentially(work,
104 | startInclusive, q1 + 1,
105 | mid, right,
106 | output, startInclusive);
107 | m2.join();
108 | }
109 |
110 | private void mergeSequentially(int startInclusive, int mid, int endExclusive, double[] output, double[] work) {
111 | MergeAction.mergeSequentially(work, startInclusive, mid, mid, endExclusive, output, startInclusive);
112 | }
113 |
114 | private void computeSequentially(int startInclusive, int endExclusive, double[] result) {
115 | for (int i = startInclusive; i < endExclusive; i++) {
116 | result[i] = computation.compute(splittable, i);
117 | }
118 |
119 | Arrays.sort(result, startInclusive, endExclusive);
120 | }
121 | }
122 |
123 | static class MergeAction extends RecursiveAction {
124 | private final double[] src;
125 | private final int src1Start;
126 | private final int src1End;
127 | private final int src2Start;
128 | private final int src2End;
129 | private final double[] dst;
130 | private final int dstStart;
131 |
132 | MergeAction(double[] src, int src1Start, int src1End, int src2Start, int src2End, double[] dst, int dstStart) {
133 | this.src = src;
134 | this.src1Start = src1Start;
135 | this.src1End = src1End;
136 | this.src2Start = src2Start;
137 | this.src2End = src2End;
138 | this.dst = dst;
139 | this.dstStart = dstStart;
140 | }
141 |
142 | @Override
143 | protected void compute() {
144 | mergeSequentially(src, src1Start, src1End, src2Start, src2End, dst, dstStart);
145 | }
146 |
147 | static void mergeSequentially(double[] src, int left, int leftEnd, int right, int rightEnd, double[] dst, int dstStart) {
148 | for (int i = dstStart; ; i++) {
149 | if (left >= leftEnd) {
150 | System.arraycopy(src, right, dst, i, rightEnd - right);
151 | return;
152 | }
153 | if (right >= rightEnd) {
154 | System.arraycopy(src, left, dst, i, leftEnd - left);
155 | return;
156 | }
157 |
158 | if (src[left] <= src[right]) {
159 | dst[i] = src[left++];
160 | } else {
161 | dst[i] = src[right++];
162 | }
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/gof/GoodnessOfFitTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.gof;
2 |
3 | import org.slf4j.Logger;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.Comparator;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.concurrent.ForkJoinPool;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.function.Consumer;
13 | import java.util.function.Function;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 |
17 | /**
18 | * Provides functionalities for Goodness-of-Fit test to test randomness of the given random number generator.
19 | */
20 | @SuppressWarnings("unused")
21 | public abstract class GoodnessOfFitTest {
22 | static class BuilderBase> {
23 | @SuppressWarnings("unchecked")
24 | private final SELF self = (SELF) this;
25 |
26 | int numRandomValues;
27 | double significanceLevel = DEFAULT_SIGNIFICANCE_LEVEL;
28 |
29 | public SELF numRandomValues(int numRandomValues) {
30 | this.numRandomValues = numRandomValues;
31 | return self;
32 | }
33 |
34 | public SELF significanceLevel(double significanceLevel) {
35 | this.significanceLevel = significanceLevel;
36 | return self;
37 | }
38 | }
39 |
40 | /** Default significance level (0.1%) */
41 | private static final double DEFAULT_SIGNIFICANCE_LEVEL = 0.001;
42 |
43 | /** Name of the random number generator to be tested */
44 | public final String rngName;
45 |
46 | /** Significance level to reject the null hypothesis */
47 | public final double significanceLevel;
48 |
49 | GoodnessOfFitTest(String rngName, double significanceLevel) {
50 | this.rngName = rngName;
51 | this.significanceLevel = significanceLevel;
52 | }
53 |
54 | /**
55 | * Returns {@link ContinuousGofTest} builder object.
56 | *
57 | * @return builder object.
58 | */
59 | public static ContinuousGofTest.Builder continuous() {
60 | return new ContinuousGofTest.Builder();
61 | }
62 |
63 | /**
64 | * Returns {@link DiscreteGofTest} builder object.
65 | *
66 | * @return builder obbject.
67 | */
68 | public static DiscreteGofTest.Builder discrete() {
69 | return new DiscreteGofTest.Builder();
70 | }
71 |
72 | /**
73 | * Calculate p-values of Goodness-of-Fit test.
74 | *
75 | * @return test result.
76 | */
77 | public abstract Map test();
78 |
79 | /**
80 | * Calculate p-values of Goodness-of-Fit test in parallel.
81 | *
82 | * @return test result.
83 | */
84 | public Map testInParallel() {
85 | ForkJoinPool pool = new ForkJoinPool();
86 | try {
87 | return testInParallel(pool);
88 |
89 | } finally {
90 | pool.shutdown();
91 | try {
92 | pool.awaitTermination(1, TimeUnit.SECONDS);
93 | } catch (InterruptedException ignore) {
94 | }
95 | }
96 | }
97 |
98 | /**
99 | * Calculate p-values of Goodness-of-Fit test in parallel using given {@link ForkJoinPool} instance.
100 | *
101 | * @param pool Fork/Join pool to execute tasks in paralle.
102 | * @return test result.
103 | */
104 | public abstract Map testInParallel(ForkJoinPool pool);
105 |
106 | /**
107 | * Runs Goodness-of-Fit tests and verifis whether the test failed to reject the null hypothesis
108 | * (which means the random sequence that was generated by random number generator can be described as random) or not.
109 | */
110 | public void testAndVerify() {
111 | Map result = test();
112 |
113 | result.forEach((t, p) ->
114 | assertThat(p)
115 | .describedAs("At a significance level of %e, the Goodness-of-Fit test of [%s] should fail to reject null hypothesis (The p-value %f should be greater than or equal to %e).\n" +
116 | "This means that the random sequence generated by the random number generator [%s] fit the theoretical probability distribution.",
117 | significanceLevel, t, p, significanceLevel,
118 | rngName)
119 | .isGreaterThanOrEqualTo(significanceLevel));
120 | }
121 | }
122 |
123 | @SuppressWarnings("unused")
124 | class PerformanceMeasure {
125 | static class LogRecord {
126 | private final String message;
127 | private Object[] args;
128 | private final long beginMillis = System.currentTimeMillis();
129 | private long endMillis;
130 |
131 | LogRecord(String message) {
132 | this.message = message;
133 | }
134 |
135 | void updateArgs(Object... args) {
136 | this.args = args;
137 | }
138 |
139 | long elapsedMillis() {
140 | return endMillis - beginMillis;
141 | }
142 | }
143 |
144 | private final List bufferedLogs = new ArrayList<>();
145 |
146 | static T run(Logger logger, Function process) {
147 | PerformanceMeasure m = new PerformanceMeasure();
148 | try {
149 | return process.apply(m);
150 | } finally {
151 | m.flushLogs(logger);
152 | }
153 | }
154 |
155 | void measure(String logMessageTemplate, Consumer processToMeasure) {
156 | measure(logMessageTemplate, args(), processToMeasure);
157 | }
158 |
159 | void measure(String logMessageTemplate, Object arg1, Consumer processToMeasure) {
160 | measure(logMessageTemplate, args(arg1), processToMeasure);
161 | }
162 |
163 | void measure(String logMessageTemplate, Object arg1, Object arg2, Consumer processToMeasure) {
164 | measure(logMessageTemplate, args(arg1, arg2), processToMeasure);
165 | }
166 |
167 | void measure(String logMessageTemplate, Object[] args, Consumer processToMeasure) {
168 | long begin = System.currentTimeMillis();
169 |
170 | LogRecord logRecord = new LogRecord(logMessageTemplate);
171 | logRecord.args = args;
172 | bufferedLogs.add(logRecord);
173 |
174 | processToMeasure.accept(logRecord);
175 | logRecord.endMillis = System.currentTimeMillis();
176 | }
177 |
178 | Object[] args(Object... args) {
179 | return args;
180 | }
181 |
182 | private void flushLogs(Logger logger) {
183 | bufferedLogs.sort(Comparator.comparingLong(o -> o.beginMillis));
184 |
185 | for (LogRecord log : bufferedLogs) {
186 | Object[] args = Arrays.copyOf(log.args, log.args.length + 1);
187 | args[log.args.length] = log.elapsedMillis();
188 | logger.info(log.message + " ({} ms)", args);
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/fast-rng/src/main/java/biz/k11i/rng/BetaRNG.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import java.util.Random;
4 |
5 | import static biz.k11i.util.MathFunctions.exp;
6 | import static biz.k11i.util.MathFunctions.log;
7 | import static biz.k11i.util.MathFunctions.pow;
8 |
9 | /**
10 | * Beta random number generator.
11 | */
12 | public interface BetaRNG {
13 | BetaRNG FAST_RNG = new BetaRNGImpl(GammaRNG.FAST_RNG);
14 | BetaRNG GENERAL_RNG = new BetaRNGImpl(GammaRNG.GENERAL_RNG);
15 |
16 | /**
17 | * Generates a random value sampled from beta distribution.
18 | *
19 | * @param random random number generator
20 | * @param alpha shape parameter (alpha)
21 | * @param beta shape parameter (beta)
22 | * @return a random value
23 | */
24 | double generate(Random random, double alpha, double beta);
25 |
26 | class BetaRNGImpl implements BetaRNG {
27 | private static final double[] CASE2_MAX_THRESHOLDS;
28 |
29 | static {
30 | CASE2_MAX_THRESHOLDS = new double[11];
31 | CASE2_MAX_THRESHOLDS[0] = 90.0;
32 | CASE2_MAX_THRESHOLDS[1 /* 0.01 */] = 70.0;
33 | CASE2_MAX_THRESHOLDS[2 /* 0.02 */] = 50.0;
34 | CASE2_MAX_THRESHOLDS[3 /* 0.03 */] = 12.0;
35 | CASE2_MAX_THRESHOLDS[4 /* 0.04 */] = 6.0;
36 | CASE2_MAX_THRESHOLDS[5 /* 0.05 */] = 3.0;
37 | CASE2_MAX_THRESHOLDS[6 /* 0.06 */] = 2.5;
38 | CASE2_MAX_THRESHOLDS[7 /* 0.07 */] = 2.0;
39 | CASE2_MAX_THRESHOLDS[8 /* 0.08 */] = 1.6;
40 | CASE2_MAX_THRESHOLDS[9 /* 0.09 */] = 1.3;
41 | CASE2_MAX_THRESHOLDS[10 /* 0.10 */] = 1.0;
42 | }
43 |
44 | private final BetaRNG twoGammaVariates;
45 |
46 | BetaRNGImpl(GammaRNG gammaRNG) {
47 | this.twoGammaVariates = new BetaRNGAlgorithms.TwoGammaVariates(gammaRNG);
48 | }
49 |
50 | @Override
51 | public double generate(Random random, double alpha, double beta) {
52 | return (alpha <= beta ? selectAlgorithm(alpha, beta) : selectAlgorithm(beta, alpha))
53 | .generate(random, alpha, beta);
54 | }
55 |
56 | BetaRNG selectAlgorithm(double min, double max) {
57 | if (min > 1.0) { // case 3: max >= min > 1
58 | return twoGammaVariates;
59 | }
60 |
61 | if (max < 1.0) { // case 1: min <= max < 1
62 | return max + min <= 1.5 ? BetaRNGAlgorithms.Johnk.INSTANCE : BetaRNGAlgorithms.B00.INSTANCE;
63 | }
64 |
65 | // case 2 + special case
66 | // min <= 1, max >= 1
67 |
68 | if (min < 0.1 && max <= CASE2_MAX_THRESHOLDS[(int) (min * 100)]) {
69 | return BetaRNGAlgorithms.Johnk.INSTANCE;
70 | }
71 |
72 | if (max > 1.0) {
73 | return twoGammaVariates;
74 | }
75 |
76 | // min <= 1, max = 1
77 |
78 | if (min == 1.0) {
79 | // max = 1, min = 1
80 | return BetaRNGAlgorithms.Unif.INSTANCE;
81 | }
82 |
83 | return BetaRNGAlgorithms.CdfInversion.INSTANCE;
84 | }
85 | }
86 |
87 |
88 | }
89 |
90 | class BetaRNGAlgorithms {
91 | /**
92 | * Implementation of Beta random number generator using Jöhnk's algorithm.
93 | *
94 | * Jöhnk, M. D.
95 | * "Erzeugung von betaverteilten und gammaverteilten Zufallszahlen."
96 | * Metrika 8.1 (1964): 5-15.
97 | *
98 | */
99 | static class Johnk implements BetaRNG {
100 | static final BetaRNG INSTANCE = new Johnk();
101 |
102 | @Override
103 | public double generate(Random random, double alpha, double beta) {
104 | while (true) {
105 | double u = log(random.nextDouble()) / alpha;
106 | double v = log(random.nextDouble()) / beta;
107 |
108 | double uu = exp(u);
109 | double vv = exp(v);
110 |
111 | double w = uu + vv;
112 | if (w <= 1) {
113 | if (w > 0) {
114 | return uu / w;
115 | }
116 |
117 | double logM = u > v ? u : v;
118 | u -= logM;
119 | v -= logM;
120 |
121 | return exp(u - log(exp(u) + exp(v)));
122 | }
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Implementation of Beta random number generator using Sakasegawa's B00 algorithm.
129 | *
130 | * Sakasegawa, H.
131 | * "Stratified rejection and squeeze method for generating beta random numbers."
132 | * Annals of the Institute of Statistical Mathematics 35.1 (1983): 291-302.
133 | *
134 | */
135 | static class B00 implements BetaRNG {
136 | static final BetaRNG INSTANCE = new B00();
137 |
138 | @Override
139 | public double generate(Random random, double alpha, double beta) {
140 | double t = (1 - alpha) / (2 - alpha - beta);
141 | double s = (beta - alpha) * (1 - alpha - beta);
142 | double r = alpha * (1 - alpha);
143 | t -= ((s * t + 2 * r) * t - r) / 2 * (s * t + r);
144 | double p = t / alpha;
145 | double q = (1 - t) / beta;
146 | s = pow((1 - t), beta - 1);
147 | double c = pow(t, alpha - 1);
148 | r = (c - 1) / (t - 1);
149 |
150 | while (true) {
151 | // step 1
152 | double u = random.nextDouble() * (p + q);
153 | double v = random.nextDouble();
154 |
155 | if (u <= p) {
156 | // step 2
157 | double x = t * pow(u / p, 1 / alpha);
158 | v *= s;
159 |
160 | if (v < (1 - beta) * x + 1) {
161 | return x;
162 | }
163 | if (v < (s - 1) * x / t + 1 && v <= pow(1 - x, beta - 1)) {
164 | return x;
165 | }
166 |
167 | } else {
168 | // step 3
169 | double x = 1 - (1 - t) * pow((u - p) / q, 1 / beta);
170 | v *= c;
171 |
172 | if (v < (alpha - 1) * (x - 1) + 1) {
173 | return x;
174 | }
175 | if (v <= r * (x - 1) + 1 && v <= pow(x, alpha - 1)) {
176 | return x;
177 | }
178 | }
179 | }
180 | }
181 | }
182 |
183 | /**
184 | * Generates Beta variates using two Gamma variates.
185 | */
186 | static class TwoGammaVariates implements BetaRNG {
187 | private final GammaRNG gammaRNG;
188 |
189 | TwoGammaVariates(GammaRNG gammaRNG) {
190 | this.gammaRNG = gammaRNG;
191 | }
192 |
193 | @Override
194 | public double generate(Random random, double alpha, double beta) {
195 | double a = gammaRNG.generate(random, alpha, 1);
196 | if (a == 0.0) {
197 | return 0.0;
198 | }
199 |
200 | return a / (a + gammaRNG.generate(random, beta, 1));
201 | }
202 | }
203 |
204 | /**
205 | * Generates Beta variates using inversion method.
206 | */
207 | static class CdfInversion implements BetaRNG {
208 | static final BetaRNG INSTANCE = new CdfInversion();
209 |
210 | @Override
211 | public double generate(Random random, double alpha, double beta) {
212 | return alpha == 1.0
213 | ? 1 - pow(random.nextDouble(), 1.0 / beta)
214 | : pow(random.nextDouble(), 1.0 / alpha);
215 | }
216 | }
217 |
218 | static class Unif implements BetaRNG {
219 | static final BetaRNG INSTANCE = new Unif();
220 |
221 | @Override
222 | public double generate(Random random, double ignore1, double ignore2) {
223 | return random.nextDouble();
224 | }
225 | }
226 | }
--------------------------------------------------------------------------------
/fast-rng/src/main/java/biz/k11i/rng/GaussianRNG.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import java.util.Random;
4 |
5 | import static biz.k11i.util.MathFunctions.exp;
6 | import static biz.k11i.util.MathFunctions.log;
7 |
8 | /**
9 | * Gaussian random number generator.
10 | */
11 | public interface GaussianRNG {
12 | GaussianRNG FAST_RNG = ZigguratFast.Z_256;
13 | GaussianRNG GENERAL_RNG = ZigguratGeneral.Z_256;
14 |
15 | /**
16 | * Generates a random value sampled from gaussian distribution (normal distribution).
17 | *
18 | * @param random random number generator
19 | * @return a random value
20 | */
21 | double generate(Random random);
22 |
23 | abstract class ZigguratBase {
24 | static double f(double x) {
25 | // f(x) = e^{-x^2 / 2}
26 | return exp(-0.5 * x * x);
27 | }
28 |
29 | static double tail(Random random, double r) {
30 | double _x, _y;
31 | do {
32 | _x = -log(random.nextDouble()) / r;
33 | _y = -log(random.nextDouble());
34 |
35 | } while (_y + _y < _x * _x);
36 | return r + _x;
37 | }
38 | }
39 |
40 | /**
41 | * Implementation of Gaussian random number generator using Ziggurat algorithm.
42 | *
43 | * Marsaglia, George, and Wai Wan Tsang.
44 | * "The ziggurat method for generating random variables."
45 | * Journal of statistical software 5.8 (2000): 1-7.
46 | *
47 | *
48 | * Tesuaki Yotsuji. 計算機シミュレーションのための確率分布乱数生成法.
49 | * Pleiades PUBLISHING Co.,Ltd. (2010)
50 | *
51 | *
52 | * This implementation assumes that the values returned from {@link Random#nextLong()}
53 | * have the independence of each bit.
54 | *
55 | */
56 | class ZigguratFast extends ZigguratBase implements GaussianRNG {
57 | private static final ZigguratFast Z_256 = new ZigguratFast(8, 3.6541528853610088, 0.00492867323399);
58 |
59 | private final int N;
60 | private final double R;
61 | private final double V;
62 | private final int INDEX_RIGHT_SHIFT_BITS;
63 | private final long SIGN_BIT_MASK;
64 | private final long U_BIT_MASK;
65 | private final int TAIL_INDEX;
66 |
67 | private final long[] k;
68 | private final double[] w;
69 | private final double[] f;
70 |
71 | /**
72 | * Constructs {@link ZigguratFast} with parameters.
73 | *
74 | * @param nBits number of rectangles (2^nBits)
75 | * @param r rightmost x_i
76 | * @param v area of the rectangle
77 | */
78 | ZigguratFast(int nBits, double r, double v) {
79 | N = 1 << nBits;
80 | R = r;
81 | V = v;
82 | INDEX_RIGHT_SHIFT_BITS = 64 - nBits;
83 | SIGN_BIT_MASK = 1L << (64 - nBits - 1);
84 | U_BIT_MASK = (1L << (64 - nBits - 1)) - 1;
85 | TAIL_INDEX = N - 1;
86 |
87 | k = new long[N];
88 | w = new double[N];
89 | f = new double[N];
90 |
91 | double fr = f(r);
92 | long b = 1L << (64 - 8 - 1);
93 |
94 | w[N - 1] = v * exp(0.5 * r * r) / b;
95 | w[N - 2] = r / b;
96 | k[N - 1] = (long) Math.floor(r / w[N - 1]);
97 | f[N - 1] = fr;
98 |
99 | double x = r;
100 |
101 | for (int i = N - 2; i >= 1; i--) {
102 | x = Math.sqrt(-2.0 * log(f(x) + v / x));
103 | w[i - 1] = x / b;
104 | k[i] = (long) Math.floor(x / w[i]);
105 | f[i] = f(x);
106 | }
107 |
108 | k[0] = 0;
109 | f[0] = 1;
110 | }
111 |
112 | public double generate(Random random) {
113 | while (true) {
114 | long u = random.nextLong();
115 | int i = (int) (u >>> INDEX_RIGHT_SHIFT_BITS);
116 | int sign = (u & SIGN_BIT_MASK) == 0 ? 1 : -1;
117 | u &= U_BIT_MASK;
118 |
119 | if (u < k[i]) {
120 | return sign * u * w[i];
121 | }
122 |
123 | if (i == TAIL_INDEX) {
124 | return sign * tail(random, R);
125 | }
126 |
127 | double x = u * w[i];
128 | if (random.nextDouble() * (f[i] - f[i + 1]) <= f(x) - f[i + 1]) {
129 | return sign * x;
130 | }
131 | }
132 | }
133 |
134 | @Override
135 | public String toString() {
136 | return String.format("ZigguratFast(N = %d, R = %f, V = %f)", N, R, V);
137 | }
138 | }
139 |
140 | /**
141 | * Implementation of Gaussian random number generator using Ziggurat algorithm.
142 | *
143 | * Marsaglia, George, and Wai Wan Tsang.
144 | * "The ziggurat method for generating random variables."
145 | * Journal of statistical software 5.8 (2000): 1-7.
146 | *
147 | *
148 | * Tesuaki Yotsuji. 計算機シミュレーションのための確率分布乱数生成法.
149 | * Pleiades PUBLISHING Co.,Ltd. (2010)
150 | *
151 | *
152 | * This implementation is a bit slower than {@link ZigguratFast}
153 | * but it does not require the independence of each bit to the values returned from {@link Random#nextLong()}.
154 | *
155 | */
156 | class ZigguratGeneral extends ZigguratBase implements GaussianRNG {
157 | private static final ZigguratGeneral Z_256 = new ZigguratGeneral(8, 3.6541528853610088, 0.00492867323399);
158 |
159 | private final int N;
160 | private final double R;
161 | private final double V;
162 | private final int INDEX_BIT_MASK;
163 | private final int TAIL_INDEX;
164 |
165 | private final double[] x;
166 | private final double[] xx;
167 | private final double[] t;
168 |
169 | /**
170 | * Constructs {@link ZigguratGeneral} with parameters.
171 | *
172 | * @param nBits number of rectangles (2^nBits)
173 | * @param r rightmost x_i
174 | * @param v area of the rectangle
175 | */
176 | ZigguratGeneral(int nBits, double r, double v) {
177 | N = 1 << nBits;
178 | R = r;
179 | V = v;
180 | INDEX_BIT_MASK = N - 1;
181 | TAIL_INDEX = N - 1;
182 |
183 | x = new double[N + 1];
184 | xx = new double[N + 1];
185 | t = new double[N];
186 |
187 | x[N] = v * exp(0.5 * r * r);
188 | x[N - 1] = r;
189 |
190 | for (int i = N - 2; i >= 1; i--) {
191 | x[i] = Math.sqrt(-2.0 * log(f(x[i + 1]) + v / x[i + 1]));
192 | }
193 | x[0] = 0;
194 |
195 | for (int i = 0; i < t.length; i++) {
196 | t[i] = x[i] / x[i + 1];
197 | }
198 |
199 | for (int i = 0; i < x.length; i++) {
200 | xx[i] = x[i] * x[i];
201 | }
202 | }
203 |
204 | @Override
205 | public double generate(Random random) {
206 | while (true) {
207 | int i = random.nextInt() & INDEX_BIT_MASK;
208 |
209 | double u1 = 2 * random.nextDouble() - 1;
210 | if (Math.abs(u1) < t[i]) {
211 | return u1 * x[i + 1];
212 | }
213 |
214 | if (i == TAIL_INDEX) {
215 | return Math.signum(u1) * tail(random, R);
216 | }
217 |
218 | double y = u1 * x[i + 1];
219 | double yy = y * y;
220 | double gU = exp(-0.5 * (xx[i] - yy));
221 | double gL = exp(-0.5 * (xx[i + 1] - yy));
222 |
223 | if (random.nextDouble() * (gU - gL) <= 1 - gL) {
224 | return y;
225 | }
226 | }
227 | }
228 |
229 | @Override
230 | public String toString() {
231 | return String.format("ZigguratGeneral(N = %d, R = %f, V = %f)", N, R, V);
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/fast-rng/src/main/java/biz/k11i/rng/GammaRNG.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng;
2 |
3 | import java.util.Random;
4 |
5 | import static biz.k11i.util.MathFunctions.*;
6 | import static java.lang.Math.sqrt;
7 |
8 | /**
9 | * Gamma random number generator.
10 | */
11 | public interface GammaRNG {
12 | GammaRNG FAST_RNG = new FastRNG();
13 | GammaRNG GENERAL_RNG = new GeneralRNG();
14 |
15 | /**
16 | * Generates a random value sampled from gamma distribution.
17 | *
18 | * @param random random number generator
19 | * @param shape shape parameter (alpha)
20 | * @param scale scale parameter (beta)
21 | * @return a random value
22 | */
23 | double generate(Random random, double shape, double scale);
24 |
25 | class FastRNG implements GammaRNG {
26 | private final GammaRNG mt = new GammaRNGAlgorithms.MarsagliaTsang(GaussianRNG.FAST_RNG);
27 | private final GammaRNG exponential = new GammaRNGAlgorithms.Exponential(ExponentialRNG.FAST_RNG);
28 | private final GammaRNG wh = new GammaRNGAlgorithms.WilsonHilfertyApproximation(GaussianRNG.FAST_RNG);
29 |
30 | @Override
31 | public double generate(Random random, double shape, double scale) {
32 | if (shape >= 50) {
33 | return wh.generate(random, shape, scale);
34 | }
35 | if (shape != 1.0) {
36 | return mt.generate(random, shape, scale);
37 | }
38 |
39 | // shape == 1.0
40 | return exponential.generate(random, shape, scale);
41 | }
42 | }
43 |
44 | class GeneralRNG implements GammaRNG {
45 | private final GammaRNG best = new GammaRNGAlgorithms.Best();
46 | private final GammaRNG mt = new GammaRNGAlgorithms.MarsagliaTsang(GaussianRNG.GENERAL_RNG);
47 | private final GammaRNG exponential = new GammaRNGAlgorithms.Exponential(ExponentialRNG.GENERAL_RNG);
48 | private final GammaRNG wh = new GammaRNGAlgorithms.WilsonHilfertyApproximation(GaussianRNG.GENERAL_RNG);
49 |
50 | @Override
51 | public double generate(Random random, double shape, double scale) {
52 | if (shape >= 50) {
53 | return wh.generate(random, shape, scale);
54 | }
55 | if (shape < 0.1) {
56 | return best.generate(random, shape, scale);
57 | }
58 | if (shape != 1.0) {
59 | return mt.generate(random, shape, scale);
60 | }
61 |
62 | // shape == 1.0
63 | return exponential.generate(random, shape, scale);
64 | }
65 | }
66 | }
67 |
68 | class GammaRNGAlgorithms {
69 | abstract static class BaseGammaRNG implements GammaRNG {
70 | @Override
71 | public double generate(Random random, double shape, double scale) {
72 | return generate(random, shape) * scale;
73 | }
74 |
75 | abstract double generate(Random random, double shape);
76 |
77 | @Override
78 | public String toString() {
79 | return String.format("%s", this.getClass().getSimpleName());
80 | }
81 | }
82 |
83 | /**
84 | * Implementation of Gamma random number generator using Best's algorithm (1983).
85 | *
86 | * Best, D. J.
87 | * “A note on gamma variate generators with shape parameter less than unity.”
88 | * Computing 30.2 (1983): 185-188.
89 | *
90 | */
91 | static class Best extends BaseGammaRNG {
92 | @Override
93 | double generate(Random random, double shape) {
94 | double c1 = 0.07 + 0.75 * sqrt(1 - shape);
95 | double c2 = 1 + shape * exp(-c1) / c1;
96 | double c3 = 1.0 / shape;
97 |
98 | while (true) {
99 | double u1 = random.nextDouble();
100 | double u2 = random.nextDouble();
101 | double v = c2 * u1;
102 |
103 | if (v <= 1) {
104 | double x = c1 * pow(v, c3);
105 | if (u2 <= (2 - x) / (2 + x) || u2 <= exp(-x)) {
106 | return x;
107 | }
108 | } else {
109 | double x = -log(c1 * c3 * (c2 - v));
110 | double y = x / c1;
111 | if (u2 * (shape + y - shape * y) <= 1 || u2 < pow(y, shape - 1)) {
112 | return x;
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 | static class Exponential implements GammaRNG {
120 | private final ExponentialRNG exponentialRNG;
121 |
122 | Exponential(ExponentialRNG exponentialRNG) {
123 | this.exponentialRNG = exponentialRNG;
124 | }
125 |
126 | @Override
127 | public double generate(Random random, double shape, double scale) {
128 | return exponentialRNG.generate(random, 1.0) * scale;
129 | }
130 |
131 | @Override
132 | public String toString() {
133 | return String.format("Exponential[%s]", exponentialRNG.getClass().getSimpleName());
134 | }
135 | }
136 |
137 | /**
138 | * Implementation of Gamma random number generator using Marsaglia and Tsang's algorithm (2000).
139 | *
140 | * Marsaglia, George, and Wai Wan Tsang.
141 | * "A simple method for generating gamma variables."
142 | * ACM Transactions on Mathematical Software (TOMS) 26.3 (2000): 363-372.
143 | *
144 | */
145 | static class MarsagliaTsang extends BaseGammaRNG {
146 | private final GaussianRNG gaussianRNG;
147 |
148 | MarsagliaTsang(GaussianRNG gaussianRNG) {
149 | this.gaussianRNG = gaussianRNG;
150 | }
151 |
152 | @Override
153 | double generate(Random random, double shape) {
154 | if (shape >= 1) {
155 | return generateMT(random, shape);
156 | }
157 |
158 | double r = generateMT(random, shape + 1);
159 | double u = random.nextDouble();
160 |
161 | if (shape != 0.5) {
162 | return r * pow(u, 1.0 / shape);
163 | }
164 |
165 | // shape == 0.5
166 | return r * u * u;
167 | }
168 |
169 | double generateMT(Random random, double shape) {
170 | double d = shape - 1.0 / 3;
171 | double c = 1 / sqrt(9 * d);
172 |
173 | while (true) {
174 | double x = gaussianRNG.generate(random);
175 | double v = 1 + c * x;
176 | if (v <= 0) {
177 | continue;
178 | }
179 |
180 | v = v * v * v;
181 | x = x * x;
182 |
183 | double u = random.nextDouble();
184 | if (u < 1 - 0.0331 * x * x) {
185 | return d * v;
186 | }
187 |
188 | if (log(u) < 0.5 * x + d * (1 - v + log(v))) {
189 | return d * v;
190 | }
191 | }
192 | }
193 |
194 | @Override
195 | public String toString() {
196 | return String.format("%s[%s]",
197 | getClass().getSimpleName(),
198 | this.gaussianRNG.getClass().getSimpleName());
199 | }
200 | }
201 |
202 | /**
203 | * Implementation of Gamma random number generator using Wilson-Hilferty approximation
204 | * for large shape parameter (>= 50).
205 | *
206 | * Wilson, Edwin B., and Margaret M. Hilferty.
207 | * “The distribution of chi-square.”
208 | * Proceedings of the National Academy of Sciences 17.12 (1931): 684-688.
209 | *
210 | */
211 | static class WilsonHilfertyApproximation extends BaseGammaRNG {
212 | private final GaussianRNG gaussianRNG;
213 |
214 | WilsonHilfertyApproximation(GaussianRNG gaussianRNG) {
215 | this.gaussianRNG = gaussianRNG;
216 | }
217 |
218 | @Override
219 | double generate(Random random, double shape) {
220 | double t0 = 1.0 / (9.0 * shape);
221 | double t1 = 1.0 - t0;
222 | double t2 = sqrt(t0);
223 |
224 | while (true) {
225 | double t = t1 + t2 * gaussianRNG.generate(random);
226 | if (t <= 0) {
227 | continue;
228 | }
229 |
230 | return shape * t * t * t;
231 | }
232 | }
233 |
234 | @Override
235 | public String toString() {
236 | return String.format("WilsonHilfertyApproximation[%s]", gaussianRNG.getClass().getSimpleName());
237 | }
238 | }
239 | }
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/gof/ContinuousGofTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.gof;
2 |
3 | import biz.k11i.rng.test.util.ComputationAndSorting;
4 | import biz.k11i.rng.test.util.SplittableRandomWrapper;
5 | import biz.k11i.rng.test.util.distribution.ContinuousDistribution;
6 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
7 | import biz.k11i.rng.test.util.inference.AndersonDarlingTest;
8 | import net.jafama.FastMath;
9 | import org.apache.commons.math3.distribution.RealDistribution;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.util.Arrays;
14 | import java.util.HashMap;
15 | import java.util.LinkedHashMap;
16 | import java.util.Map;
17 | import java.util.Objects;
18 | import java.util.Random;
19 | import java.util.concurrent.ForkJoinPool;
20 |
21 | /**
22 | * Provides Goodness-of-Fit test for discrete random number generator.
23 | */
24 | class ContinuousGofTest extends GoodnessOfFitTest {
25 | /**
26 | * Builds {@link ContinuousGofTest} object.
27 | */
28 | public static class Builder extends BuilderBase {
29 | private ContinuousDistribution distribution;
30 | private String name;
31 | private RandomNumberGenerator generator;
32 |
33 | @SuppressWarnings("unused")
34 | public Builder probabilityDistribution(RealDistribution distribution) {
35 | this.distribution = ProbabilityDistributions.wrap(distribution);
36 | return this;
37 | }
38 |
39 | public Builder probabilityDistribution(ContinuousDistribution distribution) {
40 | this.distribution = Objects.requireNonNull(distribution);
41 | return this;
42 | }
43 |
44 | public Builder randomNumberGenerator(String name, RandomNumberGenerator generator) {
45 | this.name = name;
46 | this.generator = Objects.requireNonNull(generator);
47 | return this;
48 | }
49 |
50 | public GoodnessOfFitTest build() {
51 | return new ContinuousGofTest(
52 | name,
53 | significanceLevel,
54 | distribution,
55 | generator,
56 | numRandomValues);
57 | }
58 | }
59 |
60 | /**
61 | * Generates continuous random variates.
62 | */
63 | @FunctionalInterface
64 | public interface RandomNumberGenerator {
65 | /**
66 | * Generates a continuous random variate.
67 | *
68 | * @param random uses to sample uniform random variates.
69 | * @return continuous random variate.
70 | */
71 | double generate(Random random);
72 | }
73 |
74 | private static final Logger LOGGER = LoggerFactory.getLogger(ContinuousGofTest.class);
75 |
76 | private final ContinuousDistribution distribution;
77 | private final RandomNumberGenerator generator;
78 | private final int numRandomValues;
79 | private final double[] x;
80 | private final double[] work;
81 | private long seed;
82 | private final BufferRecycler bufferRecycler = new BufferRecycler();
83 | private ComputationAndSorting computationAndSorting;
84 |
85 | private ContinuousGofTest(
86 | String name,
87 | double significanceLevel,
88 | ContinuousDistribution distribution,
89 | RandomNumberGenerator generator,
90 | int numRandomValues) {
91 | super(name, significanceLevel);
92 | this.distribution = distribution;
93 | this.generator = generator;
94 | this.numRandomValues = numRandomValues;
95 | this.x = new double[numRandomValues];
96 | this.work = new double[numRandomValues];
97 | }
98 |
99 | @Override
100 | public Map test() {
101 | return PerformanceMeasure.run(LOGGER, m -> {
102 | m.measure("Generate {} random numbers", numRandomValues, ignore -> {
103 | Random random = new SplittableRandomWrapper(seed++);
104 | for (int i = 0; i < numRandomValues; i++) {
105 | double rv = generator.generate(random);
106 | x[i] = distribution.cdf(rv);
107 | }
108 | });
109 |
110 | m.measure("Sort {} random numbers", x.length, ignore -> Arrays.sort(x));
111 |
112 | Map result = new LinkedHashMap<>();
113 |
114 | m.measure("Transform sorted random numbers", ignore -> {
115 | for (Transformation t : Transformation.values()) {
116 | m.measure("Transform by [{}]: p-value = {}", r -> {
117 | double[] transformed = t.transform(x, bufferRecycler);
118 | double p = AndersonDarlingTest.andersonDarlingTest(transformed);
119 | result.put(t.toString(), p);
120 |
121 | r.updateArgs(t, p);
122 | });
123 | }
124 | });
125 |
126 | return result;
127 | });
128 | }
129 |
130 | @Override
131 | public Map testInParallel(ForkJoinPool pool) {
132 | return PerformanceMeasure.run(LOGGER, m -> {
133 | if (computationAndSorting == null) {
134 | computationAndSorting = new ComputationAndSorting<>(
135 | numRandomValues,
136 | pool.getParallelism(),
137 | (r, index) -> distribution.cdf(generator.generate(r)));
138 | }
139 |
140 | m.measure("Generate & sort {} random numbers", numRandomValues, ignore -> {
141 | SplittableRandomWrapper random = new SplittableRandomWrapper(seed++);
142 | pool.invoke(computationAndSorting.newForkJoinTask(random, x, work));
143 | });
144 |
145 | Map result = new LinkedHashMap<>();
146 |
147 | m.measure("Transform sorted random numbers", ignore -> {
148 | for (Transformation t : Transformation.values()) {
149 | m.measure("Transform by [{}]: p-value = {}", r -> {
150 | double[] transformed = t.transformInParallel(pool, x, bufferRecycler);
151 | double p = AndersonDarlingTest.andersonDarlingTest(transformed);
152 | result.put(t.toString(), p);
153 |
154 | r.updateArgs(t, p);
155 | });
156 | }
157 | });
158 |
159 | return result;
160 | });
161 | }
162 | }
163 |
164 | enum Transformation {
165 | RAW {
166 | @Override
167 | public double[] transform(double[] x, BufferRecycler ignore) {
168 | return x;
169 | }
170 | },
171 |
172 | SPACING {
173 | @Override
174 | public double[] transform(double[] x, BufferRecycler bufferRecycler) {
175 | int n = x.length;
176 |
177 | double[] s = bufferRecycler.allocate("SPACING_S", n + 1);
178 | initializeS(x, s);
179 | Arrays.sort(s);
180 |
181 | // Compute S
182 | for (int i = s.length - 1; i > 0; i--) {
183 | s[i] = (n - i + 1) * (s[i] - s[i - 1]);
184 | }
185 | s[0] = (n + 1) * s[0];
186 |
187 | // Compute V
188 | double[] v = bufferRecycler.allocate("SPACING_V", n);
189 | double t = 0;
190 |
191 | for (int i = 0; i < n; i++) {
192 | v[i] = t + s[i];
193 | t = v[i];
194 | }
195 |
196 | return v;
197 | }
198 |
199 | private void initializeS(double[] values, double[] s) {
200 | // Initialize S
201 | double prevValue = 0.0;
202 | for (int i = 0; i < values.length; i++) {
203 | int si = i;
204 | int ui = i - 1;
205 | s[si] = values[ui + 1] - prevValue;
206 | prevValue = values[ui + 1];
207 | }
208 | s[s.length - 1] = 1.0 - values[values.length - 1];
209 | }
210 |
211 | @Override
212 | public double[] transformInParallel(ForkJoinPool pool, double[] x, BufferRecycler bufferRecycler) {
213 | int n = x.length;
214 |
215 | ComputationAndSorting spacingInitS = new ComputationAndSorting<>(n, pool.getParallelism(), (ignore, index) -> {
216 | if (index == 0) {
217 | return x[0];
218 | }
219 | if (index < n) {
220 | return x[index] - x[index - 1];
221 | }
222 | return 1.0 - x[n - 1];
223 | });
224 |
225 | double[] s = bufferRecycler.allocate("SPACING_S", n + 1);
226 | double[] work = bufferRecycler.allocate("SPACING_S_WORK", n + 1);
227 | pool.invoke(spacingInitS.newForkJoinTask(ComputationAndSorting.NullSplittable.INSTANCE, s, work));
228 |
229 | // Compute S and V
230 | double[] v = bufferRecycler.allocate("SPACING_V", n);
231 | v[0] = (n + 1) * s[0];
232 |
233 | for (int i = 1; i < n; i++) {
234 | v[i] = v[i - 1] + (n - i + 1) * (s[i] - s[i - 1]);
235 | }
236 |
237 | return v;
238 | }
239 | },
240 |
241 | POWER_RATIO {
242 | @Override
243 | public double[] transform(double[] x, BufferRecycler bufferRecycler) {
244 | int n = x.length;
245 | double[] result = bufferRecycler.allocate("POWER_RATIO_RESULT", n);
246 |
247 | for (int i = 0; i < n - 1; i++) {
248 | if (x[i + 1] == 0) {
249 | result[i] = 1.0;
250 | } else {
251 | result[i] = FastMath.pow(x[i] / x[i + 1], i + 1);
252 | }
253 | }
254 | result[n - 1] = FastMath.pow(x[n - 1], n);
255 |
256 | Arrays.sort(result);
257 |
258 | return result;
259 | }
260 |
261 | @Override
262 | public double[] transformInParallel(ForkJoinPool pool, double[] x, BufferRecycler bufferRecycler) {
263 | int n = x.length;
264 |
265 | ComputationAndSorting powerRatio = new ComputationAndSorting<>(n, pool.getParallelism(),
266 | (ignore, index) -> {
267 | if (index < n - 1) {
268 | if (x[index + 1] == 0) {
269 | return 1.0;
270 | }
271 | return FastMath.pow(x[index] / x[index + 1], index + 1);
272 | }
273 | return FastMath.pow(x[n - 1], n);
274 | });
275 |
276 | double[] result = bufferRecycler.allocate("POWER_RATIO_RESULT", n);
277 | double[] work = bufferRecycler.allocate("POWER_RATIO_WORK", n);
278 | pool.invoke(powerRatio.newForkJoinTask(ComputationAndSorting.NullSplittable.INSTANCE, result, work));
279 |
280 | return result;
281 | }
282 | };
283 |
284 | public abstract double[] transform(double[] x, BufferRecycler bufferRecycler);
285 |
286 | public double[] transformInParallel(ForkJoinPool pool, double[] x, BufferRecycler bufferRecycler) {
287 | return transform(x, bufferRecycler);
288 | }
289 | }
290 |
291 | class BufferRecycler {
292 | private Map buffers = new HashMap<>(10);
293 |
294 | double[] allocate(String name, int n) {
295 | return buffers.computeIfAbsent(name, ignore -> new double[n]);
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/gof/DiscreteGofTest.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.gof;
2 |
3 | import biz.k11i.rng.test.util.SplittableRandomWrapper;
4 | import biz.k11i.rng.test.util.distribution.DiscreteDistribution;
5 | import biz.k11i.rng.test.util.distribution.ProbabilityDistributions;
6 | import biz.k11i.rng.test.util.inference.MTest;
7 | import org.apache.commons.math3.distribution.IntegerDistribution;
8 | import org.apache.commons.math3.stat.inference.ChiSquareTest;
9 | import org.apache.commons.math3.stat.inference.GTest;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Arrays;
15 | import java.util.LinkedHashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.Random;
19 | import java.util.concurrent.Callable;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import java.util.concurrent.ForkJoinPool;
22 |
23 | /**
24 | * Provides Goodness-of-Fit test for discrete random number generator.
25 | */
26 | class DiscreteGofTest extends GoodnessOfFitTest {
27 | /**
28 | * Builds {@link DiscreteGofTest} object.
29 | */
30 | @SuppressWarnings("unused")
31 | public static class Builder extends BuilderBase {
32 | private DiscreteDistribution distribution;
33 | private int maxFrequencyBins;
34 | private String name;
35 | private RandomNumberGenerator generator;
36 |
37 | public Builder probabilityDistribution(IntegerDistribution distribution) {
38 | this.distribution = ProbabilityDistributions.wrap(distribution);
39 | return this;
40 | }
41 |
42 | public Builder probabilityDistribution(DiscreteDistribution distribution) {
43 | this.distribution = distribution;
44 | return this;
45 | }
46 |
47 | public Builder maxFrequencyBins(int maxFrequencyBins) {
48 | this.maxFrequencyBins = maxFrequencyBins;
49 | return this;
50 | }
51 |
52 | public Builder randomNumberGenerator(String name, RandomNumberGenerator generator) {
53 | this.name = name;
54 | this.generator = generator;
55 | return this;
56 | }
57 |
58 | public GoodnessOfFitTest build() {
59 | return new DiscreteGofTest(
60 | name,
61 | significanceLevel,
62 | distribution,
63 | generator,
64 | numRandomValues,
65 | maxFrequencyBins);
66 | }
67 | }
68 |
69 | /**
70 | * Generates discrete random variates.
71 | */
72 | @FunctionalInterface
73 | public interface RandomNumberGenerator {
74 | /**
75 | * Generates a discrete random variate.
76 | *
77 | * @param random uses to sample uniform random variates.
78 | * @return discrete random variate.
79 | */
80 | int generate(Random random);
81 | }
82 |
83 | private static final Logger LOGGER = LoggerFactory.getLogger(DiscreteGofTest.class);
84 |
85 | private final RandomNumberGenerator generator;
86 | private final int numRandomValues;
87 | private final FrequencyTable freqTable;
88 | private int seed;
89 |
90 | private DiscreteGofTest(
91 | String name,
92 | double significanceLevel,
93 | DiscreteDistribution distribution,
94 | RandomNumberGenerator generator,
95 | int numRandomValues,
96 | int maxFrequencyBins) {
97 | super(name, significanceLevel);
98 | this.generator = generator;
99 | this.numRandomValues = numRandomValues;
100 | this.freqTable = FrequencyTable.build(distribution, maxFrequencyBins);
101 | }
102 |
103 | @Override
104 | public Map test() {
105 | return PerformanceMeasure.run(LOGGER, m -> {
106 | m.measure("Generate {} random numbers and count frequencies", numRandomValues, ignore -> {
107 | Random random = new SplittableRandomWrapper(seed++);
108 | for (int i = 0; i < numRandomValues; i++) {
109 | int rv = generator.generate(random);
110 | freqTable.increment(rv);
111 | }
112 | });
113 |
114 | return buildResult(m);
115 | });
116 | }
117 |
118 | @Override
119 | public Map testInParallel(ForkJoinPool pool) {
120 | return PerformanceMeasure.run(LOGGER, m -> {
121 | m.measure("Generate {} random numbers and count frequencies", numRandomValues, ignore -> {
122 | SplittableRandomWrapper random = new SplittableRandomWrapper(seed++);
123 |
124 | int numTasks = pool.getParallelism();
125 | List> tasks = new ArrayList<>(numTasks);
126 |
127 | for (int i = 0; i < numTasks; i++) {
128 | int start = i * numRandomValues / numTasks;
129 | int end = (i + 1) * numRandomValues / numTasks;
130 |
131 | tasks.add(new RandomValueGenerationTask(generator, end - start, freqTable, random.split()));
132 | }
133 | pool.invokeAll(tasks);
134 | });
135 |
136 | return buildResult(m);
137 | });
138 | }
139 |
140 | private Map buildResult(PerformanceMeasure m) {
141 | Map result = new LinkedHashMap<>();
142 |
143 | m.measure("Test on contingency table", ignore -> {
144 | long[] freq = freqTable.sumUpAndReset();
145 | for (TestMethod test : TestMethod.values()) {
146 | m.measure("Test by [{}]: p-value = {}", r -> {
147 | double p = test.test(freqTable.probs, freq);
148 | result.put(test.toString(), p);
149 |
150 | r.updateArgs(test, p);
151 | });
152 | }
153 |
154 | long[] totalFreq = freqTable.totalFrequencies;
155 | for (TestMethod test : TestMethod.values()) {
156 | m.measure("Test total frequency by [{}]: p-value = {}", r -> {
157 | double p = test.test(freqTable.probs, totalFreq);
158 | r.updateArgs(test, p);
159 | });
160 | }
161 | });
162 |
163 | return result;
164 | }
165 | }
166 |
167 | /**
168 | * Statistical tests to test contingency tables.
169 | */
170 | enum TestMethod {
171 | CHI_SQUARE_TEST {
172 | private final ChiSquareTest chiSquareTest = new ChiSquareTest();
173 |
174 | @Override
175 | double test(double[] probabilities, long[] frequencies) {
176 | return chiSquareTest.chiSquareTest(probabilities, frequencies);
177 | }
178 | },
179 | G_TEST {
180 | private final GTest gTest = new GTest();
181 |
182 | @Override
183 | double test(double[] probabilities, long[] frequencies) {
184 | return gTest.gTest(probabilities, frequencies);
185 | }
186 | },
187 | M_TEST {
188 | @Override
189 | double test(double[] probabilities, long[] frequencies) {
190 | return MTest.mTest(probabilities, frequencies);
191 | }
192 | };
193 |
194 | abstract double test(double[] probabilities, long[] frequencies);
195 | }
196 |
197 | class FrequencyTable {
198 | private static final double EPSILON = Math.nextDown(1.0);
199 | private final boolean isParent;
200 | private final int numBins;
201 | final double[] probs;
202 | private final int[] bounds;
203 | final long[] totalFrequencies;
204 | private final long[] frequencies;
205 | private final ConcurrentHashMap children = new ConcurrentHashMap<>();
206 |
207 | private FrequencyTable(double[] probs, int[] bounds) {
208 | this(true, probs, bounds);
209 | }
210 |
211 | private FrequencyTable(boolean isParent, double[] probs, int[] bounds) {
212 | this.isParent = isParent;
213 | this.numBins = probs.length;
214 | this.probs = probs;
215 | this.bounds = bounds;
216 | this.totalFrequencies = new long[numBins];
217 | this.frequencies = new long[numBins];
218 | }
219 |
220 | static FrequencyTable build(DiscreteDistribution distribution, int maxBins) {
221 | // Calculate boundaries
222 | int[] bounds = new int[maxBins - 1];
223 | int boundaryCount = 0;
224 |
225 | for (int i = 1; i < maxBins; i++) {
226 | int bound = distribution.inverseCdf(i / (double) maxBins);
227 | if (boundaryCount > 0 && bound == bounds[boundaryCount - 1]) {
228 | continue;
229 | }
230 | double point = distribution.cdf(bound);
231 | if (point > EPSILON) {
232 | break;
233 | }
234 | bounds[boundaryCount++] = bound;
235 | }
236 | bounds = Arrays.copyOf(bounds, boundaryCount);
237 |
238 | // Calculate expected probabilities
239 | double[] probs = new double[boundaryCount + 1];
240 | double prevPoint = 0;
241 |
242 | for (int i = 0; i < boundaryCount; i++) {
243 | double point = distribution.cdf(bounds[i]);
244 | probs[i] = point - prevPoint;
245 | prevPoint = point;
246 | }
247 | probs[boundaryCount] = 1.0 - prevPoint;
248 |
249 | return new FrequencyTable(probs, bounds);
250 | }
251 |
252 | void increment(int x) {
253 | int index = Arrays.binarySearch(bounds, x);
254 | if (index < 0) {
255 | index = ~index;
256 | }
257 |
258 | frequencies[index]++;
259 | }
260 |
261 | FrequencyTable child() {
262 | return children.computeIfAbsent(
263 | Thread.currentThread().getId(),
264 | ignore -> new FrequencyTable(false, this.probs, this.bounds));
265 | }
266 |
267 | long[] sumUpAndReset() {
268 | if (!isParent) {
269 | throw new IllegalStateException("This object is not parent");
270 | }
271 |
272 | for (FrequencyTable child : children.values()) {
273 | for (int i = 0; i < numBins; i++) {
274 | this.frequencies[i] += child.frequencies[i];
275 | child.frequencies[i] = 0;
276 | }
277 | }
278 |
279 | long[] result = Arrays.copyOf(frequencies, numBins);
280 | for (int i = 0; i < numBins; i++) {
281 | totalFrequencies[i] += frequencies[i];
282 | frequencies[i] = 0;
283 | }
284 |
285 | return result;
286 | }
287 | }
288 |
289 | class RandomValueGenerationTask implements Callable {
290 | private final DiscreteGofTest.RandomNumberGenerator generator;
291 | private final int numRandomValues;
292 | private final FrequencyTable parentFrequencyTable;
293 | private final SplittableRandomWrapper random;
294 |
295 | RandomValueGenerationTask(
296 | DiscreteGofTest.RandomNumberGenerator generator,
297 | int numRandomValues,
298 | FrequencyTable parentFrequencyTable,
299 | SplittableRandomWrapper random) {
300 | this.generator = generator;
301 | this.numRandomValues = numRandomValues;
302 | this.parentFrequencyTable = parentFrequencyTable;
303 | this.random = random;
304 | }
305 |
306 | @Override
307 | public Void call() {
308 | FrequencyTable table = parentFrequencyTable.child();
309 | for (int i = 0; i < numRandomValues; i++) {
310 | int rv = generator.generate(random);
311 | table.increment(rv);
312 | }
313 | return null;
314 | }
315 | }
316 |
--------------------------------------------------------------------------------
/fast-rng-test/src/main/java/biz/k11i/rng/test/util/distribution/ProbabilityDistributions.java:
--------------------------------------------------------------------------------
1 | package biz.k11i.rng.test.util.distribution;
2 |
3 | import org.apache.commons.math3.distribution.BetaDistribution;
4 | import org.apache.commons.math3.distribution.GammaDistribution;
5 | import org.apache.commons.math3.distribution.IntegerDistribution;
6 | import org.apache.commons.math3.distribution.RealDistribution;
7 | import org.apache.commons.math3.exception.MaxCountExceededException;
8 | import org.apache.commons.math3.exception.OutOfRangeException;
9 | import org.apache.commons.math3.special.Erf;
10 | import org.apache.commons.math3.util.ContinuedFraction;
11 | import org.apache.commons.math3.util.FastMath;
12 |
13 | /**
14 | * Provides probability distributions that can compute cumulative probability function and inverse distribution function.
15 | *
16 | *
17 | * Some implementations of the probability distribution came from the
18 | * Apache Commons Math that is licensed under the Apache License 2.0.
19 | *
20 | *
21 | * Licensed to the Apache Software Foundation (ASF) under one or more
22 | * contributor license agreements. See the NOTICE file distributed with
23 | * this work for additional information regarding copyright ownership.
24 | * The ASF licenses this file to You under the Apache License, Version 2.0
25 | * (the "License"); you may not use this file except in compliance with
26 | * the License. You may obtain a copy of the License at
27 | *
28 | * http://www.apache.org/licenses/LICENSE-2.0
29 | *
30 | * Unless required by applicable law or agreed to in writing, software
31 | * distributed under the License is distributed on an "AS IS" BASIS,
32 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 | * See the License for the specific language governing permissions and
34 | * limitations under the License.
35 | *
36 | *
37 | *
38 | * {@link Gaussian}
39 | * {@link Gamma}
40 | * {@link Beta}
41 | *
42 | *
43 | */
44 | public interface ProbabilityDistributions {
45 | static ContinuousDistribution gaussian(double mean, double sd) {
46 | return new Gaussian(mean, sd);
47 | }
48 |
49 | static ContinuousDistribution gamma(double shape, double scale) {
50 | return new Gamma(shape, scale);
51 | }
52 |
53 | static ContinuousDistribution beta(double alpha, double beta) {
54 | return new Beta(alpha, beta);
55 | }
56 |
57 | static ContinuousDistribution wrap(RealDistribution distribution) {
58 | return new CommonsMath3DistributionWrapper.Continuous(distribution);
59 | }
60 |
61 | static DiscreteDistribution wrap(IntegerDistribution distribution) {
62 | return new CommonsMath3DistributionWrapper.Discrete(distribution);
63 | }
64 | }
65 |
66 | /**
67 | * Implementation of Gaussian distrbution.
68 | *
69 | * This class includes the code came from {@link org.apache.commons.math3.distribution.NormalDistribution},
70 | * {@link org.apache.commons.math3.special.Gamma} and {@link Erf}.
71 | *
72 | */
73 | class Gaussian implements ContinuousDistribution {
74 | private static final double EPSILON = 1.0e-15;
75 | private static final int MAX_ITERATIONS = 10000;
76 | private static final double SQRT2 = FastMath.sqrt(2.0);
77 | private static final double A = 0.5;
78 | private static final double LOG_GAMMA_A = org.apache.commons.math3.special.Gamma.logGamma(A);
79 | private static final ContinuedFraction fraction = new ContinuedFraction() {
80 | /** {@inheritDoc} */
81 | @Override
82 | protected double getA(int n, double x) {
83 | return ((2.0 * n) + 1.0) - A + x;
84 | }
85 |
86 | /** {@inheritDoc} */
87 | @Override
88 | protected double getB(int n, double x) {
89 | return n * (A - n);
90 | }
91 | };
92 |
93 | private final double mean;
94 | private final double sd;
95 |
96 |
97 | Gaussian(double mean, double sd) {
98 | this.mean = mean;
99 | this.sd = sd;
100 | }
101 |
102 | @Override
103 | public double cdf(double x) {
104 | final double dev = x - mean;
105 | if (FastMath.abs(dev) > 40 * sd) {
106 | return dev < 0 ? 0.0d : 1.0d;
107 | }
108 | return 0.5 * erfc(-dev / (sd * SQRT2));
109 | }
110 |
111 | @Override
112 | public double inverseCdf(double p) {
113 | if (p < 0.0 || p > 1.0) {
114 | throw new OutOfRangeException(p, 0, 1);
115 | }
116 | return mean + sd * SQRT2 * Erf.erfInv(2 * p - 1);
117 | }
118 |
119 | private static double erfc(double x) {
120 | if (FastMath.abs(x) > 40) {
121 | return x > 0 ? 0 : 2;
122 | }
123 | final double ret = regularizedGammaQ(x * x);
124 | return x < 0 ? 2 - ret : ret;
125 | }
126 |
127 | private static double regularizedGammaP(double x) {
128 | double ret;
129 |
130 | // calculate series
131 | double n = 0.0; // current element index
132 | double an = 1.0 / A; // n-th element in the series
133 | double sum = an; // partial sum
134 | while (FastMath.abs(an / sum) > EPSILON &&
135 | n < MAX_ITERATIONS &&
136 | sum < Double.POSITIVE_INFINITY) {
137 | // compute next element in the series
138 | n += 1.0;
139 | an *= x / (A + n);
140 |
141 | // update partial sum
142 | sum += an;
143 | }
144 | if (n >= MAX_ITERATIONS) {
145 | throw new MaxCountExceededException(MAX_ITERATIONS);
146 | } else if (Double.isInfinite(sum)) {
147 | ret = 1.0;
148 | } else {
149 | ret = FastMath.exp(-x + (A * FastMath.log(x)) - LOG_GAMMA_A) * sum;
150 | }
151 |
152 | return ret;
153 | }
154 |
155 | private static double regularizedGammaQ(double x) {
156 | double ret;
157 |
158 | if (x == 0.0) {
159 | ret = 1.0;
160 | } else if (x < A + 1.0) {
161 | ret = 1.0 - regularizedGammaP(x);
162 | } else {
163 | ret = 1.0 / fraction.evaluate(x, EPSILON, MAX_ITERATIONS);
164 | ret = FastMath.exp(-x + (A * FastMath.log(x)) - LOG_GAMMA_A) * ret;
165 | }
166 |
167 | return ret;
168 | }
169 | }
170 |
171 | /**
172 | * Implementation of Gamma distrbution.
173 | *
174 | * This class includes the code came from {@link org.apache.commons.math3.distribution.GammaDistribution} and
175 | * {@link org.apache.commons.math3.special.Gamma}.
176 | *
177 | */
178 | class Gamma extends ContinuousDistributionBase {
179 | private static final double DEFAULT_EPSILON = 10e-15;
180 |
181 | private final double a;
182 | private final double scale;
183 | private final double logGammaA;
184 | private final ContinuedFraction fraction;
185 |
186 | Gamma(double shape, double scale) {
187 | super(new GammaDistribution(shape, scale), GammaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
188 | this.a = shape;
189 | this.scale = scale;
190 | this.logGammaA = org.apache.commons.math3.special.Gamma.logGamma(a);
191 | this.fraction = new ContinuedFraction() {
192 | /** {@inheritDoc} */
193 | @Override
194 | protected double getA(int n, double x) {
195 | return ((2.0 * n) + 1.0) - a + x;
196 | }
197 |
198 | /** {@inheritDoc} */
199 | @Override
200 | protected double getB(int n, double x) {
201 | return n * (a - n);
202 | }
203 | };
204 | }
205 |
206 | @Override
207 | public double cdf(double x) {
208 | if (x <= 0) {
209 | return 0;
210 | } else {
211 | return regularizedGammaP(x / scale);
212 | }
213 | }
214 |
215 | private double regularizedGammaP(double x) {
216 | double ret;
217 |
218 | if (x >= a + 1) {
219 | // use regularizedGammaQ because it should converge faster in this
220 | // case.
221 | ret = 1.0 - regularizedGammaQ(x);
222 | } else {
223 | // calculate series
224 | double n = 0.0; // current element index
225 | double an = 1.0 / a; // n-th element in the series
226 | double sum = an; // partial sum
227 | while (FastMath.abs(an / sum) > DEFAULT_EPSILON &&
228 | n < Integer.MAX_VALUE &&
229 | sum < Double.POSITIVE_INFINITY) {
230 | // compute next element in the series
231 | n += 1.0;
232 | an *= x / (a + n);
233 |
234 | // update partial sum
235 | sum += an;
236 | }
237 | if (n >= Integer.MAX_VALUE) {
238 | throw new MaxCountExceededException(Integer.MAX_VALUE);
239 | } else if (Double.isInfinite(sum)) {
240 | ret = 1.0;
241 | } else {
242 | ret = FastMath.exp(-x + (a * FastMath.log(x)) - logGammaA) * sum;
243 | }
244 | }
245 |
246 | return ret;
247 | }
248 |
249 | private double regularizedGammaQ(double x) {
250 | double ret;
251 |
252 | ret = 1.0 / fraction.evaluate(x, DEFAULT_EPSILON, Integer.MAX_VALUE);
253 | ret = FastMath.exp(-x + (a * FastMath.log(x)) - logGammaA) * ret;
254 |
255 | return ret;
256 | }
257 | }
258 |
259 | /**
260 | * Implementation of Beta distrbution.
261 | *
262 | * This class includes the code came from {@link org.apache.commons.math3.distribution.BetaDistribution} and
263 | * {@link org.apache.commons.math3.special.Beta}.
264 | *
265 | */
266 | class Beta extends ContinuousDistributionBase {
267 | private static final double DEFAULT_EPSILON = 1E-14;
268 |
269 | private final double a;
270 | private final double b;
271 | private final double t1;
272 | private final double t2;
273 | private final double logA;
274 | private final double logBeta;
275 | private final ContinuedFraction fraction;
276 | private final Beta sym;
277 |
278 | Beta(double alpha, double beta) {
279 | this(alpha, beta, null);
280 | }
281 |
282 | private Beta(double alpha, double beta, Beta sym) {
283 | super(new BetaDistribution(alpha, beta), BetaDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
284 |
285 | this.a = alpha;
286 | this.b = beta;
287 | this.t1 = (alpha + 1) / (2 + beta + alpha);
288 | this.t2 = (beta + 1) / (2 + beta + alpha);
289 | this.logA = FastMath.log(alpha);
290 | this.logBeta = org.apache.commons.math3.special.Beta.logBeta(alpha, beta);
291 | this.fraction = new ContinuedFraction() {
292 | /** {@inheritDoc} */
293 | @Override
294 | protected double getB(int n, double x) {
295 | double ret;
296 | double m;
297 | if (n % 2 == 0) { // even
298 | m = n / 2.0;
299 | ret = (m * (b - m) * x) /
300 | ((a + (2 * m) - 1) * (a + (2 * m)));
301 | } else {
302 | m = (n - 1.0) / 2.0;
303 | ret = -((a + m) * (a + b + m) * x) /
304 | ((a + (2 * m)) * (a + (2 * m) + 1.0));
305 | }
306 | return ret;
307 | }
308 |
309 | /** {@inheritDoc} */
310 | @Override
311 | protected double getA(int n, double x) {
312 | return 1.0;
313 | }
314 | };
315 |
316 | if (sym == null) {
317 | this.sym = new Beta(beta, alpha, this);
318 | } else {
319 | this.sym = sym;
320 | }
321 | }
322 |
323 | @Override
324 | public double cdf(double x) {
325 | if (x <= 0) {
326 | return 0;
327 | } else if (x >= 1) {
328 | return 1;
329 | } else {
330 | return regularizedBeta(x);
331 | }
332 | }
333 |
334 | private double regularizedBeta(double x) {
335 | double ret;
336 |
337 | if (x > t1 &&
338 | 1 - x <= t2) {
339 | ret = 1 - sym.regularizedBeta(1 - x);
340 | } else {
341 | ret = FastMath.exp((a * FastMath.log(x)) + (b * FastMath.log1p(-x)) -
342 | logA - logBeta) *
343 | 1.0 / fraction.evaluate(x, DEFAULT_EPSILON, Integer.MAX_VALUE);
344 | }
345 |
346 | return ret;
347 | }
348 | }
349 |
350 | interface CommonsMath3DistributionWrapper {
351 | class Continuous implements ContinuousDistribution {
352 | private final RealDistribution distribution;
353 |
354 | Continuous(RealDistribution distribution) {
355 | this.distribution = distribution;
356 | }
357 |
358 | @Override
359 | public double cdf(double x) {
360 | return distribution.cumulativeProbability(x);
361 | }
362 |
363 | @Override
364 | public double inverseCdf(double p) {
365 | return distribution.inverseCumulativeProbability(p);
366 | }
367 | }
368 |
369 | class Discrete implements DiscreteDistribution {
370 | private final IntegerDistribution distribution;
371 |
372 | Discrete(IntegerDistribution distribution) {
373 | this.distribution = distribution;
374 | }
375 |
376 | @Override
377 | public double cdf(int x) {
378 | return distribution.cumulativeProbability(x);
379 | }
380 |
381 | @Override
382 | public int inverseCdf(double p) {
383 | return distribution.inverseCumulativeProbability(p);
384 | }
385 | }
386 | }
387 |
--------------------------------------------------------------------------------