summaryrefslogtreecommitdiff
path: root/pbt/ts/src/gen.test.ts
blob: 6e891e42c52e5885793764230d047859e622fb17 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import Prando from 'prando';
import { none } from 'fp-ts/lib/Option.js';
import * as G from './gen.js';
import { test, expect } from 'vitest';
import { chiSquare, computeChiSquaredPValue, computeCramersV } from './stats.js';

test('can generate and shrink bytes', async () => {
  const prng = new Prando(Math.random() * 1000);
  const gen = new G.BytesGen(prng);
  const bytes: number[][] = [];
  for (let i = 1; i < 10; i++) {
    bytes.push(gen.generate(i));
  }
  console.log('Generated bytes:', bytes);
  const replay = gen.replay();
  for (let i = 1; i < 10; i++) {
    const replayed = replay.generate(i);
    expect(replayed, `Replaying at ${i}`).toEqual(bytes[i - 1]);
  }
});

test('can generate an arbitrary integer', () => {
  const prng = new Prando(Math.random() * 1000);
  const gen = G.genInt();
  const value = gen.generate();
  expect(typeof value).toBe('number');
});

test('can generate N arbitrary integers', () => {
  const prng = new Prando(Math.random() * 1000);
  const gen = G.genInt();
  const value = gen.generateN(5);
  expect(value.length).toBe(5);
});

test('can generate arbitrary integer within bounds', () => {
  const seed = Math.random() * 1000;
  const gen = G.genIntWithin(1000, 200000, seed);
  for (let i = 0; i < 100; i++) {
    const value = gen.generate();
    expect(value).toBeGreaterThanOrEqual(1000);
    expect(value).toBeLessThanOrEqual(200000);
  }
});

test('generated integers are uniformly distributed', () => {
  const seed = Math.random() * 1000;
  const lb = 0;
  const ub = 100000;
  const gen = G.genIntWithin(lb, ub, seed);
  const range = ub - lb;
  const buckets = 100;
  const numSamples = 100000;
  // indexed by lower bound
  const counts: Record<number, number> = {};
  for (let i = 1; i <= buckets; i++) {
    counts[Math.ceil(range / buckets) * i + lb] = 0;
  }
  const values = [];
  for (let i = 0; i < numSamples; i++) {
    const value = gen.generate();
    values.push(value);
    const bucket = Object.keys(counts).find((b) => value < parseInt(b));
    if (!bucket) {
      console.log('ignoring bucket for value', value);
    } else {
      const b = parseInt(bucket);
      counts[b] = (counts[b] || 0) + 1;
    }
  }

  // compute χ² statistic
  const expectedCount = numSamples / buckets;
  const chiSquared = chiSquare(Object.values(counts), new Array(buckets).fill(expectedCount));
  // compute p-value and Cramér's V.
  const cramerv = computeCramersV(chiSquared, numSamples, buckets);
  const pValue = computeChiSquaredPValue(chiSquared, buckets);
  console.log(`Chi-squared: ${chiSquared}, p-value: ${pValue}, Cramer V: ${cramerv}`);
  expect(cramerv).toBeLessThan(0.015);
  expect(pValue).toBeGreaterThan(0.05);
});

test('can generate uniformly distributed arbitrary strings shorter than 256 characters', () => {
  const seed = Math.random() * (1 << 31);
  const gen = G.genASCIIString(seed);
  const buckets = 16;
  const numSamples = (1 << 16);
  const lengths = new Array(buckets).fill(0);
  for (let i = 0; i < numSamples; i++) {
    const value = gen.generate();
    lengths[value.length % buckets]++;
    expect(value.length).toBeLessThan(256);
  }

  console.log(lengths);
  const chiSquared = chiSquare(lengths, new Array(buckets).fill(numSamples / buckets));
  const cramerv = computeCramersV(chiSquared, numSamples, buckets);
  const pValue = computeChiSquaredPValue(chiSquared, buckets);
  console.log(`Chi-squared: ${chiSquared}, p-value: ${pValue}, Cramer V: ${cramerv}`);
  expect(cramerv).toBeLessThan(0.015);
});