clustering-tfjs
Version:
High-performance TypeScript clustering algorithms (K-Means, Spectral, Agglomerative) with TensorFlow.js acceleration and scikit-learn compatibility
97 lines (96 loc) • 3.7 kB
JavaScript
"use strict";
/**
* Minimal TypeScript implementation of the original MT19937 32-bit variant
* used by NumPy's legacy `RandomState` (and therefore by scikit-learn).
*
* This port only exposes the functionality required by the k-means++ seeding
* routine:
* • Generation of 32-bit unsigned integers (\[0, 2**32))
* • High-precision uniform floats in the half-open interval \[0, 1)
*
* The algorithm closely follows the reference implementation described in
* Matsumoto & Nishimura (1998) and the public domain C code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MT19937 = void 0;
class MT19937 {
constructor(seed) {
/** State vector – 624 32-bit unsigned ints. */
this.mt = new Uint32Array(MT19937.N);
/** Current index within the state vector. */
this.index = MT19937.N;
this.init(seed >>> 0); // ensure unsigned 32-bit
}
/* --------------------------------------------------------------------- */
/* Public helpers */
/* --------------------------------------------------------------------- */
/** Returns next 32-bit unsigned int in \[0, 2**32). */
nextUint32() {
if (this.index >= MT19937.N) {
this.twist();
}
let y = this.mt[this.index++];
// Tempering (same bit-shifts as NumPy's implementation)
y ^= y >>> 11;
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= y >>> 18;
return y >>> 0; // ensure unsigned
}
/**
* Returns a 53-bit precision float in the interval \[0, 1) identical to
* NumPy's `random_sample` implementation.
*/
nextFloat() {
const a = this.nextUint32() >>> 5; // Upper 27 bits
const b = this.nextUint32() >>> 6; // Upper 26 bits
return (a * 67108864 + b) * 1.1102230246251565e-16; // 1 / 2**53
}
/** Uniform integer in \[0, max). Mirrors NumPy's rejection sampling to
* eliminate modulo bias so that sequences match exactly.
*/
nextInt(max) {
if (!Number.isInteger(max) || max <= 0 || max > 0xffffffff) {
throw new Error('max must be a 32-bit positive integer');
}
const bound = max >>> 0;
const threshold = (0x100000000 - bound) % bound; // 2**32 == 0x100000000
// eslint-disable-next-line no-constant-condition
while (true) {
const r = this.nextUint32();
if (r >= threshold) {
return r % bound;
}
}
}
/* --------------------------------------------------------------------- */
/* Internals */
/* --------------------------------------------------------------------- */
init(seed) {
this.mt[0] = seed >>> 0;
for (let i = 1; i < MT19937.N; i++) {
const prev = this.mt[i - 1] >>> 0;
this.mt[i] =
((1812433253 * (prev ^ (prev >>> 30)) + i) & 0xffffffff) >>> 0;
}
this.index = MT19937.N;
}
twist() {
const { N, M, MATRIX_A, UPPER_MASK, LOWER_MASK } = MT19937;
for (let i = 0; i < N; i++) {
const x = (this.mt[i] & UPPER_MASK) | (this.mt[(i + 1) % N] & LOWER_MASK);
let xa = x >>> 1;
if (x % 2 !== 0) {
xa ^= MATRIX_A;
}
this.mt[i] = this.mt[(i + M) % N] ^ xa;
}
this.index = 0;
}
}
exports.MT19937 = MT19937;
MT19937.N = 624;
MT19937.M = 397;
MT19937.MATRIX_A = 0x9908b0df;
MT19937.UPPER_MASK = 0x80000000;
MT19937.LOWER_MASK = 0x7fffffff;