#!/usr/bin/env node const { createSolver, JSSolver } = require('../src/solver.js'); const { MatrixUtils } = require('../src/utils/matrix-utils.js'); /** * Comprehensive test suite for solver fixes */ async function runSolverFixTests() { console.log('๐Ÿงช Comprehensive Solver Fix Test Suite'); console.log('=====================================\n'); let totalTests = 0; let passedTests = 0; const results = []; // Test Case 1: Auto-fix diagonal issues console.log('Test 1: Auto-fix missing diagonal elements'); console.log('-'.repeat(45)); try { totalTests++; // Create matrix with missing diagonal const problematicMatrix = { rows: 3, cols: 3, format: 'coo', entries: 5, data: { rowIndices: [0, 0, 1, 2, 2], colIndices: [1, 2, 2, 0, 1], values: [1, -1, 2, -1, 1] } }; const vector = [1, 2, 3]; // Should auto-fix the matrix const solver = await createSolver({ matrix: problematicMatrix, method: 'jacobi', tolerance: 1e-8, maxIterations: 100, autoFixMatrix: true, verbose: true }); const result = await solver.solve(vector); if (result.converged) { console.log('โœ… PASS: Auto-fix enabled successful convergence'); passedTests++; results.push({ test: 'Auto-fix diagonal', status: 'PASS', details: `Converged in ${result.iterations} iterations` }); } else { console.log('โŒ FAIL: Auto-fix did not achieve convergence'); results.push({ test: 'Auto-fix diagonal', status: 'FAIL', details: `Did not converge after ${result.iterations} iterations` }); } } catch (error) { console.log(`โŒ FAIL: Auto-fix test error: ${error.message}`); results.push({ test: 'Auto-fix diagonal', status: 'FAIL', details: error.message }); } console.log(); // Test Case 2: Well-conditioned matrix generation console.log('Test 2: Well-conditioned matrix generation'); console.log('-'.repeat(45)); try { totalTests++; for (const size of [50, 100, 200]) { console.log(` Testing ${size}ร—${size} matrix...`); const matrix = MatrixUtils.generateWellConditionedSparseMatrix(size, 0.05, { diagonalStrategy: 'rowsum_plus_one', ensureDominance: true }); const conditioning = MatrixUtils.analyzeConditioning(matrix); if (conditioning.isWellConditioned && conditioning.isDiagonallyDominant) { console.log(` โœ… Size ${size}: Grade ${conditioning.conditioningGrade}, dominance ratio ${conditioning.diagonalDominanceRatio.toFixed(3)}`); } else { console.log(` โŒ Size ${size}: Poor conditioning (Grade ${conditioning.conditioningGrade})`); throw new Error(`Poor conditioning for size ${size}`); } } console.log('โœ… PASS: All matrix sizes well-conditioned'); passedTests++; results.push({ test: 'Well-conditioned generation', status: 'PASS', details: 'All sizes passed conditioning checks' }); } catch (error) { console.log(`โŒ FAIL: Matrix generation test error: ${error.message}`); results.push({ test: 'Well-conditioned generation', status: 'FAIL', details: error.message }); } console.log(); // Test Case 3: Convergence rate testing console.log('Test 3: Convergence rate analysis'); console.log('-'.repeat(45)); try { totalTests++; const testConfigs = [ { size: 50, sparsity: 0.05, method: 'jacobi', matrixType: 'general' }, { size: 50, sparsity: 0.05, method: 'gauss-seidel', matrixType: 'general' }, { size: 50, sparsity: 0.05, method: 'conjugate-gradient', matrixType: 'symmetric' }, { size: 100, sparsity: 0.03, method: 'jacobi', matrixType: 'general' }, { size: 100, sparsity: 0.03, method: 'gauss-seidel', matrixType: 'general' }, { size: 100, sparsity: 0.03, method: 'conjugate-gradient', matrixType: 'symmetric' } ]; let convergenceCount = 0; const convergenceResults = []; for (const config of testConfigs) { console.log(` Testing ${config.method} on ${config.size}ร—${config.size} ${config.matrixType} matrix...`); const matrix = config.matrixType === 'symmetric' ? MatrixUtils.generateSymmetricPositiveDefiniteMatrix(config.size, config.sparsity) : MatrixUtils.generateWellConditionedSparseMatrix(config.size, config.sparsity); const vector = Array.from({ length: config.size }, () => Math.random() * 10 - 5); const solver = await createSolver({ matrix, method: config.method, tolerance: 1e-8, maxIterations: 500, verbose: false }); const result = await solver.solve(vector); const testResult = { ...config, converged: result.converged, iterations: result.iterations, residual: result.residual }; convergenceResults.push(testResult); if (result.converged) { convergenceCount++; console.log(` โœ… Converged in ${result.iterations} iterations (residual: ${result.residual.toExponential(2)})`); } else { console.log(` โŒ Failed to converge (residual: ${result.residual.toExponential(2)})`); } } const convergenceRate = (convergenceCount / testConfigs.length) * 100; console.log(`\nOverall convergence rate: ${convergenceRate.toFixed(1)}%`); if (convergenceRate >= 90) { console.log('โœ… PASS: Convergence rate โ‰ฅ 90%'); passedTests++; results.push({ test: 'Convergence rate', status: 'PASS', details: `${convergenceRate.toFixed(1)}% convergence rate` }); } else { console.log('โŒ FAIL: Convergence rate < 90%'); results.push({ test: 'Convergence rate', status: 'FAIL', details: `Only ${convergenceRate.toFixed(1)}% convergence rate` }); } } catch (error) { console.log(`โŒ FAIL: Convergence rate test error: ${error.message}`); results.push({ test: 'Convergence rate', status: 'FAIL', details: error.message }); } console.log(); // Test Case 4: Validation and error handling console.log('Test 4: Enhanced validation and error handling'); console.log('-'.repeat(45)); try { totalTests++; // Test that invalid matrices are properly detected const invalidMatrices = [ { name: "Missing diagonal with autoFix disabled", matrix: { rows: 3, cols: 3, format: 'coo', entries: 3, data: { rowIndices: [0, 1, 2], colIndices: [1, 2, 0], values: [1, 1, 1] } }, shouldFail: true, autoFix: false }, { name: "Zero diagonal elements", matrix: { rows: 2, cols: 2, format: 'dense', data: [[0, 1], [1, 2]] }, shouldFail: true, autoFix: false } ]; let validationTestsPassed = 0; for (const test of invalidMatrices) { try { const solver = await createSolver({ matrix: test.matrix, method: 'jacobi', autoFixMatrix: test.autoFix, verbose: false }); const result = await solver.solve([1, 1]); if (test.shouldFail) { console.log(` โŒ ${test.name}: Should have failed but didn't`); } else { console.log(` โœ… ${test.name}: Passed as expected`); validationTestsPassed++; } } catch (error) { if (test.shouldFail) { console.log(` โœ… ${test.name}: Correctly failed with: ${error.message.slice(0, 50)}...`); validationTestsPassed++; } else { console.log(` โŒ ${test.name}: Unexpectedly failed with: ${error.message}`); } } } if (validationTestsPassed === invalidMatrices.length) { console.log('โœ… PASS: All validation tests behaved correctly'); passedTests++; results.push({ test: 'Validation handling', status: 'PASS', details: 'All validation cases handled correctly' }); } else { console.log(`โŒ FAIL: ${validationTestsPassed}/${invalidMatrices.length} validation tests passed`); results.push({ test: 'Validation handling', status: 'FAIL', details: `Only ${validationTestsPassed}/${invalidMatrices.length} passed` }); } } catch (error) { console.log(`โŒ FAIL: Validation test error: ${error.message}`); results.push({ test: 'Validation handling', status: 'FAIL', details: error.message }); } console.log(); // Test Case 5: Performance with large matrices console.log('Test 5: Performance with larger matrices'); console.log('-'.repeat(45)); try { totalTests++; const largeMatrix = MatrixUtils.generateWellConditionedSparseMatrix(500, 0.02); const largeVector = Array.from({ length: 500 }, () => Math.random() * 5); console.log(` Testing 500ร—500 matrix (${largeMatrix.entries} non-zeros)...`); const startTime = Date.now(); const solver = await createSolver({ matrix: largeMatrix, method: 'jacobi', tolerance: 1e-6, maxIterations: 1000, verbose: false }); const result = await solver.solve(largeVector); const elapsed = Date.now() - startTime; console.log(` Solve time: ${elapsed}ms`); console.log(` Iterations: ${result.iterations}`); console.log(` Converged: ${result.converged ? 'Yes' : 'No'}`); console.log(` Final residual: ${result.residual.toExponential(2)}`); if (result.converged && elapsed < 10000) { // Should solve within 10 seconds console.log('โœ… PASS: Large matrix solved efficiently'); passedTests++; results.push({ test: 'Large matrix performance', status: 'PASS', details: `Solved in ${elapsed}ms with ${result.iterations} iterations` }); } else { console.log('โŒ FAIL: Large matrix performance unsatisfactory'); results.push({ test: 'Large matrix performance', status: 'FAIL', details: `${elapsed}ms, converged: ${result.converged}` }); } } catch (error) { console.log(`โŒ FAIL: Large matrix test error: ${error.message}`); results.push({ test: 'Large matrix performance', status: 'FAIL', details: error.message }); } // Summary console.log('\n' + '='.repeat(60)); console.log('๐ŸŽฏ TEST SUMMARY'); console.log('='.repeat(60)); console.log(`Total tests: ${totalTests}`); console.log(`Passed: ${passedTests}`); console.log(`Failed: ${totalTests - passedTests}`); console.log(`Success rate: ${((passedTests / totalTests) * 100).toFixed(1)}%`); console.log('\nDetailed Results:'); for (const result of results) { const status = result.status === 'PASS' ? 'โœ…' : 'โŒ'; console.log(` ${status} ${result.test}: ${result.details}`); } if (passedTests === totalTests) { console.log('\n๐ŸŽ‰ ALL TESTS PASSED! The Jacobi solver fixes are working correctly.'); return true; } else { console.log(`\nโš ๏ธ ${totalTests - passedTests} tests failed. Review the fixes.`); return false; } } // Run the test suite if (require.main === module) { runSolverFixTests() .then(success => { process.exit(success ? 0 : 1); }) .catch(error => { console.error('Fatal test error:', error); process.exit(1); }); } module.exports = { runSolverFixTests };