UNPKG

@mathigon/fermat

Version:

Powerful mathematics and statistics library for JavaScript.

172 lines (136 loc) 4.74 kB
// ============================================================================ // Fermat.js | Random Numbers // (c) Mathigon // ============================================================================ import {repeat, total, uid} from '@mathigon/core'; /** Randomly shuffles the elements in an array a. */ export function shuffle<T>(a: T[]): T[] { a = a.slice(0); // create copy for (let i = a.length - 1; i > 0; --i) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } /** Generates a random integer between 0 and a, or between a and b. */ export function integer(a: number, b?: number) { const start = (b === undefined ? 0 : a); const length = (b === undefined ? a : b - a + 1); return start + Math.floor(length * Math.random()); } /** Chooses a random index value from weights [2, 5, 3] */ export function weighted(weights: number[]) { const x = Math.random() * total(weights); let cum = 0; return weights.findIndex((w) => (cum += w) >= x); } /** Randomly selects an element from an array. */ export function find<T>(items: T[]): T { return items[Math.floor(items.length * Math.random())]; } // --------------------------------------------------------------------------- // Smart Random Number Generators const SMART_RANDOM_CACHE = new Map<string, number[]>(); /** * Returns a random number between 0 and n, but avoids returning the same * number multiple times in a row. */ export function smart(n: number, id: string) { if (!id) id = uid(); if (!SMART_RANDOM_CACHE.has(id)) SMART_RANDOM_CACHE.set(id, repeat(1, n)); const cache = SMART_RANDOM_CACHE.get(id)!; const x = weighted(cache.map(x => x * x)); cache[x] -= 1; if (cache[x] <= 0) SMART_RANDOM_CACHE.set(id, cache.map(x => x + 1)); return x; } // --------------------------------------------------------------------------- // Probability Distribution /** Generates a Bernoulli random variable. */ export function bernoulli(p = 0.5) { return (Math.random() < p ? 1 : 0); } /** Generates a Binomial random variable. */ export function binomial(n = 1, p = 0.5) { let t = 0; for (let i = 0; i < n; ++i) t += bernoulli(p); return t; } /** Generates a Poisson random variable. */ export function poisson(l = 1) { if (l <= 0) return 0; const L = Math.exp(-l); let p = 1; let k = 0; for (; p > L; ++k) p *= Math.random(); return k - 1; } /** Generates a uniform random variable. */ export function uniform(a = 0, b = 1) { return a + (b - a) * Math.random(); } /** Generates a normal random variable with mean m and variance v. */ export function normal(m = 0, v = 1) { const u1 = Math.random(); const u2 = Math.random(); const rand = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2); return rand * Math.sqrt(v) + m; } /** Generates an exponential random variable. */ export function exponential(l = 1) { return l <= 0 ? 0 : -Math.log(Math.random()) / l; } /** Generates a geometric random variable. */ export function geometric(p = 0.5) { if (p <= 0 || p > 1) return undefined; return Math.floor(Math.log(Math.random()) / Math.log(1 - p)); } /** Generates an Cauchy random variable. */ export function cauchy() { let rr; let v1; let v2; do { v1 = 2 * Math.random() - 1; v2 = 2 * Math.random() - 1; rr = v1 * v1 + v2 * v2; } while (rr >= 1); return v1 / v2; } // --------------------------------------------------------------------------- // PDFs and CDFs /** Generates pdf(x) for the normal distribution with mean m and variance v. */ export function normalPDF(x: number, m = 1, v = 0) { return Math.exp(-((x - m) ** 2) / (2 * v)) / Math.sqrt(2 * Math.PI * v); } const G = 7; const P = [ 0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7 ]; function gamma(z: number): number { if (z < 0.5) return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z)); z -= 1; let x = P[0]; for (let i = 1; i < G + 2; i++) x += P[i] / (z + i); const t = z + G + 0.5; return Math.sqrt(2 * Math.PI) * Math.pow(t, z + 0.5) * Math.exp(-t) * x; } /** Riemann-integrates fn(x) from xMin to xMax with an interval size dx. */ export function integrate(fn: (x: number) => number, xMin: number, xMax: number, dx = 1) { let result = 0; for (let x = xMin; x < xMax; x += dx) { result += (fn(x) * dx || 0); } return result; } /** The chi CDF function. */ export function chiCDF(chi: number, deg: number) { const int = integrate(t => Math.pow(t, (deg - 2) / 2) * Math.exp(-t / 2), 0, chi); return 1 - int / Math.pow(2, deg / 2) / gamma(deg / 2); }