diff --git a/.zenodo.json b/.zenodo.json index a744f34..df774f6 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -10,10 +10,18 @@ } ], "keywords": [ - "Ziggurat", - "Gaussian", - "Java", - "normal distribution", - "pseudorandom numbers" - ] + "Ziggurat", + "Gaussian", + "Java", + "normal distribution", + "pseudorandom numbers" + ], + "related_identifiers": [ + { + "scheme": "doi", + "identifier": "10.48550/arXiv.2405.19493", + "relation": "isDocumentedBy", + "resource_type": "publication" + } + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index c73ab15..e50fd17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - 2023-10-01 +## [Unreleased] - 2024-05-31 ### Added @@ -16,12 +16,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Removed support for importing from JitPack. ### Fixed +* Refactored loop in PolarGaussian based on SpotBugs report. +* Refactored PolarGaussian to eliminate redundant code between Random and SplittableRandom cases. ### CI/CD * Integrated SpotBugs into build process. * Integrated FindSecBugs into build process. ### Other +* Updated documentation. ## [1.0.5] - 2022-12-01 diff --git a/CITATION.cff b/CITATION.cff index 73c2769..f360650 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,12 +14,17 @@ preferred-citation: - family-names: "Cicirello" given-names: "Vincent A" orcid: "https://orcid.org/0000-0003-1072-8559" - institution: "Cicirello.org" + doi: "10.48550/arXiv.2405.19493" month: 5 year: 2024 title: "Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm" - url: "https://reports.cicirello.org/24/009/" identifiers: - type: other - value: "ALG-24-009" - description: "Technical Report" + value: "arXiv:2405.19493" + description: "arXiv preprint" + - type: doi + value: 10.48550/arXiv.2405.19493 + description: "The DOI of the report" + - type: url + value: "https://reports.cicirello.org/24/009/ALG-24-009.pdf" + description: "A full-text pdf of the report" diff --git a/README.md b/README.md index 1b1c79d..9b1bf6c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ number generators as well as for `SplittableRandom`. The other legacy generators still use the slow polar method, however. This report explores how and where our ziggurat library is still relevant in Java 17. -> Vincent A. Cicirello. 2024. [Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm](https://reports.cicirello.org/24/009/). Technical Report ALG-24-009, Cicirello.org, May 2024. [[PDF]](https://reports.cicirello.org/24/009/ALG-24-009.pdf) +> Vincent A. Cicirello. 2024. [Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm](https://reports.cicirello.org/24/009/). arXiv:[2405.19493](https://arxiv.org/abs/2405.19493), May 2024. doi:[10.48550/arXiv.2405.19493](https://doi.org/10.48550/arXiv.2405.19493). [[PDF]](https://reports.cicirello.org/24/009/ALG-24-009.pdf) You can find some additional experimental data comparing the performance of a sequential genetic algorithm (GA) using this implementation of the Ziggurat method for diff --git a/experiment/README.md b/experiment/README.md index d3f172e..2c8516e 100644 --- a/experiment/README.md +++ b/experiment/README.md @@ -7,7 +7,7 @@ library, sample code, and other misc items, as follows. The code in the directory [timing17](timing17) can be used to reproduce the experiments of the following paper, which among other things explores the relevance of the library for Java 17+: -> Vincent A. Cicirello. 2024. [Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm](https://reports.cicirello.org/24/009/). Technical Report ALG-24-009, Cicirello.org, May 2024. [[PDF]](https://reports.cicirello.org/24/009/ALG-24-009.pdf) +> Vincent A. Cicirello. 2024. [Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm](https://reports.cicirello.org/24/009/). arXiv:[2405.19493](https://arxiv.org/abs/2405.19493), May 2024. doi:[10.48550/arXiv.2405.19493](https://doi.org/10.48550/arXiv.2405.19493). [[PDF]](https://reports.cicirello.org/24/009/ALG-24-009.pdf) ## Which Gaussian Algorithm Does Java Use diff --git a/experiment/timing17/README.md b/experiment/timing17/README.md index cfc3ddb..64aea67 100644 --- a/experiment/timing17/README.md +++ b/experiment/timing17/README.md @@ -2,7 +2,7 @@ The code in this directory can be used to reproduce the experiments of the following paper: -> Vincent A. Cicirello. 2024. [Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm](https://reports.cicirello.org/24/009/). Technical Report ALG-24-009, Cicirello.org, May 2024. [[PDF]](https://reports.cicirello.org/24/009/ALG-24-009.pdf) +> Vincent A. Cicirello. 2024. [Fast Gaussian Distributed Pseudorandom Number Generation in Java via the Ziggurat Algorithm](https://reports.cicirello.org/24/009/). arXiv:[2405.19493](https://arxiv.org/abs/2405.19493), May 2024. doi:[10.48550/arXiv.2405.19493](https://doi.org/10.48550/arXiv.2405.19493). [[PDF]](https://reports.cicirello.org/24/009/ALG-24-009.pdf) ## Building with Maven diff --git a/pom.xml b/pom.xml index 959957f..db7a7a7 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ repo Ziggurat Gaussian. - Copyright (C) 2019-2023 Vincent A. Cicirello. + Copyright (C) 2019-2024 Vincent A. Cicirello. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -271,7 +271,7 @@ true false true - Vincent A. Cicirello. All rights reserved.]]> + Vincent A. Cicirello. All rights reserved.]]> diff --git a/src/main/java/org/cicirello/math/rand/PolarGaussian.java b/src/main/java/org/cicirello/math/rand/PolarGaussian.java index 9069752..ecd2486 100644 --- a/src/main/java/org/cicirello/math/rand/PolarGaussian.java +++ b/src/main/java/org/cicirello/math/rand/PolarGaussian.java @@ -2,7 +2,7 @@ * Java implementation of the Polar Method * for generating Gaussian distributed random numbers. * - * Copyright 2015, 2017-2020, 2022 Vincent A. Cicirello, . + * Copyright 2015, 2017-2024 Vincent A. Cicirello, . * * This program is free software: you can * redistribute it and/or modify it under the terms of the GNU @@ -26,6 +26,7 @@ import java.util.Random; import java.util.SplittableRandom; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.DoubleSupplier; /** * This class provides methods for generating pseudorandom numbers from a Gaussian distribution @@ -130,23 +131,7 @@ public static double nextGaussian() { * @return A random number from a Gaussian distribution with mean 0 and standard deviation 1. */ public static double nextGaussian(Random r) { - Double next = nextG.get(); - if (next != null) { - nextG.set(null); - return next; - } else { - double v1 = 0; - double v2 = 0; - double s = 0; - while (s >= 1 || s == 0) { - v1 = 2 * r.nextDouble() - 1; - v2 = 2 * r.nextDouble() - 1; - s = v1 * v1 + v2 * v2; - } - double m = StrictMath.sqrt(-2 * StrictMath.log(s) / s); - nextG.set(v2 * m); - return v1 * m; - } + return nextGaussian(r::nextDouble); } /** @@ -156,19 +141,23 @@ public static double nextGaussian(Random r) { * @return A random number from a Gaussian distribution with mean 0 and standard deviation 1. */ public static double nextGaussian(SplittableRandom r) { + return nextGaussian(r::nextDouble); + } + + static double nextGaussian(DoubleSupplier r) { Double next = nextG.get(); if (next != null) { nextG.set(null); return next; } else { - double v1 = 0; - double v2 = 0; - double s = 0; - while (s >= 1 || s == 0) { - v1 = 2 * r.nextDouble() - 1; - v2 = 2 * r.nextDouble() - 1; + double v1; + double v2; + double s; + do { + v1 = 2 * r.getAsDouble() - 1; + v2 = 2 * r.getAsDouble() - 1; s = v1 * v1 + v2 * v2; - } + } while (s >= 1 || s == 0); double m = StrictMath.sqrt(-2 * StrictMath.log(s) / s); nextG.set(v2 * m); return v1 * m; diff --git a/src/main/java/org/cicirello/math/rand/ZigguratGaussian.java b/src/main/java/org/cicirello/math/rand/ZigguratGaussian.java index d9515d7..1bac179 100644 --- a/src/main/java/org/cicirello/math/rand/ZigguratGaussian.java +++ b/src/main/java/org/cicirello/math/rand/ZigguratGaussian.java @@ -3,7 +3,7 @@ * of the Ziggurat method for generating Gaussian distributed * random numbers. * - * Copyright 2015, 2017-2022 Vincent A. Cicirello, . + * Copyright 2015, 2017-2024 Vincent A. Cicirello, . * * This program is free software: you can * redistribute it and/or modify it under the terms of the GNU @@ -62,14 +62,25 @@ *

This Java implementation originated as part of an effort to speed up the runtime of a parallel * genetic algorithm (PGA). The PGA in question evolved its control parameters (i.e., crossover and * mutation rates, etc) using Gaussian mutation. The only Gaussian implementation within the Java - * API is the polar method (nextGaussian method of the {@link Random} and {@link ThreadLocalRandom} - * classes, however the polar method is quite slow relative to other newer available alternatives, - * such as the Ziggurat method. + * API (pre-Java 17) is the polar method (nextGaussian method of the {@link Random} and {@link + * ThreadLocalRandom} classes, however the polar method is quite slow relative to other newer + * available alternatives, such as the Ziggurat method. * - *

You can find some experimental data comparing the performance of a sequential genetic - * algorithm (GA) using this implementation of the Ziggurat method for Gaussian mutation vs using - * the more common polar method, as well as experimental data for the same comparison but with a - * PGA, in the following paper: + *

You can find information on when this ziggurat implementation is likely advantageous, + * including results of experiments in the following report: + * + *

+ * + *

You can find some additional experimental data comparing the performance of a sequential + * genetic algorithm (GA) using this implementation of the Ziggurat method for Gaussian mutation vs + * using the more common polar method, as well as experimental data for the same comparison but with + * a PGA, in the following paper: * *

    *
  • V. A. Cicirello. org.cicirello.math.rand * - *

    Copyright © 2015, 2017-2022 Vincent A. + *

    Copyright © 2015, 2017-2024 Vincent A. * Cicirello. * *

    You can find information on when this ziggurat implementation is likely advantageous, + * including results of experiments in the following report: + * + *

    + * *

    You can find some experimental data comparing the performance of a sequential genetic * algorithm (GA) using the implementation of the Ziggurat method for Gaussian mutation vs using the * more common polar method, as well as experimental data for the same comparison but with a PGA, in diff --git a/src/test/java/org/cicirello/math/rand/PolarGaussianTests.java b/src/test/java/org/cicirello/math/rand/PolarGaussianTests.java index 6cbcd14..1e099e7 100644 --- a/src/test/java/org/cicirello/math/rand/PolarGaussianTests.java +++ b/src/test/java/org/cicirello/math/rand/PolarGaussianTests.java @@ -1,7 +1,7 @@ /* * JUnit test cases for PolarGaussian. * - * Copyright 2019-2022 Vincent A. Cicirello, . + * Copyright 2019-2024 Vincent A. Cicirello, . * * This program is free software: you can * redistribute it and/or modify it under the terms of the GNU @@ -172,6 +172,41 @@ public void testNoParamNextGaussian10() { assertTrue(positive && negative); } + @Test + public void testLowProbEdgeCase() { + class ForceEdgeCase extends Random { + private int count; + + public ForceEdgeCase(long seed) { + super(seed); + count = 0; + } + + @Override + public double nextDouble() { + if (count < 2) { + count++; + return 0.5; + } + return super.nextDouble(); + } + } + Random r = new ForceEdgeCase(42); + int[] buckets = new int[20]; + final int N = buckets.length * EXPECTED_SAMPLES_PER_BUCKET; + for (int i = 0; i < N; i++) { + int j = whichBucket(PolarGaussian.nextGaussian(r::nextDouble)); + buckets[j]++; + } + double chi = chiSquare(buckets); + if (VERBOSE_OUTPUT) { + System.out.printf("Random, sigma=1, chi=%5.4f\n", chi); + } + assertTrue( + chi <= 30.144); // 19 degrees of freedom, 95% percentage point of chi square distribution: + // 30.144 + } + private double chiSquare(int[] buckets) { int x = 0; for (int e : buckets) {