mirror of
https://github.com/ruvnet/RuView
synced 2026-08-10 20:31:42 +00:00
feat: complete vendor repos, add edge intelligence and WASM modules
- Add 154 missing vendor files (gitignore was filtering them) - vendor/midstream: 564 files (was 561) - vendor/sublinear-time-solver: 1190 files (was 1039) - Add ESP32 edge processing (ADR-039): presence, vitals, fall detection - Add WASM programmable sensing (ADR-040/041) with wasm3 runtime - Add firmware CI workflow (.github/workflows/firmware-ci.yml) - Add wifi-densepose-wasm-edge crate for edge WASM modules - Update sensing server, provision.py, UI components Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
+348
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Core matrix operations for sublinear-time solvers
|
||||
*/
|
||||
import { SolverError, ErrorCodes } from './types.js';
|
||||
export class MatrixOperations {
|
||||
/**
|
||||
* Validates matrix format and properties
|
||||
*/
|
||||
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);
|
||||
}
|
||||
// Check indices are valid
|
||||
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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Matrix-vector multiplication: result = matrix * vector
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Get matrix entry at (row, col)
|
||||
*/
|
||||
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; // Implicit zero
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/**
|
||||
* Get diagonal entry at position i
|
||||
*/
|
||||
static getDiagonal(matrix, i) {
|
||||
return this.getEntry(matrix, i, i);
|
||||
}
|
||||
/**
|
||||
* Extract diagonal as vector
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Get row sum for diagonal dominance check
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Get column sum for diagonal dominance check
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Check if matrix is diagonally dominant
|
||||
*/
|
||||
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 };
|
||||
}
|
||||
/**
|
||||
* Check if matrix is symmetric
|
||||
*/
|
||||
static isSymmetric(matrix, tolerance = 1e-10) {
|
||||
this.validateMatrix(matrix);
|
||||
if (matrix.rows !== matrix.cols) {
|
||||
return false;
|
||||
}
|
||||
// For sparse matrices, this is more complex - we'd need to compare all entries
|
||||
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 sparse matrices, check symmetry by comparing entries
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Calculate sparsity ratio (fraction of zero entries)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Analyze matrix properties
|
||||
*/
|
||||
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'; // Prefer row if both
|
||||
}
|
||||
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 }
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert dense matrix to COO sparse format
|
||||
*/
|
||||
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'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert COO sparse matrix to dense format
|
||||
*/
|
||||
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'
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user