Resources Numerical accuracy Calculation policy

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.

addons/flow/lib/rng.js ModelCraft 0.8.1 64-bit 4 calculations Reviewed

Tests None found by the review

Seeds from a string

Exact

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.

exponential: −mean × ln(1 − u); Erlang: −(mean ÷ k) × ln(Π (1 − uᵢ)); normal: mean + sd × √(−2 ln u₁) × cos(2π u₂); triangular: a + √(u (b − a)(m − a)) or b − √((1 − u)(b − a)(b − m))

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.

Every policy that takes this approach