Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lcg #35

Merged
merged 18 commits into from Aug 19, 2020
12 changes: 8 additions & 4 deletions README.md
Expand Up @@ -108,9 +108,13 @@ Returns a function for generating random numbers with a [Poisson distribution](h
Returns the same type of function for generating random numbers but where the given random number generator *source* is used as the source of randomness instead of Math.random. The given random number generator must implement the same interface as Math.random and only return values in the range [0, 1). This is useful when a seeded random number generator is preferable to Math.random. For example:

```js
var d3 = require("d3-random"),
seedrandom = require("seedrandom"),
random = d3.randomNormal.source(seedrandom("a22ebc7c488a3a47"))(0, 1);
const d3 = require("d3-random"),
seed = 42,
random = d3.randomNormal.source(d3.randomLcg(seed))(0, 1);

random(); // 0.9744193494813501
random(); // -0.26990580687568544
```

<a name="randomLcg" href="#randomLcg">#</a> d3.<b>randomLcg</b>(<i>[seed]</i>) · [Examples](https://observablehq.com/@fil/linear-congruential-generator)

Returns a [Linear congruential generator](https://en.wikipedia.org/wiki/Linear_congruential_generator), seeded on the specified non-negative integer, which defaults to 0. The generator can be called repeatedly to obtain a pseudo-random sequence of values, well-distributed on the interval [0,1), and with a long period (up to 1 billion numbers). When run with the same seed, the sequence is guaranteed to be the same. Different seeds will usually result in different sequences, but no guarantee is made.
Fil marked this conversation as resolved.
Show resolved Hide resolved
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -39,7 +39,6 @@
"jsdom": "15",
"rollup": "1",
"rollup-plugin-terser": "5",
"seedrandom": "2",
"tape": "4"
}
}
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -15,3 +15,4 @@ export {default as randomWeibull} from "./weibull.js";
export {default as randomCauchy} from "./cauchy.js";
export {default as randomLogistic} from "./logistic.js";
export {default as randomPoisson} from "./poisson.js";
export {default as randomLcg} from "./lcg.js";
11 changes: 11 additions & 0 deletions src/lcg.js
@@ -0,0 +1,11 @@
const a = 1664525;
Fil marked this conversation as resolved.
Show resolved Hide resolved
const c = 1013904223;
const m = 4294967296; // 2^32

export default function lcg(s = 1) {
if (s < 0) s = Math.abs(s);
if (s < 1) s = Math.floor(a + m * s);
const random = () => (s = (a * s + c) % m) / m;
random(), random(), random(), random(); // initial mix
return random;
}
13 changes: 6 additions & 7 deletions test/bates-test.js
@@ -1,13 +1,12 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
d3 = Object.assign({}, require("../"), require("d3-array")),
skewness = require("./skewness"),
kurtosis = require("./kurtosis");

require("./inDelta");

tape("d3.randomBates(n) returns random numbers with a mean of one-half", function(test) {
var randomBates = d3.randomBates.source(seedrandom("f330fbece4c1c99f"));
var randomBates = d3.randomBates.source(d3.randomLcg(1));
test.inDelta(d3.mean(d3.range(10000).map(randomBates(1))), 0.5, 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomBates(10))), 0.5, 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomBates(1.5))), 0.5, 0.05);
Expand All @@ -16,7 +15,7 @@ tape("d3.randomBates(n) returns random numbers with a mean of one-half", functio
});

tape("d3.randomBates(n) returns random numbers with a variance of 1 / (12 * n)", function(test) {
var randomBates = d3.randomBates.source(seedrandom("c4af5ee918417093"));
var randomBates = d3.randomBates.source(d3.randomLcg(2));
test.inDelta(d3.variance(d3.range(10000).map(randomBates(1))), 1 / 12, 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomBates(10))), 1 / 120, 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomBates(1.5))), 1 / 18, 0.05);
Expand All @@ -25,7 +24,7 @@ tape("d3.randomBates(n) returns random numbers with a variance of 1 / (12 * n)",
});

tape("d3.randomBates(n) returns random numbers with a skewness of 0", function(test) {
var randomBates = d3.randomBates.source(seedrandom("bb0bb470f346ff65"));
var randomBates = d3.randomBates.source(d3.randomLcg(3));
test.inDelta(skewness(d3.range(10000).map(randomBates(1))), 0, 0.05);
test.inDelta(skewness(d3.range(10000).map(randomBates(10))), 0, 0.05);
test.inDelta(skewness(d3.range(10000).map(randomBates(1.5))), 0, 0.05);
Expand All @@ -34,16 +33,16 @@ tape("d3.randomBates(n) returns random numbers with a skewness of 0", function(t
});

tape("d3.randomBates(n) returns random numbers with a kurtosis of -6 / (5 * n)", function(test) {
var randomBates = d3.randomBates.source(seedrandom("3c21f0c8f5a8332c"));
var randomBates = d3.randomBates.source(d3.randomLcg(4));
test.inDelta(kurtosis(d3.range(10000).map(randomBates(1))), -6 / 5, 0.05);
test.inDelta(kurtosis(d3.range(10000).map(randomBates(10))), -6 / 50, 0.05);
test.inDelta(kurtosis(d3.range(10000).map(randomBates(10))), -6 / 50, 0.1);
test.inDelta(kurtosis(d3.range(10000).map(randomBates(1.5))), -6 / 7.5, 0.05);
test.inDelta(kurtosis(d3.range(10000).map(randomBates(4.2))), -6 / 21, 0.05);
test.end();
});

tape("d3.randomBates(0) is equivalent to d3.randomUniform()", function(test) {
var randomBates = d3.randomBates.source(seedrandom("7f1d6e8020b157d6"));
var randomBates = d3.randomBates.source(d3.randomLcg(5));
test.inDelta(d3.mean(d3.range(10000).map(randomBates(0))), 0.5, 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomBates(0))), 1 / 12, 0.05);
test.inDelta(skewness(d3.range(10000).map(randomBates(0))), 0, 0.05);
Expand Down
19 changes: 9 additions & 10 deletions test/bernoulli-test.js
@@ -1,5 +1,4 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
skewness = require("./skewness"),
kurtosis = require("./kurtosis"),
d3 = Object.assign({}, require("../"), require("d3-array"));
Expand All @@ -12,7 +11,7 @@ var skew = function(p) { return (1 - 2 * p) / Math.sqrt(variance(p)); };
var kurt = function(p) { return (6 * Math.pow(p, 2) - 6 * p + 1) / variance(p); };

tape("randomBernoulli(p) returns random bernoulli distributed numbers with a mean of p", function(test) {
var randomBernoulli = d3.randomBernoulli.source(seedrandom("d5cb594f444fc692"));
var randomBernoulli = d3.randomBernoulli.source(d3.randomLcg(1));
test.inDelta(d3.mean(d3.range(10000).map(randomBernoulli(1))), mean(1), variance(1));
test.inDelta(d3.mean(d3.range(10000).map(randomBernoulli(.5))), mean(.5), variance(.5));
test.inDelta(d3.mean(d3.range(10000).map(randomBernoulli(.25))), mean(.25), variance(.25));
Expand All @@ -21,7 +20,7 @@ tape("randomBernoulli(p) returns random bernoulli distributed numbers with a mea
});

tape("randomBernoulli(p) returns random bernoulli distributed numbers with a variance of p * (1 - p)", function(test) {
var randomBernoulli = d3.randomBernoulli.source(seedrandom("c4af5ee918417093"));
var randomBernoulli = d3.randomBernoulli.source(d3.randomLcg(2));
test.inDelta(d3.variance(d3.range(10000).map(randomBernoulli(1))), variance(1), 0);
test.inDelta(d3.variance(d3.range(10000).map(randomBernoulli(.5))), variance(.5), 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomBernoulli(.25))), variance(.25), 0.05);
Expand All @@ -30,17 +29,17 @@ tape("randomBernoulli(p) returns random bernoulli distributed numbers with a var
});

tape("randomBernoulli(p) returns random bernoulli distributed numbers with a skewness of (1 - 2 * p) / sqrt(p * (1 - p)).", function(test) {
var randomBernoulli = d3.randomBernoulli.source(seedrandom("bb0bb470f346ff65"));
test.inDelta(skewness(d3.range(10000).map(randomBernoulli(.5))), skew(.5), 0.05);
var randomBernoulli = d3.randomBernoulli.source(d3.randomLcg(3));
test.inDelta(skewness(d3.range(10000).map(randomBernoulli(.5))), skew(.5), 0.08);
test.inDelta(skewness(d3.range(10000).map(randomBernoulli(.25))), skew(.25), 0.05);
test.end();
});

tape("randomBernoulli(p) returns random bernoulli distributed numbers with a kurtosis excess of (6 * p^2 - 6 * p - 1) / (p * (1 - p)).", function(test) {
var randomBernoulli = d3.randomBernoulli.source(seedrandom("e6roo8u1129lg5lx"));
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.05))), kurt(.05), kurt(.05) * 0.1);
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.10))), kurt(.10), kurt(.10) * 0.1);
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.15))), kurt(.15), kurt(.15) * 0.1);
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.20))), kurt(.20), kurt(.20) * 0.1);
var randomBernoulli = d3.randomBernoulli.source(d3.randomLcg(4));
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.05))), kurt(.05), kurt(.05) * 0.2);
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.10))), kurt(.10), kurt(.10) * 0.2);
test.inDelta(kurtosis(d3.range(10000).map(randomBernoulli(.15))), kurt(.15), kurt(.15) * 0.2);
test.inDelta(kurtosis(d3.range(50000).map(randomBernoulli(.20))), kurt(.20), kurt(.20) * 0.4);
test.end();
});
5 changes: 2 additions & 3 deletions test/beta-test.js
@@ -1,5 +1,4 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
d3 = Object.assign({}, require("../"), require("d3-array"));

require("./inDelta");
Expand All @@ -8,7 +7,7 @@ var mean = function(alpha, beta) { return alpha / (alpha + beta); };
var variance = function(alpha, beta) { return (alpha * beta) / Math.pow(alpha + beta, 2) / (alpha + beta + 1); };

tape("randomBeta(alpha, beta) returns random numbers with a mean of alpha / (alpha + beta)", function(test) {
var randomBeta = d3.randomBeta.source(seedrandom("1338e6f554b1da6d"));
var randomBeta = d3.randomBeta.source(d3.randomLcg(1));
test.inDelta(d3.mean(d3.range(10000).map(randomBeta(1, 1))), mean(1, 1), 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomBeta(1, 2))), mean(1, 2), 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomBeta(2, 1))), mean(2, 1), 0.05);
Expand All @@ -19,7 +18,7 @@ tape("randomBeta(alpha, beta) returns random numbers with a mean of alpha / (alp
});

tape("randomBeta(alpha, beta) returns random numbers with a variance of (alpha * beta) / (alpha + beta)^2 / (alpha + beta + 1)", function(test) {
var randomBeta = d3.randomBeta.source(seedrandom("f86d852a93c73d91"));
var randomBeta = d3.randomBeta.source(d3.randomLcg(2));
test.inDelta(d3.variance(d3.range(10000).map(randomBeta(1, 1))), variance(1, 1), 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomBeta(1, 2))), variance(1, 2), 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomBeta(2, 1))), variance(2, 1), 0.05);
Expand Down
9 changes: 4 additions & 5 deletions test/binomial-test.js
@@ -1,5 +1,4 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
skewness = require("./skewness"),
kurtosis = require("./kurtosis"),
d3 = Object.assign({}, require("../"), require("d3-array"));
Expand All @@ -12,7 +11,7 @@ var skew = function(n, p) { return (1 - 2 * p) / Math.sqrt(variance(n, p)); };
var kurt = function(n, p) { return (6 * Math.pow(p, 2) - 6 * p + 1) / (variance(n, p)); };

tape("randomBinomial(n, p) returns random binomial distributed numbers with a mean of n * p", function(test) {
var randomBinomial = d3.randomBinomial.source(seedrandom("d5cb594f444fc692"));
var randomBinomial = d3.randomBinomial.source(d3.randomLcg(1));
test.inDelta(d3.mean(d3.range(10000).map(randomBinomial(100, 1))), mean(100, 1), variance(100, 1));
test.inDelta(d3.mean(d3.range(10000).map(randomBinomial(100, .5))), mean(100, .5), variance(100, .5));
test.inDelta(d3.mean(d3.range(10000).map(randomBinomial(100, .25))), mean(100, .25), variance(100, .25));
Expand All @@ -22,7 +21,7 @@ tape("randomBinomial(n, p) returns random binomial distributed numbers with a me
});

tape("randomBinomial(n, p) returns random binomial distributed numbers with a variance of n * p * (1 - p)", function(test) {
var randomBinomial = d3.randomBinomial.source(seedrandom("c4af5ee918417093"));
var randomBinomial = d3.randomBinomial.source(d3.randomLcg(2));
test.inDelta(d3.variance(d3.range(10000).map(randomBinomial(100, 1))), variance(100, 1), 0);
test.inDelta(d3.variance(d3.range(10000).map(randomBinomial(100, .5))), variance(100, .5), 0.5);
test.inDelta(d3.variance(d3.range(10000).map(randomBinomial(100, .25))), variance(100, .25), 0.5);
Expand All @@ -32,7 +31,7 @@ tape("randomBinomial(n, p) returns random binomial distributed numbers with a va
});

tape("randomBinomial(n, p) returns random binomial distributed numbers with a skewness of (1 - 2 * p) / sqrt(n * p * (1 - p))", function(test) {
var randomBinomial = d3.randomBinomial.source(seedrandom("tp4ywtastf9i9408"));
var randomBinomial = d3.randomBinomial.source(d3.randomLcg(3));
test.inDelta(skewness(d3.range(10000).map(randomBinomial(100, .05))), skew(100, .05), 0.05);
test.inDelta(skewness(d3.range(10000).map(randomBinomial(100, .10))), skew(100, .10), 0.05);
test.inDelta(skewness(d3.range(10000).map(randomBinomial(100, .15))), skew(100, .15), 0.05);
Expand All @@ -46,7 +45,7 @@ tape("randomBinomial(n, p) returns random binomial distributed numbers with a sk
});

tape("randomBinomial(n, p) returns random binomial distributed numbers with a kurtosis excess of (6 * p^2 - 6 * p - 1) / (n * p * (1 - p))", function(test) {
var randomBinomial = d3.randomBinomial.source(seedrandom("n8qthobcylorx9b1"));
var randomBinomial = d3.randomBinomial.source(d3.randomLcg(4));
test.inDelta(kurtosis(d3.range(10000).map(randomBinomial(100, .05))), kurt(100, .05), 0.2);
test.inDelta(kurtosis(d3.range(10000).map(randomBinomial(100, .10))), kurt(100, .10), 0.1);
test.inDelta(kurtosis(d3.range(10000).map(randomBinomial(100, .15))), kurt(100, .15), 0.1);
Expand Down
3 changes: 1 addition & 2 deletions test/cauchy-test.js
@@ -1,5 +1,4 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
d3 = Object.assign({}, require("../"), require("d3-array"));

require("./inDelta");
Expand All @@ -8,7 +7,7 @@ require("./inDelta");
// we simply test for the median, equivalent to the location parameter.

tape("randomCauchy(a, b) returns random numbers with a median of a", function(test) {
var randomCauchy = d3.randomCauchy.source(seedrandom("60a03c00bbf7486f"));
var randomCauchy = d3.randomCauchy.source(d3.randomLcg(42));
test.inDelta(d3.median(d3.range(10000).map(randomCauchy())), 0, 0.05);
test.inDelta(d3.median(d3.range(10000).map(randomCauchy(5))), 5, 0.05);
test.inDelta(d3.median(d3.range(10000).map(randomCauchy(0, 4))), 0, 0.1);
Expand Down
3 changes: 1 addition & 2 deletions test/exponential-test.js
@@ -1,11 +1,10 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
d3 = Object.assign({}, require("../"), require("d3-array"));

require("./inDelta");

tape("d3.randomExponential(lambda) returns random exponentially distributed numbers with a mean of 1/lambda.", function(test) {
var randomExponential = d3.randomExponential.source(seedrandom("d5cb594f444fc692"));
var randomExponential = d3.randomExponential.source(d3.randomLcg(42));

var mean = 20,
lambda = 1 / mean, // average rate (e.g. 1 per 20 minutes)
Expand Down
19 changes: 9 additions & 10 deletions test/gamma-test.js
@@ -1,14 +1,13 @@
var tape = require("tape"),
seedrandom = require("seedrandom"),
skewness = require("./skewness"),
kurtosis = require("./kurtosis"),
d3 = Object.assign({}, require("../"), require("d3-array"));

require("./inDelta");

tape("randomGamma(k) returns random numbers with a mean of k", function(test) {
var randomGamma = d3.randomGamma.source(seedrandom("ba00bda55400448e"));
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(0.1))), 0.1, 0.005);
var randomGamma = d3.randomGamma.source(d3.randomLcg(1));
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(0.1))), 0.1, 0.01);
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(0.5))), 0.5, 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(1))), 1, 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(2))), 2, 0.05);
Expand All @@ -17,7 +16,7 @@ tape("randomGamma(k) returns random numbers with a mean of k", function(test) {
});

tape("randomGamma(k) returns random numbers with a variance of k", function(test) {
var randomGamma = d3.randomGamma.source(seedrandom("cb43934c1dcf650d"));
var randomGamma = d3.randomGamma.source(d3.randomLcg(2));
test.inDelta(d3.variance(d3.range(10000).map(randomGamma(0.1))), 0.1, 0.005);
test.inDelta(d3.variance(d3.range(10000).map(randomGamma(0.5))), 0.5, 0.05);
test.inDelta(d3.variance(d3.range(10000).map(randomGamma(1))), 1, 0.05);
Expand All @@ -27,7 +26,7 @@ tape("randomGamma(k) returns random numbers with a variance of k", function(test
});

tape("randomGamma(k) returns random numbers with a skewness of 2 / sqrt(k)", function(test) {
var randomGamma = d3.randomGamma.source(seedrandom("b9e71de4d94951e0"));
var randomGamma = d3.randomGamma.source(d3.randomLcg(3));
test.inDelta(skewness(d3.range(10000).map(randomGamma(0.1))), Math.sqrt(40), 1);
test.inDelta(skewness(d3.range(10000).map(randomGamma(0.5))), Math.sqrt(8), 0.1);
test.inDelta(skewness(d3.range(10000).map(randomGamma(1))), 2, 0.1);
Expand All @@ -37,7 +36,7 @@ tape("randomGamma(k) returns random numbers with a skewness of 2 / sqrt(k)", fun
});

tape("randomGamma(k) returns random numbers with an excess kurtosis of 6 / k", function(test) {
var randomGamma = d3.randomGamma.source(seedrandom("d26f35533df47873"));
var randomGamma = d3.randomGamma.source(d3.randomLcg(4));
test.inDelta(kurtosis(d3.range(10000).map(randomGamma(0.1))), 60, 15);
test.inDelta(kurtosis(d3.range(10000).map(randomGamma(0.5))), 12, 3);
test.inDelta(kurtosis(d3.range(10000).map(randomGamma(1))), 6, 1.5);
Expand All @@ -47,10 +46,10 @@ tape("randomGamma(k) returns random numbers with an excess kurtosis of 6 / k", f
});

tape("randomGamma(k, theta) returns random numbers with a mean of k * theta and a variance of k * theta^2", function(test) {
var randomGamma = d3.randomGamma.source(seedrandom("22fe28c580a2f7d8"));
var randomGamma = d3.randomGamma.source(d3.randomLcg(5));
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(1, 2))), 2, 0.05);
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(2, 4))), 8, 0.1);
test.inDelta(d3.variance(d3.range(10000).map(randomGamma(1, 2))), 4, 0.2);
test.inDelta(d3.variance(d3.range(10000).map(randomGamma(2, 4))), 32, 1);
test.inDelta(d3.mean(d3.range(10000).map(randomGamma(2, 4))), 8, 0.2);
test.inDelta(d3.deviation(d3.range(10000).map(randomGamma(1, 2))), 2, 0.1);
test.inDelta(d3.deviation(d3.range(10000).map(randomGamma(2, 4))), Math.sqrt(2) * 4, 0.1);
test.end();
});