Random numbers and time distributions for material flow
Draws the arrival, service, breakdown and repair times of a material flow run from a seeded random number generator.
How the precision is kept
The generator is sfc32 in 32-bit integer arithmetic, seeded through a string hash, so the same seed and key give the same numbers on every machine. Each draw is a whole number from 0 to 2³² − 1 divided by 2³², which is exact in a 64-bit number. The distributions turn draws into times with 64-bit logarithms, square roots and cosines.
Calculations under this policy
4 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
Turns the seed and a stream key into four 32-bit seeds.
Math.imul and bit shifts on 32-bit integers only; no floating-point step, so the seeds are the same on every machine.
addons/flow/lib/rng.js
lines 16 to 28
ModelCraft 0.8.1
/** 32-bit string hash (xmur3-style mixing) → a function giving four seeds. */
function hashSeeds(str) {
let h = 1779033703 ^ str.length;
for (let i = 0; i < str.length; i++) {
h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
h = (h << 13) | (h >>> 19);
}
return () => {
h = Math.imul(h ^ (h >>> 16), 2246822507);
h = Math.imul(h ^ (h >>> 13), 3266489909);
return (h ^= h >>> 16) >>> 0;
};
}
Uniform random number
64-bit
One number from 0 up to, but not including, 1.
next = (32-bit state output) ÷ 4294967296
The state update is 32-bit integer arithmetic. The output is an unsigned 32-bit integer divided by 2³², which a 64-bit number holds exactly; it has 32 bits of resolution, not 53. The first 12 outputs are discarded.
addons/flow/lib/rng.js
lines 30 to 46
ModelCraft 0.8.1
export function makeRng(seed, key = '') {
const s = hashSeeds(String(seed) + '\u0000' + String(key));
let a = s(), b = s(), c = s(), d = s();
const next = () => {
a >>>= 0; b >>>= 0; c >>>= 0; d >>>= 0;
let t = (a + b) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
d = (d + 1) | 0;
t = (t + d) | 0;
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
for (let i = 0; i < 12; i++) next(); // warm the state up
return { next };
}
One time from a distribution
64-bit
A time in seconds drawn from a constant, uniform, exponential, Erlang, normal or triangular distribution.
The logarithms take 1 − u, which is never 0 because u is below 1. The Erlang case multiplies k factors and takes one logarithm of the product; cleanDist limits k to 100. The triangular case returns a when b equals a, so the division by b − a is guarded.
addons/flow/lib/rng.js
lines 81 to 107
ModelCraft 0.8.1
export function sample(d, r) {
switch (d.kind) {
case 'const': return d.v;
case 'uniform': return d.a + (d.b - d.a) * r.next();
case 'exp': return -d.mean * Math.log(1 - r.next());
case 'erlang': {
let p = 1;
for (let i = 0; i < d.k; i++) p *= 1 - r.next();
return -(d.mean / d.k) * Math.log(p);
}
case 'normal': {
for (let i = 0; i < 64; i++) {
const u1 = 1 - r.next(), u2 = r.next();
const x = d.mean + d.sd * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
if (x >= 0) return x;
}
return Math.max(0, d.mean);
}
case 'tri': {
const u = r.next(), { a, m, b } = d;
if (b === a) return a;
const f = (m - a) / (b - a);
return u < f ? a + Math.sqrt(u * (b - a) * (m - a)) : b - Math.sqrt((1 - u) * (b - a) * (b - m));
}
}
return 0;
}
Mean of a distribution
64-bit
The mean time of a distribution, as the panel shows it.
uniform: (a + b) ÷ 2; triangular: (a + m + b) ÷ 3; others: the stated mean
Closed forms in 64-bit. For the normal distribution this is the mean before negative draws are discarded, so the mean of the sampled times is higher when sd is large against the mean.
addons/flow/lib/rng.js
lines 70 to 79
ModelCraft 0.8.1
export function meanOf(d) {
switch (d.kind) {
case 'const': return d.v;
case 'uniform': return (d.a + d.b) / 2;
case 'exp': case 'erlang': return d.mean;
case 'normal': return d.mean; // before the cut at 0; the panel says so when sd is large
case 'tri': return (d.a + d.m + d.b) / 3;
}
return NaN;
}
In detail
Method
sfc32 generator; xmur3-style string hash for the seeds; inverse transform for exponential, Erlang and triangular; Box–Muller for normal, drawn again while negative.
Number types
32-bit integers inside the generator (by design); 64-bit numbers for every sampled time.
Limits
A uniform draw has 2³² possible values, so sampled times have at most 32 bits of randomness. A normal time is redrawn up to 64 times while negative, then max(0, mean) is returned.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.