Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/feedthejoe / src/core/rank/random.js |
| Date | Not specified in source |
| Repository | Janady13/feedthejoe |
| Revision | 0e608a2b515a959bf35d2783428766624400ca3c |
| Branch | main |
| Path or archive member | src/core/rank/random.js |
| Original SHA-256 | 868e00489bbe252597640b28e472c4d23b31c17d888f53d09b2528018059de4f |
| Git blob SHA | d5d3c444b280d4a3ec20c4079a70698ada5a3220 |
| Reading-copy SHA-256 | 868e00489bbe252597640b28e472c4d23b31c17d888f53d09b2528018059de4f |
Recovered source transcript
Download complete source text ↓126 lines · 3.8 KB
/**
* Seeded pseudo-randomness.
*
* The recommender is stochastic by design — Thompson sampling needs draws from
* a Beta posterior. Stochastic is not the same as untestable: every draw in
* this system comes from an explicit, injected generator, so a test can pin the
* seed and assert an exact ordering.
*
* Nothing here touches Math.random(). That is deliberate and load-bearing.
*/
/**
* mulberry32 — a 32-bit PRNG with a full 2^32 period and good statistical
* properties for our purposes (we need plausible jitter, not cryptography).
*
* @param {number} seed Any integer.
* @returns {() => number} Generator yielding floats in [0, 1).
*/
export function mulberry32(seed) {
let a = seed >>> 0;
return function next() {
a = (a + 0x6d2b79f5) >>> 0;
let t = a;
t = Math.imul(t ^ (t >>> 15), 1 | t);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
/**
* Derive a stable 32-bit seed from a string, so an item id maps to the same
* jitter on every render. FNV-1a.
*
* @param {string} str
* @returns {number}
*/
export function seedFrom(str) {
let h = 2166136261 >>> 0;
for (let i = 0; i < str.length; i += 1) {
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619) >>> 0;
}
return h >>> 0;
}
/**
* Standard normal deviate via the Box–Muller transform.
*
* @param {() => number} rng
* @returns {number}
*/
export function gaussian(rng) {
// u must be non-zero for log(); the half-open range of rng() makes 0 possible.
let u = 0;
while (u === 0) u = rng();
const v = rng();
return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
}
/**
* Gamma(shape, 1) draw via Marsaglia & Tsang (2000), "A Simple Method for
* Generating Gamma Variables", ACM TOMS 26(3).
*
* The method is defined for shape >= 1; for shape < 1 we use the standard
* boost identity G(a) = G(a+1) * U^(1/a).
*
* @param {() => number} rng
* @param {number} shape Must be > 0.
* @returns {number}
*/
export function gammaSample(rng, shape) {
if (!(shape > 0)) throw new RangeError(`gammaSample: shape must be > 0, got ${shape}`);
if (shape < 1) {
const u = Math.max(rng(), Number.MIN_VALUE);
return gammaSample(rng, shape + 1) * Math.pow(u, 1 / shape);
}
const d = shape - 1 / 3;
const c = 1 / Math.sqrt(9 * d);
// Rejection loop. Expected iterations is < 1.01 for all shape >= 1, so this
// terminates promptly; there is no unbounded-loop risk in practice.
for (;;) {
let x;
let v;
do {
x = gaussian(rng);
v = 1 + c * x;
} while (v <= 0);
v = v * v * v;
const u = rng();
const x2 = x * x;
// Squeeze: cheap acceptance test that avoids the logarithms most of the time.
if (u < 1 - 0.0331 * x2 * x2) return d * v;
if (Math.log(u) < 0.5 * x2 + d * (1 - v + Math.log(v))) return d * v;
}
}
/**
* Beta(alpha, beta) draw, built from two Gamma draws:
* X ~ G(a,1), Y ~ G(b,1) => X / (X + Y) ~ Beta(a, b)
*
* This is the sampling primitive behind Thompson sampling: each topic carries a
* Beta posterior over "will this visitor engage", and we rank by a draw from it
* rather than by its mean. Sampling is what produces exploration — a topic with
* little evidence has a wide posterior and will sometimes draw high, which is
* exactly the cold-start promotion behaviour we want.
*
* @param {() => number} rng
* @param {number} alpha Successes + prior. Must be > 0.
* @param {number} beta Failures + prior. Must be > 0.
* @returns {number} A value in [0, 1].
*/
export function betaSample(rng, alpha, beta) {
const x = gammaSample(rng, alpha);
const y = gammaSample(rng, beta);
const sum = x + y;
// Both gammas underflowing to 0 is astronomically unlikely but not impossible;
// fall back to the posterior mean rather than emitting NaN.
if (sum === 0) return alpha / (alpha + beta);
return x / sum;
}
Mathematical records from this source
0102
random · boost identity G(a)
This relation defines boost identity G(a) in the source section “random · boost identity G(a)”. It specifies the displayed relationship between the named quantities or…
random
This relation defines X ~ G(a,1), Y ~ G(b,1) in the source section “random”. It specifies the displayed relationship between the named quantities or parameters. Its interpretation…