peeksafe 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +146 -0
- package/LICENSE +190 -0
- package/NOTICE +78 -0
- package/README.md +492 -2
- package/RESEARCH-NOTES.md +274 -0
- package/dist/baseline.d.ts +45 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +49 -0
- package/dist/baseline.js.map +1 -0
- package/dist/check.d.ts +64 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +172 -0
- package/dist/check.js.map +1 -0
- package/dist/cluster.d.ts +328 -0
- package/dist/cluster.d.ts.map +1 -0
- package/dist/cluster.js +598 -0
- package/dist/cluster.js.map +1 -0
- package/dist/errors.d.ts +87 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +143 -0
- package/dist/errors.js.map +1 -0
- package/dist/evidence.d.ts +15 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +30 -0
- package/dist/evidence.js.map +1 -0
- package/dist/frontier.d.ts +269 -0
- package/dist/frontier.d.ts.map +1 -0
- package/dist/frontier.js +460 -0
- package/dist/frontier.js.map +1 -0
- package/dist/gate.d.ts +173 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +196 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/plan.d.ts +248 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +482 -0
- package/dist/plan.js.map +1 -0
- package/dist/rand.d.ts +17 -0
- package/dist/rand.d.ts.map +1 -0
- package/dist/rand.js +92 -0
- package/dist/rand.js.map +1 -0
- package/dist/stats.d.ts +397 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +1245 -0
- package/dist/stats.js.map +1 -0
- package/dist/stop.d.ts +163 -0
- package/dist/stop.d.ts.map +1 -0
- package/dist/stop.js +262 -0
- package/dist/stop.js.map +1 -0
- package/package.json +64 -4
- package/src/baseline.ts +72 -0
- package/src/check.ts +238 -0
- package/src/cluster.ts +830 -0
- package/src/errors.ts +208 -0
- package/src/evidence.ts +62 -0
- package/src/frontier.ts +782 -0
- package/src/gate.ts +358 -0
- package/src/index.ts +206 -0
- package/src/plan.ts +778 -0
- package/src/rand.ts +121 -0
- package/src/stats.ts +1579 -0
- package/src/stop.ts +452 -0
package/src/rand.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rand.ts: deterministic randomness.
|
|
3
|
+
*
|
|
4
|
+
* Everything stochastic in peeksafe (the Monte Carlo checks in `test/`, and any
|
|
5
|
+
* simulation a caller wants to run) draws from here.
|
|
6
|
+
*
|
|
7
|
+
* The generator is a linear congruential generator feeding an integer avalanche.
|
|
8
|
+
* The LCG alone is not good enough: consecutive outputs of an LCG sit on a
|
|
9
|
+
* lattice, which visibly biases Bernoulli streams, and a Bernoulli stream is the
|
|
10
|
+
* only thing this library ever simulates. The mixer decorrelates without adding
|
|
11
|
+
* a dependency and without losing determinism.
|
|
12
|
+
*
|
|
13
|
+
* Both sets of constants are published parameters rather than anybody's source
|
|
14
|
+
* code, and both are named in NOTICE.
|
|
15
|
+
*
|
|
16
|
+
* The exact stream matters. The measurements in the README and in
|
|
17
|
+
* `test/paper.test.ts` were produced by this generator, so replacing it with a
|
|
18
|
+
* better one (sfc32, pcg) would change every reported figure. That is a fine
|
|
19
|
+
* thing to do deliberately, with the numbers re-derived and the README updated.
|
|
20
|
+
* It is not a fine thing to do by accident, which is why the constants are
|
|
21
|
+
* spelled out here rather than imported.
|
|
22
|
+
*/
|
|
23
|
+
import { PeeksafeError } from "./errors.js";
|
|
24
|
+
|
|
25
|
+
export interface Rand {
|
|
26
|
+
(): number;
|
|
27
|
+
/** A single Bernoulli draw at rate `p`. */
|
|
28
|
+
bernoulli(p: number): boolean;
|
|
29
|
+
/** A uniform integer in `[0, nExclusive)`. */
|
|
30
|
+
int(nExclusive: number): number;
|
|
31
|
+
/** A uniform choice. Throws on an empty array rather than returning undefined. */
|
|
32
|
+
pick<T>(xs: readonly T[]): T;
|
|
33
|
+
/** A standard normal draw, via Box-Muller. */
|
|
34
|
+
normal(): number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* LCG with Knuth's multiplier 1664525 and increment 1013904223, modulo 2^32.
|
|
39
|
+
*
|
|
40
|
+
* These are tabulated constants (Knuth, TAOCP vol. 2), reprinted in most
|
|
41
|
+
* numerical texts. Exact in float64: 2^32 * 1664525 sits under 2^53, so the
|
|
42
|
+
* product never loses a bit before the mask.
|
|
43
|
+
*/
|
|
44
|
+
function lcg(seed: number): () => number {
|
|
45
|
+
let s = seed >>> 0;
|
|
46
|
+
return () => {
|
|
47
|
+
s = (s * 1664525 + 1013904223) >>> 0;
|
|
48
|
+
return s / 0x100000000;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 32-bit avalanche: the `lowbias32` multipliers from Chris Wellons' hash
|
|
54
|
+
* function prospector, which he placed in the public domain. Two rounds of
|
|
55
|
+
* xor-shift-multiply, chosen by search for low bias rather than derived.
|
|
56
|
+
*/
|
|
57
|
+
function mix32(x: number): number {
|
|
58
|
+
let h = x >>> 0;
|
|
59
|
+
h ^= h >>> 16;
|
|
60
|
+
h = Math.imul(h, 0x7feb352d) >>> 0;
|
|
61
|
+
h ^= h >>> 15;
|
|
62
|
+
h = Math.imul(h, 0x846ca68b) >>> 0;
|
|
63
|
+
h ^= h >>> 16;
|
|
64
|
+
return h >>> 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** FNV-1a. Used to derive an independent seed per named stream. */
|
|
68
|
+
export function hash32(s: string): number {
|
|
69
|
+
let h = 0x811c9dc5;
|
|
70
|
+
for (let i = 0; i < s.length; i++) {
|
|
71
|
+
h ^= s.charCodeAt(i);
|
|
72
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
73
|
+
}
|
|
74
|
+
return h >>> 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function makeRand(seed: number | string): Rand {
|
|
78
|
+
const s = typeof seed === "string" ? hash32(seed) : seed >>> 0;
|
|
79
|
+
const base = lcg(s === 0 ? 1 : s);
|
|
80
|
+
const next = (() => mix32(Math.floor(base() * 0x100000000)) / 0x100000000) as Rand;
|
|
81
|
+
|
|
82
|
+
next.bernoulli = (p: number) => next() < p;
|
|
83
|
+
|
|
84
|
+
next.int = (n: number) => {
|
|
85
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
86
|
+
throw new PeeksafeError(
|
|
87
|
+
"PEEKSAFE_E_STAT_DOMAIN",
|
|
88
|
+
`rand.int: n must be a positive integer, got ${n}`,
|
|
89
|
+
{
|
|
90
|
+
detail: { n },
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return Math.floor(next() * n) % n;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Typed `T`, so it must never hand back `undefined`. Returning `xs[0]` for an
|
|
98
|
+
// empty array is `undefined` wearing a `T`, which is how a fail-open bug gets
|
|
99
|
+
// past the type checker.
|
|
100
|
+
next.pick = <T>(xs: readonly T[]): T => {
|
|
101
|
+
if (xs.length === 0) {
|
|
102
|
+
throw new PeeksafeError(
|
|
103
|
+
"PEEKSAFE_E_STAT_DOMAIN",
|
|
104
|
+
"rand.pick: cannot pick from an empty array",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return xs[next.int(xs.length)]!;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
next.normal = () => {
|
|
111
|
+
const u = Math.max(1e-12, next());
|
|
112
|
+
const v = next();
|
|
113
|
+
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return next;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A stream keyed by name, reproducible regardless of call order elsewhere. */
|
|
120
|
+
export const streamFor = (...parts: Array<string | number>): Rand =>
|
|
121
|
+
makeRand(hash32(parts.join("|")));
|