@astermind/astermind-elm
Version:
JavaScript Extreme Learning Machine (ELM) library for browser and Node.js.
1,294 lines (1,281 loc) • 81.5 kB
JavaScript
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
// © 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$1 = 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$1));
}
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$1)
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$1)
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;
}
/* =========== 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$1(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$1(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)
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 {
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 };
}
/* ========= Prediction ========= */
/** Text prediction (uses Option A narrowing) */
predict(text, topK = 5) {
if (!this.model)
throw new Error('Model not trained.');
if (!this.useTokenizer) {
throw new Error('predict(text) requires useTokenizer:true');
}
const enc = this.assertEncoder();
const vec = enc.normalize(enc.encode(text));
const logits = this.predictLogitsFromVector(vec);
const probs = Activations.softmax(logits);
return probs
.map((p, i) => ({ label: this.categories[i], prob: p }))
.sort((a, b) => b.prob - a.prob)
.slice(0, topK);
}
/** Vector batch prediction (kept for back-compat) */
predictFromVector(inputVecRows, topK = 5) {
if (!this.model)
throw new Error('Model not trained.');
return inputVecRows.map(vec => {
const logits = this.predictLogitsFromVector(vec);
const probs = Activations.softmax(logits);
return probs
.map((p, i) => ({ label: this.categories[i], prob: p }))
.sort((a, b) => b.prob - a.prob)
.slice(0, topK);
});
}
/** Raw logits for a single numeric vector */
predictLogitsFromVector(vec) {
if (!this.model)
throw new Error('Model not trained.');
const { W, b, beta } = this.model;
// Hidden
const tempH = Matrix.multiply([vec], Matrix.transpose(W)); // 1 x hidden
const activationFn = Activations.get(this.activation);
const H = Activations.apply(tempH.map(row => row.map((val, j) => val + b[j][0])), activationFn); // 1 x hidden
// Output logits
return Matrix.multiply(H, beta)[0]; // 1 x outDim → vec
}
/** Raw logits for a batch of numeric vectors */
predictLogitsFromVect