import type { JumpableRandomGenerator } from '../types/JumpableRandomGenerator'; const N = 624; const M = 397; const A = 0x9908b0df; const F = 1812433253; const U = 11; const S = 7; const B = 0x9d2c5680; const T = 15; const C = 0xefc60000; const L = 18; const MASK_LOWER = 2147483647; // = 2 ** R - 1 (with R = 31) const MASK_UPPER = 2147483648; // = 2 ** R // jump() uses these coefficients to skip 2^128 values at once, creating independent streams. // Each char encodes 6 bits of the jump polynomial from numpy's mt19937-jump.h. // To regenerate from numpy's poly_coef (uint32[]) array: // > const bits = poly_coef.flatMap(v => Array.from({length: 32}, (_, i) => (v >>> i) & 1)); // > const encoded = String.fromCharCode(...Array.from({length: Math.ceil(bits.length / 6)}, (_, i) => { // > const v = bits.slice(i * 6, i * 6 + 6).reduce((a, b, j) => a | (b << j), 0); // > return v + 48; // > })); const JUMP_COEFS = 'SUSgbA\\W`E[]KN2RUSo8XVU?HKBFRl11E\\KoWOg5B]XEWG;BE;1:oVK[`B^Z9Qd23^XTnhL>]Unda4f[X;_j9H5QD=cN<5H`3bW>9bk1mjoI2fK0obmAAINOV:>Mek_V9ddHmm`co>k:KOSkSbIINb1VFf>LXgP>GUAQTD>Ci>XMGkUflLlb?_FaFUk@?5N7i70@;1o68ah@IAjUDh?M1B?^??FAXKThfR<;NHk3S9YhM7STJ6`:MIE`S@7298X8W>PNK=@;lLXlK6GM1YL\\lid:J>KYS=iJ]Y>QlF>?R5_[5QeYC=66;A32Ac>OHk_ne^0g>bK:g;KFPgbGUcPR_Z=TX3H9d03bKZ2IhEPKBo>LSGWd0iFdV8CT[O6lC\\blaZ>GoAYP4clf^j1IfnZJ]QeDe2X=h?Ye:_f_Sb9Ki5GDYBF4dWeMfdg^ccPllNWM7G1\\UMdoYeOOD5^e@foA22G?ADYo5:FVG[bWo96;>3kc_c1Ab30>30;1@4F8g2hY?DJ4[LOL;ZLLKo2]jo>[KMDUcR279N_kF=3WL@Dd620bMTdA\\U9k``ef2iD9JgJ8CZBHS>F^Uk;3_ocRA>2U:4GPQ6o>5jX2HIcV3S@On6KB<[SKB?FC_AAji9agbBFkAi\\;4I\\UJ]c36Ub@[;gQACVGYE]noQ[1Kambn??QQ]S?1i0oMGOijb\\aGY6lQ^CJ?9bFle8^h_g[RQV@PnYBRI_]`B]Bh@Uk03eXGY`I16L76H28X`R>IROMeNVUdU[:lghLhPCQZ:4a<30YBZCYnXe[?;jc8gKI2QH2MjnWBm4nGCZW`aVU2a;P:aNg:Th1Y=5o78K>LQ8hW@5S?I83Lk5Xk;j5@I3o[d:4RjE^oS30:WP9gC\\i8aSI>QRE@4lP:7lDg8g2`Ql[2I8aBU\\BQ?B4_clL9Q]S;^e1Ob5[>3JER2`c7B=o]fPOWOW:M?S_4S5d@J^`[AGA7@3]DnGSCO`\\?E8HT75^d9\\:m\\m1egIfk8cd6bD9\\eU8\\n[Pb0Cgd^S0n9kGJHb]i5XodlKHc34Fhi9K>0U5WK`>7Ff2^KL=WC6:kc?e5C^a1T1:4:^S5flXlGNIj08AfO?Dh7T7dWO>E]NI9?ob7B7P_h[4TEP[EU;GllFTnSmg9:\\[N]AYRJA1U5^Blhgno:aHVYc03c3J0Vc9FjEV^M75Zfd8kVC9>iJDk`AJ[6f7DK2D^DL\\AX:6b5h31XH;RQB\\NhEVLTdhL\\b1EB0lLh<=WaO@F<;8g?VE98[B\\Kjf?DUK00Aih:D`=fLPbO?VOTEYLj^cNj5PM>jKB5HVjJ4U7lXaTQNL9<@\\1`m\\Ug@VQHd7>jW=ca0`miF7;N0F=GjoQ`RFchKMGTmn8cF@Oh4GGCm7m2`U9j93Tb>=kSERjE_J939F01I1;`^LA=ebloE2Y2:9lkh0\\X:??`H6BXF2hG1Q[dF0Q=>W=J?7C\\k1T?<;R44oW?1hY^G8Zm]ZKnfOf0eCFYo6?=D8?<`6HU5SXh1;=:23LmV_FSi;OJfV<^?GkIDPISeHg1LaGE:V3Y3K3HoEKabY`:oU;=JW7ig?S?EYb86b7n8ce\\]IRa]koiWYOm2kMBUloD>Ze@NN^Z11TM`inXYhE_I=kA`:ZF4d\\>`L@;ZP[`ENU5cL[BV6\\Z?Di76:jg3hE6oG6jFc8kP=[GS1;WSedYQW1:U4\\OF32GgmMCT3VfcUB2m4J^CPRU;8dScI]LU]^bBYA5_3:Y0N5i^?200000'; class MersenneTwister implements JumpableRandomGenerator { declare private states: number[]; // states: between -0x80000000 and 0x7fffffff declare private index: number; constructor(states: number[], index: number) { this.states = states; this.index = index; } clone(): MersenneTwister { return new MersenneTwister(this.states.slice(), this.index); } next(): number { let y = this.states[this.index]; y ^= y >>> U; y ^= (y << S) & B; y ^= (y << T) & C; y ^= y >>> L; this.index = twistedNext(this.states, this.index); return y; } getState(): readonly number[] { return [this.index, ...this.states]; } jump(): void { // equivalent to 2^128 calls to next() // Implementation is derived from the one in numpy // https://github.com/numpy/numpy/blob/c53b2eb729bae1f248a2654dfcfa4a3dd3e2902b/numpy/random/src/mt19937/mt19937-jump.c const originalStates = this.states.slice(); const originalIndex = this.index; this.index = twistedNext(this.states, this.index); // states and index of this // https://github.com/numpy/numpy/blob/57fbc869cb10a65da0793ed3aebe00e7f595c0b1/numpy/random/src/mt19937/mt19937-jump.c#L71-L72 // We burn: 19935 and 19934 as they result into get_coef(pf, i) == 0 // So we start the loop with "19933 - 1" for (let i = 19932; i > 0; --i) { if ((JUMP_COEFS.charCodeAt((i / 6) | 0) - 48) & (1 << (i % 6))) { addState(this.states, this.index, originalStates, originalIndex); } this.index = twistedNext(this.states, this.index); // states and index of "copy" } // https://github.com/numpy/numpy/blob/57fbc869cb10a65da0793ed3aebe00e7f595c0b1/numpy/random/src/mt19937/mt19937-jump.c#L85-L88 // We fall in the case: get_coef(pf, 0) != 0 addState(this.states, this.index, originalStates, originalIndex); } } function addState(mt: number[], idx: number, originalMt: number[], originalIdx: number): void { let i = 0; if (originalIdx >= idx) { for (; i < N - originalIdx; i++) mt[i + idx] ^= originalMt[i + originalIdx]; for (; i < N - idx; i++) mt[i + idx] ^= originalMt[i + originalIdx - N]; for (; i < N; i++) mt[i + idx - N] ^= originalMt[i + originalIdx - N]; } else { for (; i < N - idx; i++) mt[i + idx] ^= originalMt[i + originalIdx]; for (; i < N - originalIdx; i++) mt[i + idx - N] ^= originalMt[i + originalIdx]; for (; i < N; i++) mt[i + idx - N] ^= originalMt[i + originalIdx - N]; } } function twistedNext(mt: number[], idx: number) { if (idx < N - M) { const y = (mt[idx] & MASK_UPPER) | (mt[idx + 1] & MASK_LOWER); mt[idx] = mt[idx + M] ^ (y >>> 1) ^ (-(y & 1) & A); return idx + 1; } else if (idx < N - 1) { const y = (mt[idx] & MASK_UPPER) | (mt[idx + 1] & MASK_LOWER); mt[idx] = mt[idx + M - N] ^ (y >>> 1) ^ (-(y & 1) & A); return idx + 1; } else { const y = (mt[idx] & MASK_UPPER) | (mt[0] & MASK_LOWER); mt[idx] = mt[M - 1] ^ (y >>> 1) ^ (-(y & 1) & A); return 0; } } function twist(mt: number[]) { for (let idx = 0; idx !== N; ++idx) { twistedNext(mt, idx); } } export function mersenneFromState(state: readonly number[]): JumpableRandomGenerator { const valid = state.length === N + 1 && state[0] >= 0 && state[0] < N; if (!valid) { throw new Error('The state must have been produced by a mersenne RandomGenerator'); } return new MersenneTwister(state.slice(1), state[0]); } export function mersenne(seed: number): JumpableRandomGenerator { const out: number[] = [seed | 0]; for (let idx = 1; idx !== N; ++idx) { const xored = out[idx - 1] ^ (out[idx - 1] >>> 30); out.push((Math.imul(F, xored) + idx) | 0); } twist(out); return new MersenneTwister(out, 0); }