clustering-tfjs
Version:
High-performance TypeScript clustering algorithms (K-Means, Spectral, Agglomerative) with TensorFlow.js acceleration and scikit-learn compatibility
69 lines (68 loc) • 2.77 kB
JavaScript
;
/**
* Deterministic eigen-pair post–processing.
*
* Many numerical eigensolvers return eigenvectors in arbitrary order and with
* an arbitrary global ± sign per vector. For downstream algorithms (e.g.
* Spectral Clustering) we require a stable ordering and sign convention so
* that identical input always produces identical embeddings.
*
* The convention implemented here matches scikit-learn:
* 1. Eigen-pairs are sorted by ascending eigen-value.
* 2. For every eigen-vector the component with the largest absolute value
* is made positive by optionally multiplying the vector by −1.
*
* The function operates purely on native JavaScript arrays to avoid pulling
* TensorFlow.js into low-level utilities and reduce garbage generation.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.deterministic_eigenpair_processing = deterministic_eigenpair_processing;
/**
* Applies deterministic ordering and sign convention to raw eigen-pairs.
*/
function deterministic_eigenpair_processing(input) {
const { eigenvalues, eigenvectors } = input;
if (eigenvectors.length === 0) {
return {
eigenvalues: [],
valuesSorted: [],
eigenvectors: [],
vectorsSorted: [],
};
}
const n = eigenvectors.length;
// Validate shape (n rows, n columns)
if (eigenvectors.some((row) => row.length !== n) ||
eigenvalues.length !== n) {
throw new Error('eigenvectors must be square (n×n) and eigenvalues length must equal n.');
}
// Step 1: sort indices by ascending eigenvalue
const indexed = eigenvalues.map((val, idx) => ({ val, idx }));
indexed.sort((a, b) => a.val - b.val);
const eigenvaluesSorted = indexed.map((p) => p.val);
const eigenvectorsSorted = Array.from({ length: n }, () => new Array(n));
// Step 2: for each eigenvector apply sign fix while copying into new matrix
for (let newCol = 0; newCol < n; newCol++) {
const srcCol = indexed[newCol].idx;
// Find entry with maximum absolute magnitude
let maxAbs = 0;
let maxRow = 0;
for (let row = 0; row < n; row++) {
const absVal = Math.abs(eigenvectors[row][srcCol]);
if (absVal > maxAbs) {
maxAbs = absVal;
maxRow = row;
}
}
const sign = eigenvectors[maxRow][srcCol] < 0 ? -1 : 1;
for (let row = 0; row < n; row++) {
eigenvectorsSorted[row][newCol] = sign * eigenvectors[row][srcCol];
}
}
return {
eigenvalues: eigenvaluesSorted,
valuesSorted: eigenvaluesSorted,
eigenvectors: eigenvectorsSorted,
vectorsSorted: eigenvectorsSorted,
};
}