@astermind/astermind-elm
Version:
JavaScript Extreme Learning Machine (ELM) library for browser and Node.js.
1,192 lines (1,184 loc) • 248 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.astermind = {}));
})(this, (function (exports) { 'use strict';
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// Matrix.ts — tolerant, safe helpers with dimension checks and stable ops
class DimError extends Error {
constructor(msg) {
super(msg);
this.name = 'DimError';
}
}
const EPS$4 = 1e-12;
/* ===================== Array-like coercion helpers ===================== */
// ✅ Narrow to ArrayLike<number> so numeric indexing is allowed
function isArrayLikeRow(row) {
return row != null && typeof row.length === 'number';
}
/**
* Coerce any 2D array-like into a strict rectangular number[][]
* - If width is not provided, infer from the first row's length
* - Pads/truncates to width
* - Non-finite values become 0
*/
function ensureRectNumber2D(M, width, name = 'matrix') {
if (!M || typeof M.length !== 'number') {
throw new DimError(`${name} must be a non-empty 2D array`);
}
const rows = Array.from(M);
if (rows.length === 0)
throw new DimError(`${name} is empty`);
const first = rows[0];
if (!isArrayLikeRow(first))
throw new DimError(`${name} row 0 missing/invalid`);
const C = ((width !== null && width !== void 0 ? width : first.length) | 0);
if (C <= 0)
throw new DimError(`${name} has zero width`);
const out = new Array(rows.length);
for (let r = 0; r < rows.length; r++) {
const src = rows[r];
const rr = new Array(C);
if (isArrayLikeRow(src)) {
const sr = src; // ✅ typed
for (let c = 0; c < C; c++) {
const v = sr[c];
rr[c] = Number.isFinite(v) ? Number(v) : 0;
}
}
else {
for (let c = 0; c < C; c++)
rr[c] = 0;
}
out[r] = rr;
}
return out;
}
/**
* Relaxed rectangularity check:
* - Accepts any array-like rows (typed arrays included)
* - Verifies consistent width and finite numbers
*/
function assertRect(A, name = 'matrix') {
if (!A || typeof A.length !== 'number') {
throw new DimError(`${name} must be a non-empty 2D array`);
}
const rows = A.length | 0;
if (rows <= 0)
throw new DimError(`${name} must be a non-empty 2D array`);
const first = A[0];
if (!isArrayLikeRow(first))
throw new DimError(`${name} row 0 missing/invalid`);
const C = first.length | 0;
if (C <= 0)
throw new DimError(`${name} must have positive column count`);
for (let r = 0; r < rows; r++) {
const rowAny = A[r];
if (!isArrayLikeRow(rowAny)) {
throw new DimError(`${name} row ${r} invalid`);
}
const row = rowAny; // ✅ typed
if ((row.length | 0) !== C) {
throw new DimError(`${name} has ragged rows: row 0 = ${C} cols, row ${r} = ${row.length} cols`);
}
for (let c = 0; c < C; c++) {
const v = row[c];
if (!Number.isFinite(v)) {
throw new DimError(`${name} row ${r}, col ${c} is not finite: ${v}`);
}
}
}
}
function assertMulDims(A, B) {
assertRect(A, 'A');
assertRect(B, 'B');
const nA = A[0].length;
const mB = B.length;
if (nA !== mB) {
throw new DimError(`matmul dims mismatch: A(${A.length}x${nA}) * B(${mB}x${B[0].length})`);
}
}
function isSquare(A) {
return isArrayLikeRow(A === null || A === void 0 ? void 0 : A[0]) && (A.length === (A[0].length | 0));
}
function isSymmetric(A, tol = 1e-10) {
if (!isSquare(A))
return false;
const n = A.length;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
if (Math.abs(A[i][j] - A[j][i]) > tol)
return false;
}
}
return true;
}
/* ============================== Matrix ============================== */
class Matrix {
/* ========= constructors / basics ========= */
static shape(A) {
assertRect(A, 'A');
return [A.length, A[0].length];
}
static clone(A) {
assertRect(A, 'A');
return ensureRectNumber2D(A, A[0].length, 'A(clone)');
}
static zeros(rows, cols) {
const out = new Array(rows);
for (let i = 0; i < rows; i++)
out[i] = new Array(cols).fill(0);
return out;
}
static identity(n) {
const I = Matrix.zeros(n, n);
for (let i = 0; i < n; i++)
I[i][i] = 1;
return I;
}
static transpose(A) {
assertRect(A, 'A');
const m = A.length, n = A[0].length;
const T = Matrix.zeros(n, m);
for (let i = 0; i < m; i++) {
const Ai = A[i];
for (let j = 0; j < n; j++)
T[j][i] = Number(Ai[j]);
}
return T;
}
/* ========= algebra ========= */
static add(A, B) {
A = ensureRectNumber2D(A, undefined, 'A');
B = ensureRectNumber2D(B, undefined, 'B');
assertRect(A, 'A');
assertRect(B, 'B');
if (A.length !== B.length || A[0].length !== B[0].length) {
throw new DimError(`add dims mismatch: A(${A.length}x${A[0].length}) vs B(${B.length}x${B[0].length})`);
}
const m = A.length, n = A[0].length;
const C = Matrix.zeros(m, n);
for (let i = 0; i < m; i++) {
const Ai = A[i], Bi = B[i], Ci = C[i];
for (let j = 0; j < n; j++)
Ci[j] = Ai[j] + Bi[j];
}
return C;
}
/** Adds lambda to the diagonal (ridge regularization) */
static addRegularization(A, lambda = 1e-6) {
A = ensureRectNumber2D(A, undefined, 'A');
assertRect(A, 'A');
if (!isSquare(A)) {
throw new DimError(`addRegularization expects square matrix, got ${A.length}x${A[0].length}`);
}
const C = Matrix.clone(A);
for (let i = 0; i < C.length; i++)
C[i][i] += lambda;
return C;
}
static multiply(A, B) {
A = ensureRectNumber2D(A, undefined, 'A');
B = ensureRectNumber2D(B, undefined, 'B');
assertMulDims(A, B);
const m = A.length, n = B.length, p = B[0].length;
const C = Matrix.zeros(m, p);
for (let i = 0; i < m; i++) {
const Ai = A[i];
for (let k = 0; k < n; k++) {
const aik = Number(Ai[k]);
const Bk = B[k];
for (let j = 0; j < p; j++)
C[i][j] += aik * Number(Bk[j]);
}
}
return C;
}
static multiplyVec(A, v) {
A = ensureRectNumber2D(A, undefined, 'A');
assertRect(A, 'A');
if (!v || typeof v.length !== 'number') {
throw new DimError(`matvec expects vector 'v' with length ${A[0].length}`);
}
if (A[0].length !== v.length) {
throw new DimError(`matvec dims mismatch: A cols ${A[0].length} vs v len ${v.length}`);
}
const m = A.length, n = v.length;
const out = new Array(m).fill(0);
for (let i = 0; i < m; i++) {
const Ai = A[i];
let s = 0;
for (let j = 0; j < n; j++)
s += Number(Ai[j]) * Number(v[j]);
out[i] = s;
}
return out;
}
/* ========= decompositions / solve ========= */
static cholesky(A, jitter = 0) {
A = ensureRectNumber2D(A, undefined, 'A');
assertRect(A, 'A');
if (!isSquare(A))
throw new DimError(`cholesky expects square matrix, got ${A.length}x${A[0].length}`);
const n = A.length;
const L = Matrix.zeros(n, n);
for (let i = 0; i < n; i++) {
for (let j = 0; j <= i; j++) {
let sum = A[i][j];
for (let k = 0; k < j; k++)
sum -= L[i][k] * L[j][k];
if (i === j) {
const v = sum + jitter;
L[i][j] = Math.sqrt(Math.max(v, EPS$4));
}
else {
L[i][j] = sum / L[j][j];
}
}
}
return L;
}
static solveCholesky(A, B, jitter = 1e-10) {
A = ensureRectNumber2D(A, undefined, 'A');
B = ensureRectNumber2D(B, undefined, 'B');
assertRect(A, 'A');
assertRect(B, 'B');
if (!isSquare(A) || A.length !== B.length) {
throw new DimError(`solveCholesky dims: A(${A.length}x${A[0].length}) vs B(${B.length}x${B[0].length})`);
}
const n = A.length, k = B[0].length;
const L = Matrix.cholesky(A, jitter);
// Solve L Z = B (forward)
const Z = Matrix.zeros(n, k);
for (let i = 0; i < n; i++) {
for (let c = 0; c < k; c++) {
let s = B[i][c];
for (let p = 0; p < i; p++)
s -= L[i][p] * Z[p][c];
Z[i][c] = s / L[i][i];
}
}
// Solve L^T X = Z (backward)
const X = Matrix.zeros(n, k);
for (let i = n - 1; i >= 0; i--) {
for (let c = 0; c < k; c++) {
let s = Z[i][c];
for (let p = i + 1; p < n; p++)
s -= L[p][i] * X[p][c];
X[i][c] = s / L[i][i];
}
}
return X;
}
static inverse(A) {
A = ensureRectNumber2D(A, undefined, 'A');
assertRect(A, 'A');
if (!isSquare(A))
throw new DimError(`inverse expects square matrix, got ${A.length}x${A[0].length}`);
const n = A.length;
const M = Matrix.clone(A);
const I = Matrix.identity(n);
// Augment [M | I]
const aug = new Array(n);
for (let i = 0; i < n; i++)
aug[i] = M[i].concat(I[i]);
const cols = 2 * n;
for (let p = 0; p < n; p++) {
// Pivot
let maxRow = p, maxVal = Math.abs(aug[p][p]);
for (let r = p + 1; r < n; r++) {
const v = Math.abs(aug[r][p]);
if (v > maxVal) {
maxVal = v;
maxRow = r;
}
}
if (maxVal < EPS$4)
throw new Error('Matrix is singular or ill-conditioned');
if (maxRow !== p) {
const tmp = aug[p];
aug[p] = aug[maxRow];
aug[maxRow] = tmp;
}
// Normalize pivot row
const piv = aug[p][p];
const invPiv = 1 / piv;
for (let c = 0; c < cols; c++)
aug[p][c] *= invPiv;
// Eliminate other rows
for (let r = 0; r < n; r++) {
if (r === p)
continue;
const f = aug[r][p];
if (Math.abs(f) < EPS$4)
continue;
for (let c = 0; c < cols; c++)
aug[r][c] -= f * aug[p][c];
}
}
// Extract right half as inverse
const inv = Matrix.zeros(n, n);
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++)
inv[i][j] = aug[i][n + j];
}
return inv;
}
/* ========= helpers ========= */
static inverseSPDOrFallback(A) {
if (isSymmetric(A)) {
try {
return Matrix.solveCholesky(A, Matrix.identity(A.length), 1e-10);
}
catch (_a) {
// fall through
}
}
return Matrix.inverse(A);
}
/* ========= Symmetric Eigen (Jacobi) & Inverse Square Root ========= */
static assertSquare(A, ctx = 'Matrix') {
assertRect(A, ctx);
if (!isSquare(A)) {
throw new DimError(`${ctx}: expected square matrix, got ${A.length}x${A[0].length}`);
}
}
static eigSym(A, maxIter = 64, tol = 1e-12) {
A = ensureRectNumber2D(A, undefined, 'eigSym/A');
Matrix.assertSquare(A, 'eigSym');
const n = A.length;
const B = Matrix.clone(A);
let V = Matrix.identity(n);
const abs = Math.abs;
const offdiagNorm = () => {
let s = 0;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
const v = B[i][j];
s += v * v;
}
}
return Math.sqrt(s);
};
for (let it = 0; it < maxIter; it++) {
if (offdiagNorm() <= tol)
break;
let p = 0, q = 1, max = 0;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
const v = abs(B[i][j]);
if (v > max) {
max = v;
p = i;
q = j;
}
}
}
if (max <= tol)
break;
const app = B[p][p], aqq = B[q][q], apq = B[p][q];
const tau = (aqq - app) / (2 * apq);
const t = Math.sign(tau) / (abs(tau) + Math.sqrt(1 + tau * tau));
const c = 1 / Math.sqrt(1 + t * t);
const s = t * c;
const Bpp = c * c * app - 2 * s * c * apq + s * s * aqq;
const Bqq = s * s * app + 2 * s * c * apq + c * c * aqq;
B[p][p] = Bpp;
B[q][q] = Bqq;
B[p][q] = B[q][p] = 0;
for (let k = 0; k < n; k++) {
if (k === p || k === q)
continue;
const aip = B[k][p], aiq = B[k][q];
const new_kp = c * aip - s * aiq;
const new_kq = s * aip + c * aiq;
B[k][p] = B[p][k] = new_kp;
B[k][q] = B[q][k] = new_kq;
}
for (let k = 0; k < n; k++) {
const vip = V[k][p], viq = V[k][q];
V[k][p] = c * vip - s * viq;
V[k][q] = s * vip + c * viq;
}
}
const vals = new Array(n);
for (let i = 0; i < n; i++)
vals[i] = B[i][i];
const order = vals.map((v, i) => [v, i]).sort((a, b) => a[0] - b[0]).map(([, i]) => i);
const values = order.map(i => vals[i]);
const vectors = Matrix.zeros(n, n);
for (let r = 0; r < n; r++) {
for (let c = 0; c < n; c++)
vectors[r][c] = V[r][order[c]];
}
return { values, vectors };
}
static invSqrtSym(A, eps = 1e-10) {
A = ensureRectNumber2D(A, undefined, 'invSqrtSym/A');
Matrix.assertSquare(A, 'invSqrtSym');
const { values, vectors: U } = Matrix.eigSym(A);
const n = values.length;
const Dm12 = Matrix.zeros(n, n);
for (let i = 0; i < n; i++) {
const lam = Math.max(values[i], eps);
Dm12[i][i] = 1 / Math.sqrt(lam);
}
const UD = Matrix.multiply(U, Dm12);
return Matrix.multiply(UD, Matrix.transpose(U));
}
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// Activations.ts - Common activation functions (with derivatives)
class Activations {
/* ========= Forward ========= */
/** Rectified Linear Unit */
static relu(x) {
return x > 0 ? x : 0;
}
/** Leaky ReLU with configurable slope for x<0 (default 0.01) */
static leakyRelu(x, alpha = 0.01) {
return x >= 0 ? x : alpha * x;
}
/** Logistic sigmoid */
static sigmoid(x) {
return 1 / (1 + Math.exp(-x));
}
/** Hyperbolic tangent */
static tanh(x) {
return Math.tanh(x);
}
/** Linear / identity activation */
static linear(x) {
return x;
}
/**
* GELU (Gaussian Error Linear Unit), tanh approximation.
* 0.5 * x * (1 + tanh(√(2/π) * (x + 0.044715 x^3)))
*/
static gelu(x) {
const k = Math.sqrt(2 / Math.PI);
const u = k * (x + 0.044715 * x * x * x);
return 0.5 * x * (1 + Math.tanh(u));
}
/**
* Softmax with numerical stability and optional temperature.
* @param arr logits
* @param temperature >0; higher = flatter distribution
*/
static softmax(arr, temperature = 1) {
const t = Math.max(temperature, 1e-12);
let max = -Infinity;
for (let i = 0; i < arr.length; i++) {
const v = arr[i] / t;
if (v > max)
max = v;
}
const exps = new Array(arr.length);
let sum = 0;
for (let i = 0; i < arr.length; i++) {
const e = Math.exp(arr[i] / t - max);
exps[i] = e;
sum += e;
}
const denom = sum || 1e-12;
for (let i = 0; i < exps.length; i++)
exps[i] = exps[i] / denom;
return exps;
}
/* ========= Derivatives (elementwise) ========= */
/** d/dx ReLU */
static dRelu(x) {
// subgradient at 0 -> 0
return x > 0 ? 1 : 0;
}
/** d/dx LeakyReLU */
static dLeakyRelu(x, alpha = 0.01) {
return x >= 0 ? 1 : alpha;
}
/** d/dx Sigmoid = s(x)*(1-s(x)) */
static dSigmoid(x) {
const s = Activations.sigmoid(x);
return s * (1 - s);
}
/** d/dx tanh = 1 - tanh(x)^2 */
static dTanh(x) {
const t = Math.tanh(x);
return 1 - t * t;
}
/** d/dx Linear = 1 */
static dLinear(_) {
return 1;
}
/**
* d/dx GELU (tanh approximation)
* 0.5*(1 + tanh(u)) + 0.5*x*(1 - tanh(u)^2) * du/dx
* where u = k*(x + 0.044715 x^3), du/dx = k*(1 + 0.134145 x^2), k = sqrt(2/pi)
*/
static dGelu(x) {
const k = Math.sqrt(2 / Math.PI);
const x2 = x * x;
const u = k * (x + 0.044715 * x * x2);
const t = Math.tanh(u);
const sech2 = 1 - t * t;
const du = k * (1 + 0.134145 * x2);
return 0.5 * (1 + t) + 0.5 * x * sech2 * du;
}
/* ========= Apply helpers ========= */
/** Apply an elementwise activation across a 2D matrix, returning a new matrix. */
static apply(matrix, fn) {
const out = new Array(matrix.length);
for (let i = 0; i < matrix.length; i++) {
const row = matrix[i];
const r = new Array(row.length);
for (let j = 0; j < row.length; j++)
r[j] = fn(row[j]);
out[i] = r;
}
return out;
}
/** Apply an elementwise derivative across a 2D matrix, returning a new matrix. */
static applyDerivative(matrix, dfn) {
const out = new Array(matrix.length);
for (let i = 0; i < matrix.length; i++) {
const row = matrix[i];
const r = new Array(row.length);
for (let j = 0; j < row.length; j++)
r[j] = dfn(row[j]);
out[i] = r;
}
return out;
}
/* ========= Getters ========= */
/**
* Get an activation function by name. Case-insensitive.
* For leaky ReLU, you can pass { alpha } to override the negative slope.
*/
static get(name, opts) {
var _a;
const key = name.toLowerCase();
switch (key) {
case 'relu': return this.relu;
case 'leakyrelu':
case 'leaky-relu': {
const alpha = (_a = opts === null || opts === void 0 ? void 0 : opts.alpha) !== null && _a !== void 0 ? _a : 0.01;
return (x) => this.leakyRelu(x, alpha);
}
case 'sigmoid': return this.sigmoid;
case 'tanh': return this.tanh;
case 'linear':
case 'identity':
case 'none': return this.linear;
case 'gelu': return this.gelu;
default:
throw new Error(`Unknown activation: ${name}`);
}
}
/** Get derivative function by name (mirrors get). */
static getDerivative(name, opts) {
var _a;
const key = name.toLowerCase();
switch (key) {
case 'relu': return this.dRelu;
case 'leakyrelu':
case 'leaky-relu': {
const alpha = (_a = opts === null || opts === void 0 ? void 0 : opts.alpha) !== null && _a !== void 0 ? _a : 0.01;
return (x) => this.dLeakyRelu(x, alpha);
}
case 'sigmoid': return this.dSigmoid;
case 'tanh': return this.dTanh;
case 'linear':
case 'identity':
case 'none': return this.dLinear;
case 'gelu': return this.dGelu;
default:
throw new Error(`Unknown activation derivative: ${name}`);
}
}
/** Get both forward and derivative together. */
static getPair(name, opts) {
return { f: this.get(name, opts), df: this.getDerivative(name, opts) };
}
/* ========= Optional: Softmax Jacobian (for research/tools) ========= */
/**
* Given softmax probabilities p, returns the Jacobian J = diag(p) - p p^T
* (Useful for analysis; not typically needed for ELM.)
*/
static softmaxJacobian(p) {
const n = p.length;
const J = new Array(n);
for (let i = 0; i < n; i++) {
const row = new Array(n);
for (let j = 0; j < n; j++) {
row[j] = (i === j ? p[i] : 0) - p[i] * p[j];
}
J[i] = row;
}
return J;
}
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// ELMConfig.ts - Configuration interfaces, defaults, helpers for ELM-based models
/* =========== Defaults =========== */
const defaultBase = {
hiddenUnits: 50,
activation: 'relu',
ridgeLambda: 1e-2,
weightInit: 'xavier',
seed: 1337,
dropout: 0,
log: { verbose: true, toFile: false, modelName: 'Unnamed ELM Model', level: 'info' },
};
const defaultNumericConfig = Object.assign(Object.assign({}, defaultBase), { useTokenizer: false });
const defaultTextConfig = Object.assign(Object.assign({}, defaultBase), { useTokenizer: true, maxLen: 30, charSet: 'abcdefghijklmnopqrstuvwxyz', tokenizerDelimiter: /\s+/ });
/* =========== Type guards =========== */
function isTextConfig(cfg) {
return cfg.useTokenizer === true;
}
function isNumericConfig(cfg) {
return cfg.useTokenizer !== true;
}
/* =========== Helpers =========== */
/**
* Normalize a user config with sensible defaults depending on mode.
* (Keeps the original structural type, only fills in missing optional fields.)
*/
function normalizeConfig(cfg) {
var _a, _b, _c, _d;
if (isTextConfig(cfg)) {
const merged = Object.assign(Object.assign(Object.assign({}, defaultTextConfig), cfg), { log: Object.assign(Object.assign({}, ((_a = defaultBase.log) !== null && _a !== void 0 ? _a : {})), ((_b = cfg.log) !== null && _b !== void 0 ? _b : {})) });
return merged;
}
else {
const merged = Object.assign(Object.assign(Object.assign({}, defaultNumericConfig), cfg), { log: Object.assign(Object.assign({}, ((_c = defaultBase.log) !== null && _c !== void 0 ? _c : {})), ((_d = cfg.log) !== null && _d !== void 0 ? _d : {})) });
return merged;
}
}
/**
* Rehydrate text-specific fields from a JSON-safe config
* (e.g., convert tokenizerDelimiter source string → RegExp).
*/
function deserializeTextBits(config) {
var _a, _b, _c, _d;
// If useTokenizer not true, assume numeric config
if (config.useTokenizer !== true) {
const nc = Object.assign(Object.assign(Object.assign({}, defaultNumericConfig), config), { log: Object.assign(Object.assign({}, ((_a = defaultBase.log) !== null && _a !== void 0 ? _a : {})), ((_b = config.log) !== null && _b !== void 0 ? _b : {})) });
return nc;
}
// Text config: coerce delimiter
const tDelim = config.tokenizerDelimiter;
let delimiter = undefined;
if (tDelim instanceof RegExp) {
delimiter = tDelim;
}
else if (typeof tDelim === 'string' && tDelim.length > 0) {
delimiter = new RegExp(tDelim);
}
else {
delimiter = defaultTextConfig.tokenizerDelimiter;
}
const tc = Object.assign(Object.assign(Object.assign({}, defaultTextConfig), config), { tokenizerDelimiter: delimiter, log: Object.assign(Object.assign({}, ((_c = defaultBase.log) !== null && _c !== void 0 ? _c : {})), ((_d = config.log) !== null && _d !== void 0 ? _d : {})), useTokenizer: true });
return tc;
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
class Tokenizer {
constructor(customDelimiter) {
this.delimiter = customDelimiter || /[\s,.;!?()\[\]{}"']+/;
}
tokenize(text) {
if (typeof text !== 'string') {
console.warn('[Tokenizer] Expected a string, got:', typeof text, text);
try {
text = String(text !== null && text !== void 0 ? text : '');
}
catch (_a) {
return [];
}
}
return text
.trim()
.toLowerCase()
.split(this.delimiter)
.filter(Boolean);
}
ngrams(tokens, n) {
if (n <= 0 || tokens.length < n)
return [];
const result = [];
for (let i = 0; i <= tokens.length - n; i++) {
result.push(tokens.slice(i, i + n).join(' '));
}
return result;
}
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// TextEncoder.ts - Text preprocessing and one-hot encoding for ELM
const defaultTextEncoderConfig = {
charSet: 'abcdefghijklmnopqrstuvwxyz',
maxLen: 15,
useTokenizer: false
};
class TextEncoder {
constructor(config = {}) {
const cfg = Object.assign(Object.assign({}, defaultTextEncoderConfig), config);
this.charSet = cfg.charSet;
this.charSize = cfg.charSet.length;
this.maxLen = cfg.maxLen;
this.useTokenizer = cfg.useTokenizer;
if (this.useTokenizer) {
this.tokenizer = new Tokenizer(config.tokenizerDelimiter);
}
}
charToOneHot(c) {
const index = this.charSet.indexOf(c.toLowerCase());
const vec = Array(this.charSize).fill(0);
if (index !== -1)
vec[index] = 1;
return vec;
}
textToVector(text) {
let cleaned;
if (this.useTokenizer && this.tokenizer) {
const tokens = this.tokenizer.tokenize(text).join('');
cleaned = tokens.slice(0, this.maxLen).padEnd(this.maxLen, ' ');
}
else {
cleaned = text.toLowerCase().replace(new RegExp(`[^${this.charSet}]`, 'g'), '').padEnd(this.maxLen, ' ').slice(0, this.maxLen);
}
const vec = [];
for (let i = 0; i < cleaned.length; i++) {
vec.push(...this.charToOneHot(cleaned[i]));
}
return vec;
}
normalizeVector(v) {
const norm = Math.sqrt(v.reduce((sum, x) => sum + x * x, 0));
return norm > 0 ? v.map(x => x / norm) : v;
}
getVectorSize() {
return this.charSize * this.maxLen;
}
getCharSet() {
return this.charSet;
}
getMaxLen() {
return this.maxLen;
}
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// UniversalEncoder.ts - Automatically selects appropriate encoder (char or token based)
const defaultUniversalConfig = {
charSet: 'abcdefghijklmnopqrstuvwxyz',
maxLen: 15,
useTokenizer: false,
mode: 'char'
};
class UniversalEncoder {
constructor(config = {}) {
const merged = Object.assign(Object.assign({}, defaultUniversalConfig), config);
const useTokenizer = merged.mode === 'token';
this.encoder = new TextEncoder({
charSet: merged.charSet,
maxLen: merged.maxLen,
useTokenizer,
tokenizerDelimiter: config.tokenizerDelimiter
});
}
encode(text) {
return this.encoder.textToVector(text);
}
normalize(v) {
return this.encoder.normalizeVector(v);
}
getVectorSize() {
return this.encoder.getVectorSize();
}
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// Augment.ts - Basic augmentation utilities for category training examples
class Augment {
static addSuffix(text, suffixes) {
return suffixes.map(suffix => `${text} ${suffix}`);
}
static addPrefix(text, prefixes) {
return prefixes.map(prefix => `${prefix} ${text}`);
}
static addNoise(text, charSet, noiseRate = 0.1) {
const chars = text.split('');
for (let i = 0; i < chars.length; i++) {
if (Math.random() < noiseRate) {
const randomChar = charSet[Math.floor(Math.random() * charSet.length)];
chars[i] = randomChar;
}
}
return chars.join('');
}
static mix(text, mixins) {
return mixins.map(m => `${text} ${m}`);
}
static generateVariants(text, charSet, options) {
const variants = [text];
if (options === null || options === void 0 ? void 0 : options.suffixes) {
variants.push(...this.addSuffix(text, options.suffixes));
}
if (options === null || options === void 0 ? void 0 : options.prefixes) {
variants.push(...this.addPrefix(text, options.prefixes));
}
if (options === null || options === void 0 ? void 0 : options.includeNoise) {
variants.push(this.addNoise(text, charSet));
}
return variants;
}
}
// © 2025 AsterMind LLC – All Rights Reserved.
// Patent Pending US 63/897,713
// ELM.ts - Core ELM logic with TypeScript types (numeric & text modes)
// Seeded PRNG (xorshift-ish) for deterministic init
function makePRNG$2(seed = 123456789) {
let s = seed | 0 || 1;
return () => {
s ^= s << 13;
s ^= s >>> 17;
s ^= s << 5;
return ((s >>> 0) / 0xffffffff);
};
}
function clampInt(x, lo, hi) {
const xi = x | 0;
return xi < lo ? lo : (xi > hi ? hi : xi);
}
function isOneHot2D(Y) {
return Array.isArray(Y) && Array.isArray(Y[0]) && Number.isFinite(Y[0][0]);
}
function maxLabel(y) {
let m = -Infinity;
for (let i = 0; i < y.length; i++) {
const v = y[i] | 0;
if (v > m)
m = v;
}
return m === -Infinity ? 0 : m;
}
/** One-hot (clamped) */
function toOneHotClamped(labels, k) {
const K = k | 0;
const Y = new Array(labels.length);
for (let i = 0; i < labels.length; i++) {
const j = clampInt(labels[i], 0, K - 1);
const row = new Array(K).fill(0);
row[j] = 1;
Y[i] = row;
}
return Y;
}
/** (HᵀH + λI)B = HᵀY solved via Cholesky */
function ridgeSolve(H, Y, lambda) {
const Ht = Matrix.transpose(H);
const A = Matrix.addRegularization(Matrix.multiply(Ht, H), lambda + 1e-10);
const R = Matrix.multiply(Ht, Y);
return Matrix.solveCholesky(A, R, 1e-10);
}
/* =========================
* ELM class
* ========================= */
class ELM {
constructor(config) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
// Merge with mode-appropriate defaults
const cfg = normalizeConfig(config);
this.config = cfg;
this.categories = cfg.categories;
this.hiddenUnits = cfg.hiddenUnits;
this.activation = (_a = cfg.activation) !== null && _a !== void 0 ? _a : 'relu';
this.useTokenizer = isTextConfig(cfg);
this.maxLen = isTextConfig(cfg) ? cfg.maxLen : 0;
this.charSet = isTextConfig(cfg) ? ((_b = cfg.charSet) !== null && _b !== void 0 ? _b : 'abcdefghijklmnopqrstuvwxyz') : 'abcdefghijklmnopqrstuvwxyz';
this.tokenizerDelimiter = isTextConfig(cfg) ? cfg.tokenizerDelimiter : undefined;
this.metrics = cfg.metrics;
this.verbose = (_d = (_c = cfg.log) === null || _c === void 0 ? void 0 : _c.verbose) !== null && _d !== void 0 ? _d : true;
this.modelName = (_f = (_e = cfg.log) === null || _e === void 0 ? void 0 : _e.modelName) !== null && _f !== void 0 ? _f : 'Unnamed ELM Model';
this.logToFile = (_h = (_g = cfg.log) === null || _g === void 0 ? void 0 : _g.toFile) !== null && _h !== void 0 ? _h : false;
this.dropout = (_j = cfg.dropout) !== null && _j !== void 0 ? _j : 0;
this.ridgeLambda = Math.max((_k = cfg.ridgeLambda) !== null && _k !== void 0 ? _k : 1e-2, 1e-8);
// Seeded RNG
const seed = (_l = cfg.seed) !== null && _l !== void 0 ? _l : 1337;
this.rng = makePRNG$2(seed);
// Create encoder only if tokenizer is enabled
if (this.useTokenizer) {
this.encoder = new UniversalEncoder({
charSet: this.charSet,
maxLen: this.maxLen,
useTokenizer: this.useTokenizer,
tokenizerDelimiter: this.tokenizerDelimiter,
mode: this.useTokenizer ? 'token' : 'char'
});
}
// Weights are allocated on first training call (inputDim known then)
this.model = null;
}
/* ========= Encoder narrowing (Option A) ========= */
assertEncoder() {
if (!this.encoder) {
throw new Error('Encoder is not initialized. Enable useTokenizer:true or construct an encoder.');
}
return this.encoder;
}
/* ========= initialization ========= */
xavierLimit(fanIn, fanOut) {
return Math.sqrt(6 / (fanIn + fanOut));
}
randomMatrix(rows, cols) {
var _a;
const weightInit = (_a = this.config.weightInit) !== null && _a !== void 0 ? _a : 'uniform';
if (weightInit === 'xavier') {
const limit = this.xavierLimit(cols, rows);
if (this.verbose)
console.log(`✨ Xavier init with limit sqrt(6/(${cols}+${rows})) ≈ ${limit.toFixed(4)}`);
return Array.from({ length: rows }, () => Array.from({ length: cols }, () => (this.rng() * 2 - 1) * limit));
}
else {
if (this.verbose)
console.log(`✨ Uniform init [-1,1] (seeded)`);
return Array.from({ length: rows }, () => Array.from({ length: cols }, () => (this.rng() * 2 - 1)));
}
}
buildHidden(X, W, b) {
const tempH = Matrix.multiply(X, Matrix.transpose(W)); // N x hidden
const activationFn = Activations.get(this.activation);
let H = Activations.apply(tempH.map(row => row.map((val, j) => val + b[j][0])), activationFn);
if (this.dropout > 0) {
const keepProb = 1 - this.dropout;
for (let i = 0; i < H.length; i++) {
for (let j = 0; j < H[0].length; j++) {
if (this.rng() < this.dropout)
H[i][j] = 0;
else
H[i][j] /= keepProb;
}
}
}
return H;
}
/* ========= public helpers ========= */
oneHot(n, index) {
return Array.from({ length: n }, (_, i) => (i === index ? 1 : 0));
}
setCategories(categories) {
this.categories = categories;
}
loadModelFromJSON(json) {
var _a, _b, _c, _d, _e;
try {
const parsed = JSON.parse(json);
const cfg = deserializeTextBits(parsed.config);
// Rebuild instance config
this.config = cfg;
this.categories = (_a = cfg.categories) !== null && _a !== void 0 ? _a : this.categories;
this.hiddenUnits = (_b = cfg.hiddenUnits) !== null && _b !== void 0 ? _b : this.hiddenUnits;
this.activation = (_c = cfg.activation) !== null && _c !== void 0 ? _c : this.activation;
this.useTokenizer = cfg.useTokenizer === true;
this.maxLen = (_d = cfg.maxLen) !== null && _d !== void 0 ? _d : this.maxLen;
this.charSet = (_e = cfg.charSet) !== null && _e !== void 0 ? _e : this.charSet;
this.tokenizerDelimiter = cfg.tokenizerDelimiter;
if (this.useTokenizer) {
this.encoder = new UniversalEncoder({
charSet: this.charSet,
maxLen: this.maxLen,
useTokenizer: this.useTokenizer,
tokenizerDelimiter: this.tokenizerDelimiter,
mode: this.useTokenizer ? 'token' : 'char'
});
}
else {
this.encoder = undefined;
}
// Restore weights
const { W, b, B } = parsed;
this.model = { W, b, beta: B };
this.savedModelJSON = json;
if (this.verbose)
console.log(`✅ ${this.modelName} Model loaded from JSON`);
}
catch (e) {
console.error(`❌ Failed to load ${this.modelName} model from JSON:`, e);
}
}
/* ========= Numeric training tolerance ========= */
/** Decide output dimension from config/categories/labels/one-hot */
resolveOutputDim(yOrY) {
// Prefer explicit config
const cfgOut = this.config.outputDim;
if (Number.isFinite(cfgOut) && cfgOut > 0)
return cfgOut | 0;
// Then categories length if present
if (Array.isArray(this.categories) && this.categories.length > 0)
return this.categories.length | 0;
// Infer from data
if (isOneHot2D(yOrY))
return (yOrY[0].length | 0) || 1;
return (maxLabel(yOrY) + 1) | 0;
}
/** Coerce X, and turn labels→one-hot if needed. Always returns strict number[][] */
coerceXY(X, yOrY) {
const Xnum = ensureRectNumber2D(X, undefined, 'X');
const outDim = this.resolveOutputDim(yOrY);
let Ynum;
if (isOneHot2D(yOrY)) {
// Ensure rect with exact width outDim (pad/trunc to be safe)
Ynum = ensureRectNumber2D(yOrY, outDim, 'Y(one-hot)');
}
else {
// Labels → clamped one-hot
Ynum = ensureRectNumber2D(toOneHotClamped(yOrY, outDim), outDim, 'Y(labels→one-hot)');
}
// If categories length mismatches inferred outDim, adjust categories (non-breaking)
if (!this.categories || this.categories.length !== outDim) {
this.categories = Array.from({ length: outDim }, (_, i) => { var _a, _b; return (_b = (_a = this.categories) === null || _a === void 0 ? void 0 : _a[i]) !== null && _b !== void 0 ? _b : String(i); });
}
return { Xnum, Ynum, outDim };
}
/* ========= Training on numeric vectors =========
* y can be class indices OR one-hot.
*/
trainFromData(X, y, options) {
if (!(X === null || X === void 0 ? void 0 : X.length))
throw new Error('trainFromData: X is empty');
// Coerce & shape
const { Xnum, Ynum, outDim } = this.coerceXY(X, y);
const n = Xnum.length;
const inputDim = Xnum[0].length;
// init / reuse
let W, b;
const reuseWeights = (options === null || options === void 0 ? void 0 : options.reuseWeights) === true && this.model;
if (reuseWeights && this.model) {
W = this.model.W;
b = this.model.b;
if (this.verbose)
console.log('🔄 Reusing existing weights/biases for training.');
}
else {
W = this.randomMatrix(this.hiddenUnits, inputDim);
b = this.randomMatrix(this.hiddenUnits, 1);
if (this.verbose)
console.log('✨ Initializing fresh weights/biases for training.');
}
// Hidden
let H = this.buildHidden(Xnum, W, b);
// Optional sample weights
let Yw = Ynum;
if (options === null || options === void 0 ? void 0 : options.weights) {
const ww = options.weights;
if (ww.length !== n) {
throw new Error(`Weight array length ${ww.length} does not match sample count ${n}`);
}
H = H.map((row, i) => row.map(x => x * Math.sqrt(ww[i])));
Yw = Ynum.map((row, i) => row.map(x => x * Math.sqrt(ww[i])));
}
// Solve ridge (stable)
const beta = ridgeSolve(H, Yw, this.ridgeLambda);
this.model = { W, b, beta };
// Evaluate & maybe save
const predictions = Matrix.multiply(H, beta);
if (this.metrics) {
const rmse = this.calculateRMSE(Ynum, predictions);
const mae = this.calculateMAE(Ynum, predictions);
const acc = this.calculateAccuracy(Ynum, predictions);
const f1 = this.calculateF1Score(Ynum, predictions);
const ce = this.calculateCrossEntropy(Ynum, predictions);
const r2 = this.calculateR2Score(Ynum, predictions);
const results = { rmse, mae, accuracy: acc, f1, crossEntropy: ce, r2 };
let allPassed = true;
if (this.metrics.rmse !== undefined && rmse > this.metrics.rmse)
allPassed = false;
if (this.metrics.mae !== undefined && mae > this.metrics.mae)
allPassed = false;
if (this.metrics.accuracy !== undefined && acc < this.metrics.accuracy)
allPassed = false;
if (this.metrics.f1 !== undefined && f1 < this.metrics.f1)
allPassed = false;
if (this.metrics.crossEntropy !== undefined && ce > this.metrics.crossEntropy)
allPassed = false;
if (this.metrics.r2 !== undefined && r2 < this.metrics.r2)
allPassed = false;
if (this.verbose)
this.logMetrics(results);
if (allPassed) {
this.savedModelJSON = JSON.stringify({
config: this.serializeConfig(),
W, b, B: beta
});
if (this.verbose)
console.log('✅ Model passed thresholds and was saved to JSON.');
if (this.config.exportFileName)
this.saveModelAsJSONFile(this.config.exportFileName);
}
else {
if (this.verbose)
console.log('❌ Model not saved: One or more thresholds not met.');
}
}
else {
// No metrics—always save
this.savedModelJSON = JSON.stringify({
config: this.serializeConfig(),
W, b, B: beta
});
if (this.verbose)
console.log('✅ Model trained with no metrics—saved by default.');
if (this.config.exportFileName)
this.saveModelAsJSONFile(this.config.exportFileName);
}
return { epochs: 1, metrics: undefined };
}
/* ========= Training from category strings (text mode) ========= */
train(augmentationOptions, weights) {
if (!this.useTokenizer) {
throw new Error('train(): text training requires useTokenizer:true');
}
const enc = this.assertEncoder();
const X = [];
let Y = [];
this.categories.forEach((cat, i) => {
const variants = Augment.generateVariants(cat, this.charSet, augmentationOptions);
for (const variant of variants) {
const vec = enc.normalize(enc.encode(variant));
X.push(vec);
Y.push(this.oneHot(this.categories.length, i));
}
});
const inputDim = X[0].length;
const W = this.randomMatrix(this.hiddenUnits, inputDim);
const b = this.randomMatrix(this.hiddenUnits, 1);
let H = this.buildHidden(X, W, b);
if (weights) {
if (weights.length !== H.length) {
throw new Error(`Weight array length ${weights.length} does not match sample count ${H.length}`);
}
H = H.map((row, i) => row.map(x => x * Math.sqrt(weights[i])));
Y = Y.map((row, i) => row.map(x => x * Math.sqrt(weights[i])));
}
const beta = ridgeSolve(H, Y, this.ridgeLambda);
this.model = { W, b, beta };
const predictions = Matrix.multiply(H, beta);
if (this.metrics) {
const rmse = this.calculateRMSE(Y, predictions);
const mae = this.calculateMAE(Y, predictions);
const acc = this.calculateAccuracy(Y, predictions);
const f1 = this.calculateF1Score(Y, predictions);
const ce = this.calculateCrossEntropy(Y, predictions);
const r2 = this.calculateR2Score(Y, predictions);
const results = { rmse, mae, accuracy: acc, f1, crossEntropy: ce, r2 };
let allPassed = true;
if (this.metrics.rmse !== undefined && rmse > this.metrics.rmse)
allPassed = false;
if (this.metrics.mae !== undefined && mae > this.metrics.mae)
allPassed = false;
if (this.metrics.accuracy !== undefined && acc < this.metrics.accuracy)
allP