import { SolverError, ErrorCodes } from './types.js';
export class MatrixOperations {
static validateMatrix(matrix) {
if (!matrix) {
throw new SolverError('Matrix is required', ErrorCodes.INVALID_MATRIX);
}
if (matrix.rows <= 0 || matrix.cols <= 0) {
throw new SolverError('Matrix dimensions must be positive', ErrorCodes.INVALID_DIMENSIONS);
}
if (matrix.format === 'dense') {
const dense = matrix;
if (!Array.isArray(dense.data) || dense.data.length !== dense.rows) {
throw new SolverError('Dense matrix data must be array of rows', ErrorCodes.INVALID_MATRIX);
}
for (let i = 0; i < dense.rows; i++) {
if (!Array.isArray(dense.data[i]) || dense.data[i].length !== dense.cols) {
throw new SolverError(`Row ${i} has invalid length`, ErrorCodes.INVALID_MATRIX);
}
}
}
else if (matrix.format === 'coo') {
const sparse = matrix;
const { values, rowIndices, colIndices } = sparse;
if (!Array.isArray(values) || !Array.isArray(rowIndices) || !Array.isArray(colIndices)) {
throw new SolverError('COO matrix must have values, rowIndices, and colIndices arrays', ErrorCodes.INVALID_MATRIX);
}
if (values.length !== rowIndices.length || values.length !== colIndices.length) {
throw new SolverError('COO matrix arrays must have same length', ErrorCodes.INVALID_MATRIX);
}
for (let i = 0; i < rowIndices.length; i++) {
if (rowIndices[i] < 0 || rowIndices[i] >= sparse.rows) {
throw new SolverError(`Invalid row index ${rowIndices[i]}`, ErrorCodes.INVALID_MATRIX);
}
if (colIndices[i] < 0 || colIndices[i] >= sparse.cols) {
throw new SolverError(`Invalid column index ${colIndices[i]}`, ErrorCodes.INVALID_MATRIX);
}
}
}
else {
throw new SolverError(`Unsupported matrix format: ${matrix.format}`, ErrorCodes.INVALID_MATRIX);
}
}
static multiplyMatrixVector(matrix, vector) {
this.validateMatrix(matrix);
if (vector.length !== matrix.cols) {
throw new SolverError(`Vector length ${vector.length} does not match matrix columns ${matrix.cols}`, ErrorCodes.INVALID_DIMENSIONS);
}
const result = new Array(matrix.rows).fill(0);
if (matrix.format === 'dense') {
const dense = matrix;
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.cols; j++) {
result[i] += dense.data[i][j] * vector[j];
}
}
}
else if (matrix.format === 'coo') {
const sparse = matrix;
for (let k = 0; k < sparse.values.length; k++) {
const row = sparse.rowIndices[k];
const col = sparse.colIndices[k];
const val = sparse.values[k];
result[row] += val * vector[col];
}
}
return result;
}
static getEntry(matrix, row, col) {
this.validateMatrix(matrix);
if (row < 0 || row >= matrix.rows || col < 0 || col >= matrix.cols) {
throw new SolverError(`Index (${row}, ${col}) out of bounds`, ErrorCodes.INVALID_DIMENSIONS);
}
if (matrix.format === 'dense') {
const dense = matrix;
return dense.data[row][col];
}
else if (matrix.format === 'coo') {
const sparse = matrix;
for (let k = 0; k < sparse.values.length; k++) {
if (sparse.rowIndices[k] === row && sparse.colIndices[k] === col) {
return sparse.values[k];
}
}
return 0; }
return 0;
}
static getDiagonal(matrix, i) {
return this.getEntry(matrix, i, i);
}
static getDiagonalVector(matrix) {
if (matrix.rows !== matrix.cols) {
throw new SolverError('Matrix must be square to extract diagonal', ErrorCodes.INVALID_DIMENSIONS);
}
const diagonal = new Array(matrix.rows);
for (let i = 0; i < matrix.rows; i++) {
diagonal[i] = this.getDiagonal(matrix, i);
}
return diagonal;
}
static getRowSum(matrix, row, excludeDiagonal = false) {
this.validateMatrix(matrix);
if (row < 0 || row >= matrix.rows) {
throw new SolverError(`Row index ${row} out of bounds`, ErrorCodes.INVALID_DIMENSIONS);
}
let sum = 0;
if (matrix.format === 'dense') {
const dense = matrix;
for (let j = 0; j < matrix.cols; j++) {
if (!excludeDiagonal || j !== row) {
sum += Math.abs(dense.data[row][j]);
}
}
}
else if (matrix.format === 'coo') {
const sparse = matrix;
for (let k = 0; k < sparse.values.length; k++) {
if (sparse.rowIndices[k] === row) {
const col = sparse.colIndices[k];
if (!excludeDiagonal || col !== row) {
sum += Math.abs(sparse.values[k]);
}
}
}
}
return sum;
}
static getColumnSum(matrix, col, excludeDiagonal = false) {
this.validateMatrix(matrix);
if (col < 0 || col >= matrix.cols) {
throw new SolverError(`Column index ${col} out of bounds`, ErrorCodes.INVALID_DIMENSIONS);
}
let sum = 0;
if (matrix.format === 'dense') {
const dense = matrix;
for (let i = 0; i < matrix.rows; i++) {
if (!excludeDiagonal || i !== col) {
sum += Math.abs(dense.data[i][col]);
}
}
}
else if (matrix.format === 'coo') {
const sparse = matrix;
for (let k = 0; k < sparse.values.length; k++) {
if (sparse.colIndices[k] === col) {
const row = sparse.rowIndices[k];
if (!excludeDiagonal || row !== col) {
sum += Math.abs(sparse.values[k]);
}
}
}
}
return sum;
}
static checkDiagonalDominance(matrix) {
this.validateMatrix(matrix);
if (matrix.rows !== matrix.cols) {
return { isRowDD: false, isColDD: false, strength: 0 };
}
let isRowDD = true;
let isColDD = true;
let minRowStrength = Infinity;
let minColStrength = Infinity;
for (let i = 0; i < matrix.rows; i++) {
const diagonal = Math.abs(this.getDiagonal(matrix, i));
const rowOffDiagonalSum = this.getRowSum(matrix, i, true);
const colOffDiagonalSum = this.getColumnSum(matrix, i, true);
if (diagonal === 0) {
isRowDD = false;
isColDD = false;
minRowStrength = 0;
minColStrength = 0;
break;
}
const rowStrength = diagonal - rowOffDiagonalSum;
const colStrength = diagonal - colOffDiagonalSum;
if (rowStrength < 0) {
isRowDD = false;
}
else {
minRowStrength = Math.min(minRowStrength, rowStrength / diagonal);
}
if (colStrength < 0) {
isColDD = false;
}
else {
minColStrength = Math.min(minColStrength, colStrength / diagonal);
}
}
const strength = Math.max(isRowDD ? minRowStrength : 0, isColDD ? minColStrength : 0);
return { isRowDD, isColDD, strength };
}
static isSymmetric(matrix, tolerance = 1e-10) {
this.validateMatrix(matrix);
if (matrix.rows !== matrix.cols) {
return false;
}
if (matrix.format === 'dense') {
const dense = matrix;
for (let i = 0; i < matrix.rows; i++) {
for (let j = i + 1; j < matrix.cols; j++) {
if (Math.abs(dense.data[i][j] - dense.data[j][i]) > tolerance) {
return false;
}
}
}
return true;
}
for (let i = 0; i < matrix.rows; i++) {
for (let j = i + 1; j < matrix.cols; j++) {
const entry_ij = this.getEntry(matrix, i, j);
const entry_ji = this.getEntry(matrix, j, i);
if (Math.abs(entry_ij - entry_ji) > tolerance) {
return false;
}
}
}
return true;
}
static calculateSparsity(matrix) {
this.validateMatrix(matrix);
const totalEntries = matrix.rows * matrix.cols;
if (matrix.format === 'dense') {
const dense = matrix;
let nonZeros = 0;
for (let i = 0; i < matrix.rows; i++) {
for (let j = 0; j < matrix.cols; j++) {
if (Math.abs(dense.data[i][j]) > 1e-15) {
nonZeros++;
}
}
}
return 1 - (nonZeros / totalEntries);
}
else if (matrix.format === 'coo') {
const sparse = matrix;
return 1 - (sparse.values.length / totalEntries);
}
return 0;
}
static analyzeMatrix(matrix) {
this.validateMatrix(matrix);
const dominance = this.checkDiagonalDominance(matrix);
const isSymmetric = this.isSymmetric(matrix);
const sparsity = this.calculateSparsity(matrix);
let dominanceType = 'none';
if (dominance.isRowDD && dominance.isColDD) {
dominanceType = 'row'; }
else if (dominance.isRowDD) {
dominanceType = 'row';
}
else if (dominance.isColDD) {
dominanceType = 'column';
}
return {
isDiagonallyDominant: dominance.isRowDD || dominance.isColDD,
dominanceType,
dominanceStrength: dominance.strength,
isSymmetric,
sparsity,
size: { rows: matrix.rows, cols: matrix.cols }
};
}
static denseToSparse(dense, tolerance = 1e-15) {
const values = [];
const rowIndices = [];
const colIndices = [];
for (let i = 0; i < dense.rows; i++) {
for (let j = 0; j < dense.cols; j++) {
const value = dense.data[i][j];
if (Math.abs(value) > tolerance) {
values.push(value);
rowIndices.push(i);
colIndices.push(j);
}
}
}
return {
rows: dense.rows,
cols: dense.cols,
values,
rowIndices,
colIndices,
format: 'coo'
};
}
static sparseToDense(sparse) {
const data = Array(sparse.rows).fill(null).map(() => Array(sparse.cols).fill(0));
for (let k = 0; k < sparse.values.length; k++) {
const row = sparse.rowIndices[k];
const col = sparse.colIndices[k];
const val = sparse.values[k];
data[row][col] = val;
}
return {
rows: sparse.rows,
cols: sparse.cols,
data,
format: 'dense'
};
}
}