ts-quantum
Version:
TypeScript library for quantum mechanics calculations and utilities
250 lines • 7.5 kB
JavaScript
/**
* Sparse matrix utilities for quantum operators
*/
import * as math from 'mathjs';
/**
* Create an empty sparse matrix
*/
export function createSparseMatrix(rows, cols) {
return {
rows,
cols,
entries: [],
nnz: 0
};
}
/**
* Add entry to sparse matrix
*/
export function setSparseEntry(matrix, row, col, value) {
// Remove existing entry if present
const existingIndex = matrix.entries.findIndex(entry => entry.row === row && entry.col === col);
if (existingIndex !== -1) {
matrix.entries.splice(existingIndex, 1);
matrix.nnz--;
}
// Add new entry if non-zero
if (!isZeroComplex(value)) {
matrix.entries.push({ row, col, value });
matrix.nnz++;
}
}
/**
* Get entry from sparse matrix
*/
export function getSparseEntry(matrix, row, col) {
const entry = matrix.entries.find(e => e.row === row && e.col === col);
return entry ? entry.value : math.complex(0, 0);
}
/**
* Multiply sparse matrix by dense vector
*/
export function sparseVectorMultiply(matrix, vector) {
if (matrix.cols !== vector.length) {
throw new Error(`Matrix columns (${matrix.cols}) must match vector length (${vector.length})`);
}
const result = Array.from({ length: matrix.rows }, () => math.complex(0, 0));
for (const entry of matrix.entries) {
const product = math.multiply(entry.value, vector[entry.col]);
if (typeof product !== 'object' || !('re' in product) || !('im' in product)) {
throw new Error('Invalid complex multiplication result');
}
result[entry.row] = math.add(result[entry.row], product);
}
return result;
}
/**
* Multiply two sparse matrices
*/
export function sparseMatrixMultiply(a, b) {
if (a.cols !== b.rows) {
throw new Error(`Matrix A columns (${a.cols}) must match matrix B rows (${b.rows})`);
}
const result = createSparseMatrix(a.rows, b.cols);
// Create column index for matrix B for efficient access
const bCols = {};
for (const entry of b.entries) {
if (!bCols[entry.row]) {
bCols[entry.row] = [];
}
bCols[entry.row].push(entry);
}
// Multiply
for (const aEntry of a.entries) {
const bCol = bCols[aEntry.col];
if (bCol) {
for (const bEntry of bCol) {
const row = aEntry.row;
const col = bEntry.col;
const product = math.multiply(aEntry.value, bEntry.value);
const existing = getSparseEntry(result, row, col);
const sum = math.add(existing, product);
setSparseEntry(result, row, col, sum);
}
}
}
return result;
}
/**
* Convert sparse matrix to dense
*/
export function sparseToDense(matrix) {
const dense = Array.from({ length: matrix.rows }, () => Array.from({ length: matrix.cols }, () => math.complex(0, 0)));
for (const entry of matrix.entries) {
dense[entry.row][entry.col] = entry.value;
}
return dense;
}
/**
* Convert dense matrix to sparse
*/
export function denseToSparse(dense) {
if (dense.length === 0) {
return createSparseMatrix(0, 0);
}
const rows = dense.length;
const cols = dense[0].length;
// Validate matrix is rectangular
if (!dense.every(row => row.length === cols)) {
throw new Error('Input matrix must be rectangular (all rows must have same length)');
}
const matrix = createSparseMatrix(rows, cols);
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (!isZeroComplex(dense[i][j])) {
setSparseEntry(matrix, i, j, dense[i][j]);
}
}
}
return matrix;
}
/**
* Transpose sparse matrix
*/
export function sparseTranspose(matrix) {
const result = createSparseMatrix(matrix.cols, matrix.rows);
for (const entry of matrix.entries) {
setSparseEntry(result, entry.col, entry.row, entry.value);
}
return result;
}
/**
* Conjugate transpose sparse matrix
*/
export function sparseConjugateTranspose(matrix) {
const result = createSparseMatrix(matrix.cols, matrix.rows);
for (const entry of matrix.entries) {
const conjugate = math.conj(entry.value);
setSparseEntry(result, entry.col, entry.row, conjugate);
}
return result;
}
/**
* Calculate trace of sparse matrix
*/
export function sparseTrace(matrix) {
if (matrix.rows !== matrix.cols) {
throw new Error('Trace requires square matrix');
}
let trace = math.complex(0, 0);
for (const entry of matrix.entries) {
if (entry.row === entry.col) {
trace = math.add(trace, entry.value);
}
}
return trace;
}
/**
* Calculate Frobenius norm of sparse matrix
*/
export function sparseNorm(matrix) {
let sum = 0;
for (const entry of matrix.entries) {
const magnitude = Number(math.abs(entry.value));
sum += magnitude * magnitude;
}
return Math.sqrt(sum);
}
/**
* Check if matrix is identity
*/
export function isIdentityMatrix(matrix, tolerance = 1e-12) {
if (matrix.rows !== matrix.cols) {
return false;
}
const n = matrix.rows;
// Check if we have exactly n entries (diagonal)
if (matrix.nnz !== n) {
return false;
}
// Check each entry is on diagonal and equals 1
for (const entry of matrix.entries) {
if (entry.row !== entry.col) {
return false;
}
const realPart = Number(entry.value.re);
const imagPart = Number(entry.value.im);
if (Math.abs(realPart - 1) > tolerance || Math.abs(imagPart) > tolerance) {
return false;
}
}
return true;
}
/**
* Check if matrix is diagonal
*/
export function isSparseDiagonalMatrix(matrix) {
for (const entry of matrix.entries) {
if (entry.row !== entry.col) {
return false;
}
}
return true;
}
/**
* Extract diagonal entries from sparse matrix
*/
export function extractDiagonalEntries(matrix) {
const diagonal = new Array(Math.min(matrix.rows, matrix.cols)).fill(0).map(() => math.complex(0, 0));
for (const entry of matrix.entries) {
if (entry.row === entry.col) {
diagonal[entry.row] = entry.value;
}
}
return diagonal;
}
/**
* Validate sparse matrix structure
*/
export function validateSparseMatrix(matrix) {
// Check dimensions
if (matrix.rows <= 0 || matrix.cols <= 0) {
return false;
}
// Check entries are within bounds
for (const entry of matrix.entries) {
if (entry.row < 0 || entry.row >= matrix.rows ||
entry.col < 0 || entry.col >= matrix.cols) {
return false;
}
}
// Check nnz matches entries length
return matrix.nnz === matrix.entries.length;
}
/**
* Remove entries that are effectively zero
*/
export function removeSparseZeros(matrix, tolerance = 1e-12) {
matrix.entries = matrix.entries.filter(entry => !isZeroComplex(entry.value, tolerance));
matrix.nnz = matrix.entries.length;
}
/**
* Check if complex number is effectively zero
*/
function isZeroComplex(value, tolerance = 1e-12) {
if (typeof value !== 'object' || !('re' in value) || !('im' in value)) {
throw new Error('Invalid complex number');
}
return Math.abs(value.re) < tolerance && Math.abs(value.im) < tolerance;
}
//# sourceMappingURL=sparse.js.map