export class OptimizedSparseMatrix {
values;
colIndices;
rowPtr;
rows;
cols;
nnz;
constructor(values, colIndices, rowPtr, rows, cols) {
this.values = values;
this.colIndices = colIndices;
this.rowPtr = rowPtr;
this.rows = rows;
this.cols = cols;
this.nnz = values.length;
}
static fromTriplets(triplets, rows, cols) {
triplets.sort((a, b) => {
if (a[0] !== b[0])
return a[0] - b[0];
return a[1] - b[1];
});
const deduped = [];
for (const [row, col, val] of triplets) {
const lastEntry = deduped[deduped.length - 1];
if (lastEntry && lastEntry[0] === row && lastEntry[1] === col) {
lastEntry[2] += val;
}
else {
deduped.push([row, col, val]);
}
}
const nnz = deduped.length;
const values = new Float64Array(nnz);
const colIndices = new Uint32Array(nnz);
const rowPtr = new Uint32Array(rows + 1);
let currentRow = 0;
for (let i = 0; i < nnz; i++) {
const [row, col, val] = deduped[i];
while (currentRow <= row) {
rowPtr[currentRow] = i;
currentRow++;
}
values[i] = val;
colIndices[i] = col;
}
while (currentRow <= rows) {
rowPtr[currentRow] = nnz;
currentRow++;
}
return new OptimizedSparseMatrix(values, colIndices, rowPtr, rows, cols);
}
multiplyVector(x, y) {
if (x.length !== this.cols) {
throw new Error(`Vector length ${x.length} doesn't match matrix columns ${this.cols}`);
}
if (y.length !== this.rows) {
throw new Error(`Output vector length ${y.length} doesn't match matrix rows ${this.rows}`);
}
y.fill(0.0);
for (let row = 0; row < this.rows; row++) {
const start = this.rowPtr[row];
const end = this.rowPtr[row + 1];
if (end <= start)
continue;
let sum = 0.0;
let idx = start;
const unrollEnd = start + ((end - start) & ~3);
while (idx < unrollEnd) {
sum += this.values[idx] * x[this.colIndices[idx]];
sum += this.values[idx + 1] * x[this.colIndices[idx + 1]];
sum += this.values[idx + 2] * x[this.colIndices[idx + 2]];
sum += this.values[idx + 3] * x[this.colIndices[idx + 3]];
idx += 4;
}
while (idx < end) {
sum += this.values[idx] * x[this.colIndices[idx]];
idx++;
}
y[row] = sum;
}
}
get dimensions() {
return [this.rows, this.cols];
}
get nonZeros() {
return this.nnz;
}
}
export class VectorOps {
static dotProduct(x, y) {
if (x.length !== y.length) {
throw new Error(`Vector lengths don't match: ${x.length} vs ${y.length}`);
}
const n = x.length;
let result = 0.0;
let i = 0;
const unrollEnd = n & ~3;
while (i < unrollEnd) {
result += x[i] * y[i];
result += x[i + 1] * y[i + 1];
result += x[i + 2] * y[i + 2];
result += x[i + 3] * y[i + 3];
i += 4;
}
while (i < n) {
result += x[i] * y[i];
i++;
}
return result;
}
static axpy(alpha, x, y) {
if (x.length !== y.length) {
throw new Error(`Vector lengths don't match: ${x.length} vs ${y.length}`);
}
const n = x.length;
let i = 0;
const unrollEnd = n & ~3;
while (i < unrollEnd) {
y[i] += alpha * x[i];
y[i + 1] += alpha * x[i + 1];
y[i + 2] += alpha * x[i + 2];
y[i + 3] += alpha * x[i + 3];
i += 4;
}
while (i < n) {
y[i] += alpha * x[i];
i++;
}
}
static norm(x) {
return Math.sqrt(VectorOps.dotProduct(x, x));
}
static copy(src, dst) {
dst.set(src);
}
static scale(alpha, x) {
const n = x.length;
let i = 0;
const unrollEnd = n & ~3;
while (i < unrollEnd) {
x[i] *= alpha;
x[i + 1] *= alpha;
x[i + 2] *= alpha;
x[i + 3] *= alpha;
i += 4;
}
while (i < n) {
x[i] *= alpha;
i++;
}
}
}
export class HighPerformanceConjugateGradientSolver {
config;
workspaceVectors = { r: null, p: null, ap: null };
constructor(config = {}) {
this.config = {
maxIterations: config.maxIterations ?? 1000,
tolerance: config.tolerance ?? 1e-6,
enableProfiling: config.enableProfiling ?? false,
usePreconditioning: config.usePreconditioning ?? false,
};
}
solve(matrix, b) {
const [rows, cols] = matrix.dimensions;
if (rows !== cols) {
throw new Error('Matrix must be square');
}
if (b.length !== rows) {
throw new Error('Right-hand side vector length must match matrix size');
}
const startTime = performance.now();
this.ensureWorkspaceSize(rows);
const r = this.workspaceVectors.r;
const p = this.workspaceVectors.p;
const ap = this.workspaceVectors.ap;
const x = new Float64Array(rows);
VectorOps.copy(b, r);
VectorOps.copy(r, p);
let rsold = VectorOps.dotProduct(r, r);
const bNorm = VectorOps.norm(b);
let matVecCount = 0;
let dotProductCount = 1; let axpyCount = 0;
let totalFlops = 2 * rows; let iteration = 0;
let converged = false;
while (iteration < this.config.maxIterations) {
matrix.multiplyVector(p, ap);
matVecCount++;
totalFlops += 2 * matrix.nonZeros;
const pAp = VectorOps.dotProduct(p, ap);
dotProductCount++;
totalFlops += 2 * rows;
if (Math.abs(pAp) < 1e-16) {
throw new Error('Matrix appears to be singular');
}
const alpha = rsold / pAp;
VectorOps.axpy(alpha, p, x);
axpyCount++;
totalFlops += 2 * rows;
VectorOps.axpy(-alpha, ap, r);
axpyCount++;
totalFlops += 2 * rows;
const rsnew = VectorOps.dotProduct(r, r);
dotProductCount++;
totalFlops += 2 * rows;
const residualNorm = Math.sqrt(rsnew);
const relativeResidual = bNorm > 0 ? residualNorm / bNorm : residualNorm;
if (relativeResidual < this.config.tolerance) {
converged = true;
break;
}
const beta = rsnew / rsold;
for (let i = 0; i < rows; i++) {
p[i] = r[i] + beta * p[i];
}
totalFlops += 2 * rows;
rsold = rsnew;
iteration++;
}
const computationTimeMs = performance.now() - startTime;
const gflops = computationTimeMs > 0 ? (totalFlops / (computationTimeMs / 1000)) / 1e9 : 0;
const bytesPerMatVec = matrix.nonZeros * 8 + rows * 16; const totalBytes = matVecCount * bytesPerMatVec + dotProductCount * rows * 16;
const bandwidth = computationTimeMs > 0 ? (totalBytes / (computationTimeMs / 1000)) / 1e9 : 0;
const finalResidualNorm = Math.sqrt(rsold);
return {
solution: x,
residualNorm: finalResidualNorm,
iterations: iteration,
converged,
performanceStats: {
matVecCount,
dotProductCount,
axpyCount,
totalFlops,
computationTimeMs,
gflops,
bandwidth,
},
};
}
ensureWorkspaceSize(size) {
if (!this.workspaceVectors.r || this.workspaceVectors.r.length !== size) {
this.workspaceVectors.r = new Float64Array(size);
this.workspaceVectors.p = new Float64Array(size);
this.workspaceVectors.ap = new Float64Array(size);
}
}
dispose() {
this.workspaceVectors.r = null;
this.workspaceVectors.p = null;
this.workspaceVectors.ap = null;
}
}
export class VectorPool {
pools = new Map();
maxPoolSize = 10;
getVector(size) {
const pool = this.pools.get(size);
if (pool && pool.length > 0) {
const vector = pool.pop();
vector.fill(0); return vector;
}
return new Float64Array(size);
}
returnVector(vector) {
const size = vector.length;
let pool = this.pools.get(size);
if (!pool) {
pool = [];
this.pools.set(size, pool);
}
if (pool.length < this.maxPoolSize) {
pool.push(vector);
}
}
clear() {
this.pools.clear();
}
}
export function createJacobiPreconditioner(matrix) {
const [rows] = matrix.dimensions;
const preconditioner = new Float64Array(rows);
const values = matrix.values;
const colIndices = matrix.colIndices;
const rowPtr = matrix.rowPtr;
for (let row = 0; row < rows; row++) {
const start = rowPtr[row];
const end = rowPtr[row + 1];
for (let idx = start; idx < end; idx++) {
if (colIndices[idx] === row) {
preconditioner[row] = 1.0 / Math.max(Math.abs(values[idx]), 1e-16);
break;
}
}
}
return preconditioner;
}
export function createHighPerformanceSolver(config) {
return new HighPerformanceConjugateGradientSolver(config);
}