/** * Utility functions for sublinear-time solvers */ import { SolverError, ErrorCodes } from './types.js'; export class VectorOperations { /** * Vector addition: result = a + b */ static add(a, b) { if (a.length !== b.length) { throw new SolverError(`Vector dimensions don't match: ${a.length} vs ${b.length}`, ErrorCodes.INVALID_DIMENSIONS); } return a.map((val, i) => val + b[i]); } /** * Vector subtraction: result = a - b */ static subtract(a, b) { if (a.length !== b.length) { throw new SolverError(`Vector dimensions don't match: ${a.length} vs ${b.length}`, ErrorCodes.INVALID_DIMENSIONS); } return a.map((val, i) => val - b[i]); } /** * Scalar multiplication: result = scalar * vector */ static scale(vector, scalar) { return vector.map(val => val * scalar); } /** * Dot product of two vectors */ static dot(a, b) { if (a.length !== b.length) { throw new SolverError(`Vector dimensions don't match: ${a.length} vs ${b.length}`, ErrorCodes.INVALID_DIMENSIONS); } return a.reduce((sum, val, i) => sum + val * b[i], 0); } /** * L2 norm of vector */ static norm2(vector) { return Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0)); } /** * L1 norm of vector */ static norm1(vector) { return vector.reduce((sum, val) => sum + Math.abs(val), 0); } /** * L-infinity norm of vector */ static normInf(vector) { return Math.max(...vector.map(Math.abs)); } /** * Create zero vector of specified length */ static zeros(length) { return new Array(length).fill(0); } /** * Create vector filled with ones */ static ones(length) { return new Array(length).fill(1); } /** * Create random vector with values in [0, 1) */ static random(length, seed) { const rng = seed !== undefined ? createSeededRandom(seed) : Math.random; return Array.from({ length }, () => rng()); } /** * Normalize vector to unit length */ static normalize(vector) { const norm = this.norm2(vector); if (norm === 0) { throw new SolverError('Cannot normalize zero vector', ErrorCodes.NUMERICAL_INSTABILITY); } return this.scale(vector, 1 / norm); } /** * Element-wise multiplication */ static elementwiseMultiply(a, b) { if (a.length !== b.length) { throw new SolverError(`Vector dimensions don't match: ${a.length} vs ${b.length}`, ErrorCodes.INVALID_DIMENSIONS); } return a.map((val, i) => val * b[i]); } /** * Element-wise division */ static elementwiseDivide(a, b) { if (a.length !== b.length) { throw new SolverError(`Vector dimensions don't match: ${a.length} vs ${b.length}`, ErrorCodes.INVALID_DIMENSIONS); } return a.map((val, i) => { if (Math.abs(b[i]) < 1e-15) { throw new SolverError(`Division by zero at index ${i}`, ErrorCodes.NUMERICAL_INSTABILITY); } return val / b[i]; }); } /** * Check if vectors are approximately equal */ static isEqual(a, b, tolerance = 1e-10) { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (Math.abs(a[i] - b[i]) > tolerance) { return false; } } return true; } /** * Linear interpolation between two vectors */ static lerp(a, b, t) { if (a.length !== b.length) { throw new SolverError(`Vector dimensions don't match: ${a.length} vs ${b.length}`, ErrorCodes.INVALID_DIMENSIONS); } return a.map((val, i) => val + t * (b[i] - val)); } } /** * Create a seeded random number generator */ export function createSeededRandom(seed) { let state = seed; return function () { // Simple linear congruential generator state = (state * 1664525 + 1013904223) % 0x100000000; return state / 0x100000000; }; } /** * Performance monitoring utilities */ export class PerformanceMonitor { startTime; memoryStart; constructor() { this.startTime = Date.now(); this.memoryStart = this.getMemoryUsage(); } /** * Get elapsed time in milliseconds */ getElapsedTime() { return Date.now() - this.startTime; } /** * Get memory usage in MB */ getMemoryUsage() { if (typeof process !== 'undefined' && process.memoryUsage) { const usage = process.memoryUsage(); return Math.round(usage.heapUsed / 1024 / 1024); } return 0; } /** * Get memory increase since start */ getMemoryIncrease() { return this.getMemoryUsage() - this.memoryStart; } /** * Reset timer and memory baseline */ reset() { this.startTime = Date.now(); this.memoryStart = this.getMemoryUsage(); } } /** * Convergence checking utilities */ export class ConvergenceChecker { history = []; maxHistory; constructor(maxHistory = 10) { this.maxHistory = maxHistory; } /** * Add residual to history and check convergence */ checkConvergence(residual, tolerance) { this.history.push(residual); if (this.history.length > this.maxHistory) { this.history.shift(); } const converged = residual < tolerance; let rate = 1.0; let trend = 'improving'; if (this.history.length >= 2) { const recent = this.history.slice(-2); rate = recent[1] / recent[0]; if (rate < 0.95) { trend = 'improving'; } else if (rate > 1.05) { trend = 'diverging'; } else { trend = 'stagnant'; } } return { converged, rate, trend }; } /** * Get average convergence rate over history */ getAverageRate() { if (this.history.length < 2) { return 1.0; } let totalRate = 0; let count = 0; for (let i = 1; i < this.history.length; i++) { if (this.history[i - 1] > 0) { totalRate += this.history[i] / this.history[i - 1]; count++; } } return count > 0 ? totalRate / count : 1.0; } /** * Clear convergence history */ reset() { this.history = []; } } /** * Timeout utility */ export class TimeoutController { startTime; timeoutMs; constructor(timeoutMs) { this.startTime = Date.now(); this.timeoutMs = timeoutMs; } /** * Check if timeout has been exceeded */ isExpired() { return Date.now() - this.startTime > this.timeoutMs; } /** * Get remaining time in milliseconds */ remainingTime() { return Math.max(0, this.timeoutMs - (Date.now() - this.startTime)); } /** * Throw timeout error if expired */ checkTimeout() { if (this.isExpired()) { throw new SolverError(`Operation timed out after ${this.timeoutMs}ms`, ErrorCodes.TIMEOUT); } } } /** * Validation utilities */ export class ValidationUtils { /** * Validate that value is a finite number */ static validateFiniteNumber(value, name) { if (!Number.isFinite(value)) { throw new SolverError(`${name} must be a finite number, got ${value}`, ErrorCodes.INVALID_PARAMETERS); } } /** * Validate that value is a positive number */ static validatePositiveNumber(value, name) { this.validateFiniteNumber(value, name); if (value <= 0) { throw new SolverError(`${name} must be positive, got ${value}`, ErrorCodes.INVALID_PARAMETERS); } } /** * Validate that value is a non-negative number */ static validateNonNegativeNumber(value, name) { this.validateFiniteNumber(value, name); if (value < 0) { throw new SolverError(`${name} must be non-negative, got ${value}`, ErrorCodes.INVALID_PARAMETERS); } } /** * Validate that value is within range [min, max] */ static validateRange(value, min, max, name) { this.validateFiniteNumber(value, name); if (value < min || value > max) { throw new SolverError(`${name} must be between ${min} and ${max}, got ${value}`, ErrorCodes.INVALID_PARAMETERS); } } /** * Validate that integer is within range [min, max] */ static validateIntegerRange(value, min, max, name) { if (!Number.isInteger(value)) { throw new SolverError(`${name} must be an integer, got ${value}`, ErrorCodes.INVALID_PARAMETERS); } this.validateRange(value, min, max, name); } }