mirror of
https://github.com/ruvnet/RuView
synced 2026-08-08 20:11:43 +00:00
feat: vendor midstream and sublinear-time-solver libraries
Add ruvnet/midstream (AIMDS real-time inference) and ruvnet/sublinear-time-solver (sublinear optimization algorithms) as vendored dependencies under vendor/. Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
/**
|
||||
* Convergence Detection and Metrics Validation Test Suite
|
||||
*
|
||||
* Tests the convergence detection system against known test cases
|
||||
* with expected convergence behavior.
|
||||
*/
|
||||
|
||||
const { ConvergenceDetector } = require('../../src/convergence/convergence-detector');
|
||||
const { MetricsReporter } = require('../../src/convergence/metrics-reporter');
|
||||
const { createSolver } = require('../../src/solver');
|
||||
|
||||
class ConvergenceValidator {
|
||||
constructor() {
|
||||
this.testCases = this.generateTestCases();
|
||||
this.results = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate test cases with known convergence properties
|
||||
*/
|
||||
generateTestCases() {
|
||||
return [
|
||||
{
|
||||
name: 'Well-conditioned Diagonal Matrix',
|
||||
description: 'Identity matrix should converge in 1 iteration',
|
||||
matrix: this.createIdentityMatrix(10),
|
||||
rhs: Array(10).fill(1),
|
||||
expectedIterations: 1,
|
||||
expectedConvergence: true,
|
||||
expectedRate: 0.0,
|
||||
tolerance: 1e-10
|
||||
},
|
||||
{
|
||||
name: 'Simple Diagonal Matrix',
|
||||
description: 'Diagonal matrix with 2s on diagonal',
|
||||
matrix: this.createDiagonalMatrix(5, 2.0),
|
||||
rhs: [2, 4, 6, 8, 10],
|
||||
expectedIterations: 1,
|
||||
expectedConvergence: true,
|
||||
expectedRate: 0.0,
|
||||
tolerance: 1e-10
|
||||
},
|
||||
{
|
||||
name: 'Strongly Diagonal Dominant',
|
||||
description: 'Matrix with strong diagonal dominance',
|
||||
matrix: this.createStronglyDiagonalDominant(8),
|
||||
rhs: Array(8).fill(1),
|
||||
expectedIterations: { min: 1, max: 10 },
|
||||
expectedConvergence: true,
|
||||
expectedRate: { min: 0.0, max: 0.3 },
|
||||
tolerance: 1e-8
|
||||
},
|
||||
{
|
||||
name: 'Weakly Diagonal Dominant',
|
||||
description: 'Matrix with weak diagonal dominance',
|
||||
matrix: this.createWeaklyDiagonalDominant(6),
|
||||
rhs: Array(6).fill(1),
|
||||
expectedIterations: { min: 10, max: 100 },
|
||||
expectedConvergence: true,
|
||||
expectedRate: { min: 0.3, max: 0.9 },
|
||||
tolerance: 1e-6
|
||||
},
|
||||
{
|
||||
name: 'Symmetric Positive Definite',
|
||||
description: 'Well-conditioned SPD matrix',
|
||||
matrix: this.createSPDMatrix(5),
|
||||
rhs: [1, 2, 3, 4, 5],
|
||||
expectedIterations: { min: 1, max: 20 },
|
||||
expectedConvergence: true,
|
||||
expectedRate: { min: 0.0, max: 0.5 },
|
||||
tolerance: 1e-8
|
||||
},
|
||||
{
|
||||
name: 'Near-singular Matrix',
|
||||
description: 'Poorly conditioned matrix',
|
||||
matrix: this.createNearSingularMatrix(4),
|
||||
rhs: [1, 1, 1, 1],
|
||||
expectedIterations: { min: 50, max: 1000 },
|
||||
expectedConvergence: false, // May not converge
|
||||
expectedRate: { min: 0.8, max: 1.0 },
|
||||
tolerance: 1e-4,
|
||||
maxIterations: 200
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all validation tests
|
||||
*/
|
||||
async runValidation() {
|
||||
console.log('🧪 Running Convergence Validation Tests');
|
||||
console.log('=' .repeat(60));
|
||||
|
||||
for (const testCase of this.testCases) {
|
||||
console.log(`\n📋 Test: ${testCase.name}`);
|
||||
console.log(` ${testCase.description}`);
|
||||
|
||||
try {
|
||||
const result = await this.runSingleTest(testCase);
|
||||
this.results.push(result);
|
||||
|
||||
this.printTestResult(result);
|
||||
} catch (error) {
|
||||
console.log(` ❌ ERROR: ${error.message}`);
|
||||
this.results.push({
|
||||
testCase: testCase.name,
|
||||
passed: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.printSummary();
|
||||
return this.results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single test case
|
||||
*/
|
||||
async runSingleTest(testCase) {
|
||||
const solver = await createSolver({
|
||||
matrix: testCase.matrix,
|
||||
method: 'jacobi',
|
||||
tolerance: testCase.tolerance,
|
||||
maxIterations: testCase.maxIterations || 1000,
|
||||
verbose: false
|
||||
});
|
||||
|
||||
const result = await solver.solve(testCase.rhs);
|
||||
|
||||
// Validate convergence behavior
|
||||
const validation = this.validateResult(result, testCase);
|
||||
|
||||
return {
|
||||
testCase: testCase.name,
|
||||
expected: testCase,
|
||||
actual: {
|
||||
iterations: result.iterations,
|
||||
converged: result.converged,
|
||||
convergenceRate: result.convergenceRate,
|
||||
residual: result.residual,
|
||||
reductionFactor: result.reductionFactor,
|
||||
grade: result.performanceGrade
|
||||
},
|
||||
validation,
|
||||
passed: validation.overall
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate result against expected behavior
|
||||
*/
|
||||
validateResult(result, testCase) {
|
||||
const checks = {
|
||||
convergence: this.checkConvergence(result.converged, testCase.expectedConvergence),
|
||||
iterations: this.checkIterations(result.iterations, testCase.expectedIterations),
|
||||
convergenceRate: this.checkConvergenceRate(result.convergenceRate, testCase.expectedRate),
|
||||
residual: this.checkResidual(result.residual, testCase.tolerance),
|
||||
reductionFactor: this.checkReductionFactor(result.reductionFactor)
|
||||
};
|
||||
|
||||
const passedChecks = Object.values(checks).filter(c => c.passed).length;
|
||||
const totalChecks = Object.keys(checks).length;
|
||||
|
||||
return {
|
||||
...checks,
|
||||
overall: passedChecks >= totalChecks - 1, // Allow one check to fail
|
||||
score: `${passedChecks}/${totalChecks}`
|
||||
};
|
||||
}
|
||||
|
||||
checkConvergence(actual, expected) {
|
||||
const passed = actual === expected;
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Convergence as expected' : `✗ Expected ${expected}, got ${actual}`
|
||||
};
|
||||
}
|
||||
|
||||
checkIterations(actual, expected) {
|
||||
if (typeof expected === 'number') {
|
||||
const passed = actual === expected;
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Iterations as expected' : `✗ Expected ${expected}, got ${actual}`
|
||||
};
|
||||
} else {
|
||||
const passed = actual >= expected.min && actual <= expected.max;
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Iterations in range' : `✗ Expected ${expected.min}-${expected.max}, got ${actual}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
checkConvergenceRate(actual, expected) {
|
||||
if (typeof expected === 'number') {
|
||||
const passed = Math.abs(actual - expected) < 0.1;
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Convergence rate as expected' : `✗ Expected ~${expected}, got ${actual}`
|
||||
};
|
||||
} else {
|
||||
const passed = actual >= expected.min && actual <= expected.max;
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Convergence rate in range' : `✗ Expected ${expected.min}-${expected.max}, got ${actual.toFixed(3)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
checkResidual(actual, tolerance) {
|
||||
const passed = actual <= tolerance * 10; // Allow some tolerance slack
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Residual acceptable' : `✗ Residual ${actual.toExponential(2)} too large`
|
||||
};
|
||||
}
|
||||
|
||||
checkReductionFactor(actual) {
|
||||
const passed = actual >= 0 && actual <= 1.0;
|
||||
return {
|
||||
passed,
|
||||
message: passed ? '✓ Reduction factor valid' : `✗ Invalid reduction factor ${actual}`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Print individual test result
|
||||
*/
|
||||
printTestResult(result) {
|
||||
const status = result.passed ? '✅ PASS' : '❌ FAIL';
|
||||
console.log(` ${status} (${result.validation.score})`);
|
||||
|
||||
if (result.passed) {
|
||||
console.log(` Iterations: ${result.actual.iterations}, Convergence: ${result.actual.convergenceRate.toFixed(1)}%`);
|
||||
console.log(` Grade: ${result.actual.grade}, Reduction: ${result.actual.reductionFactor.toExponential(2)}`);
|
||||
} else {
|
||||
console.log(' Issues:');
|
||||
Object.entries(result.validation).forEach(([key, check]) => {
|
||||
if (key !== 'overall' && key !== 'score' && !check.passed) {
|
||||
console.log(` ${check.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print validation summary
|
||||
*/
|
||||
printSummary() {
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('\n📊 VALIDATION SUMMARY');
|
||||
|
||||
const passed = this.results.filter(r => r.passed).length;
|
||||
const total = this.results.length;
|
||||
const percentage = (passed / total * 100).toFixed(1);
|
||||
|
||||
console.log(`\nOverall: ${passed}/${total} tests passed (${percentage}%)`);
|
||||
|
||||
if (passed === total) {
|
||||
console.log('🎉 All convergence validation tests passed!');
|
||||
console.log('✓ Convergence detection is working correctly');
|
||||
console.log('✓ Metrics reporting is accurate');
|
||||
console.log('✓ Early stopping is functioning');
|
||||
} else {
|
||||
console.log('⚠️ Some tests failed - convergence system needs attention');
|
||||
|
||||
const failed = this.results.filter(r => !r.passed);
|
||||
console.log('\nFailed tests:');
|
||||
failed.forEach(f => {
|
||||
console.log(` - ${f.testCase}: ${f.error || 'Validation failed'}`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
}
|
||||
|
||||
// Matrix generation utilities
|
||||
|
||||
createIdentityMatrix(size) {
|
||||
const matrix = Array(size).fill(0).map(() => Array(size).fill(0));
|
||||
for (let i = 0; i < size; i++) {
|
||||
matrix[i][i] = 1.0;
|
||||
}
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
|
||||
createDiagonalMatrix(size, diagonalValue) {
|
||||
const matrix = Array(size).fill(0).map(() => Array(size).fill(0));
|
||||
for (let i = 0; i < size; i++) {
|
||||
matrix[i][i] = diagonalValue;
|
||||
}
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
|
||||
createStronglyDiagonalDominant(size) {
|
||||
const matrix = Array(size).fill(0).map(() => Array(size).fill(0));
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
let rowSum = 0;
|
||||
|
||||
// Add off-diagonal elements
|
||||
for (let j = 0; j < size; j++) {
|
||||
if (i !== j) {
|
||||
const value = (Math.random() - 0.5) * 0.2; // Small off-diagonal elements
|
||||
matrix[i][j] = value;
|
||||
rowSum += Math.abs(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Set diagonal to be much larger than row sum
|
||||
matrix[i][i] = rowSum * 3 + 2.0;
|
||||
}
|
||||
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
|
||||
createWeaklyDiagonalDominant(size) {
|
||||
const matrix = Array(size).fill(0).map(() => Array(size).fill(0));
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
let rowSum = 0;
|
||||
|
||||
// Add larger off-diagonal elements
|
||||
for (let j = 0; j < size; j++) {
|
||||
if (i !== j) {
|
||||
const value = (Math.random() - 0.5) * 0.8; // Larger off-diagonal elements
|
||||
matrix[i][j] = value;
|
||||
rowSum += Math.abs(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Set diagonal to barely dominate
|
||||
matrix[i][i] = rowSum + 0.1;
|
||||
}
|
||||
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
|
||||
createSPDMatrix(size) {
|
||||
// Create A = B^T * B + I to ensure SPD
|
||||
const B = Array(size).fill(0).map(() =>
|
||||
Array(size).fill(0).map(() => Math.random() - 0.5)
|
||||
);
|
||||
|
||||
const matrix = Array(size).fill(0).map(() => Array(size).fill(0));
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
for (let j = 0; j < size; j++) {
|
||||
let sum = 0;
|
||||
for (let k = 0; k < size; k++) {
|
||||
sum += B[k][i] * B[k][j];
|
||||
}
|
||||
matrix[i][j] = sum;
|
||||
if (i === j) matrix[i][j] += 1.0; // Add identity for positive definiteness
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
|
||||
createNearSingularMatrix(size) {
|
||||
const matrix = Array(size).fill(0).map(() => Array(size).fill(0));
|
||||
|
||||
// Create a matrix with very small singular values
|
||||
for (let i = 0; i < size; i++) {
|
||||
for (let j = 0; j < size; j++) {
|
||||
matrix[i][j] = Math.random() * 0.1;
|
||||
}
|
||||
// Set diagonal to be barely non-zero
|
||||
matrix[i][i] = 0.001 + Math.random() * 0.01;
|
||||
}
|
||||
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in tests
|
||||
module.exports = { ConvergenceValidator };
|
||||
|
||||
// Run validation if called directly
|
||||
if (require.main === module) {
|
||||
const validator = new ConvergenceValidator();
|
||||
validator.runValidation().then(results => {
|
||||
const passed = results.filter(r => r.passed).length;
|
||||
process.exit(passed === results.length ? 0 : 1);
|
||||
}).catch(error => {
|
||||
console.error('Validation failed:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
const { createSolver } = require('../../src/solver.js');
|
||||
|
||||
// Generate a simple diagonally dominant matrix
|
||||
function generateTestMatrix(size) {
|
||||
const matrix = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
const row = new Array(size).fill(0);
|
||||
|
||||
// Add some off-diagonal elements
|
||||
let rowSum = 0;
|
||||
for (let j = 0; j < size; j++) {
|
||||
if (i !== j) {
|
||||
const value = (Math.random() - 0.5) * 0.3;
|
||||
row[j] = value;
|
||||
rowSum += Math.abs(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure diagonal dominance
|
||||
row[i] = rowSum + 1.0 + Math.random();
|
||||
matrix.push(row);
|
||||
}
|
||||
|
||||
return {
|
||||
data: matrix,
|
||||
rows: size,
|
||||
cols: size,
|
||||
format: 'dense'
|
||||
};
|
||||
}
|
||||
|
||||
async function runMiniBenchmark() {
|
||||
console.log('🧪 Running Mini Convergence Benchmark');
|
||||
console.log('=' .repeat(50));
|
||||
|
||||
const methods = ['jacobi', 'conjugate_gradient'];
|
||||
const sizes = [5, 10];
|
||||
|
||||
for (const method of methods) {
|
||||
console.log(`\n📊 Testing ${method.toUpperCase()}:`);
|
||||
|
||||
for (const size of sizes) {
|
||||
console.log(`\n Size ${size}x${size}:`);
|
||||
|
||||
try {
|
||||
const matrix = generateTestMatrix(size);
|
||||
const b = Array.from({ length: size }, () => Math.random() * 10);
|
||||
|
||||
const solver = await createSolver({
|
||||
matrix: matrix,
|
||||
method: method,
|
||||
tolerance: 1e-8,
|
||||
maxIterations: 100,
|
||||
verbose: false
|
||||
});
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = await solver.solve(b);
|
||||
const endTime = Date.now();
|
||||
|
||||
console.log(` ✅ Converged: ${result.converged}`);
|
||||
console.log(` 📈 Iterations: ${result.iterations}`);
|
||||
console.log(` 🎯 Convergence Rate: ${result.convergenceRate?.toFixed(1)}%`);
|
||||
console.log(` 📊 Grade: ${result.performanceGrade}`);
|
||||
console.log(` ⏱️ Time: ${endTime - startTime}ms`);
|
||||
console.log(` 🔬 Residual: ${result.residual?.toExponential(3)}`);
|
||||
console.log(` 📉 Reduction: ${result.reductionFactor?.toExponential(3)}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ Failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('🎉 Mini benchmark completed!');
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
runMiniBenchmark().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = { runMiniBenchmark };
|
||||
@@ -0,0 +1,56 @@
|
||||
const { createSolver } = require('../../src/solver.js');
|
||||
|
||||
async function quickTest() {
|
||||
console.log('Running quick convergence test...');
|
||||
|
||||
// Create a simple 3x3 diagonal matrix
|
||||
const matrix = {
|
||||
data: [
|
||||
[2, 0, 0],
|
||||
[0, 3, 0],
|
||||
[0, 0, 4]
|
||||
],
|
||||
rows: 3,
|
||||
cols: 3,
|
||||
format: 'dense'
|
||||
};
|
||||
|
||||
const b = [2, 6, 12]; // Should give solution [1, 2, 3]
|
||||
|
||||
try {
|
||||
const solver = await createSolver({
|
||||
matrix: matrix,
|
||||
method: 'jacobi',
|
||||
tolerance: 1e-10,
|
||||
maxIterations: 100,
|
||||
verbose: true
|
||||
});
|
||||
|
||||
const result = await solver.solve(b);
|
||||
|
||||
console.log('Results:');
|
||||
console.log(' Solution:', result.values.map(x => x.toFixed(3)));
|
||||
console.log(' Iterations:', result.iterations);
|
||||
console.log(' Converged:', result.converged);
|
||||
console.log(' Convergence Rate:', result.convergenceRate?.toFixed(1) + '%');
|
||||
console.log(' Performance Grade:', result.performanceGrade);
|
||||
console.log(' Residual:', result.residual?.toExponential(3));
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
quickTest().then(() => {
|
||||
console.log('✅ Quick test passed!');
|
||||
process.exit(0);
|
||||
}).catch(error => {
|
||||
console.error('❌ Quick test failed:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { quickTest };
|
||||
Reference in New Issue
Block a user