const { Command } = require('commander');
const chalk = require('chalk');
const ora = require('ora');
const express = require('express');
const fs = require('fs').promises;
const path = require('path');
const { createSolver } = require('../src/solver');
const { SolverServer } = require('../server/index');
const { FlowNexusIntegration } = require('../integrations/flow-nexus');
const program = new Command();
program
.name('sublinear-time-solver')
.description('Advanced Sublinear Time Sparse Linear System Solver')
.version(require('../package.json').version || '1.0.0')
.option('-v, --verbose', 'Enable verbose output')
.option('-q, --quiet', 'Suppress non-essential output')
.option('--debug', 'Enable debug mode');
program
.command('solve')
.description('Solve linear system Ax = b')
.requiredOption('-m, --matrix <file>', 'Input matrix file (JSON, CSV, MTX)')
.option('-b, --vector <file>', 'Right-hand side vector file')
.option('-o, --output <file>', 'Output solution file')
.option('--method <name>', 'Solver method (jacobi|gauss-seidel|cg|hybrid)', 'adaptive')
.option('--tolerance <value>', 'Convergence tolerance', '1e-10')
.option('--max-iterations <n>', 'Maximum iterations', '1000')
.option('--streaming', 'Enable streaming output')
.option('--verify', 'Enable solution verification')
.action(async (options) => {
const spinner = ora('Loading matrix...').start();
try {
const matrixData = await loadMatrix(options.matrix);
spinner.text = 'Loading vector...';
const vector = options.vector
? await loadVector(options.vector)
: generateRandomVector(matrixData.rows);
spinner.succeed(`Loaded ${matrixData.rows}×${matrixData.cols} system`);
const solver = await createSolver({
matrix: matrixData,
method: options.method,
tolerance: parseFloat(options.tolerance),
maxIterations: parseInt(options.maxIterations),
enableVerification: options.verify
});
if (options.streaming) {
console.log(chalk.blue('🔄 Starting streaming solve...'));
await streamingSolve(solver, vector, options);
} else {
console.log(chalk.blue('🔄 Starting batch solve...'));
await batchSolve(solver, vector, options);
}
} catch (error) {
spinner.fail('Solve failed');
console.error(chalk.red('Error:'), error.message);
if (options.debug) console.error(error.stack);
process.exit(1);
}
});
program
.command('serve')
.description('Start HTTP streaming server')
.option('-p, --port <number>', 'Server port', '3000')
.option('--cors', 'Enable CORS')
.option('--flow-nexus', 'Enable Flow-Nexus integration')
.option('--workers <number>', 'Number of worker threads', '1')
.option('--max-sessions <number>', 'Maximum concurrent sessions', '100')
.option('--auth-token <token>', 'Authentication token for protected endpoints')
.action(async (options) => {
const server = new SolverServer({
port: parseInt(options.port),
cors: options.cors,
workers: parseInt(options.workers),
maxSessions: parseInt(options.maxSessions),
authToken: options.authToken,
flowNexusEnabled: options.flowNexus
});
await server.start();
console.log(chalk.green(`🚀 Solver server running on port ${options.port}`));
console.log(chalk.blue(` REST API: http://localhost:${options.port}/api`));
console.log(chalk.blue(` WebSocket: ws://localhost:${options.port}/ws`));
if (options.flowNexus) {
console.log(chalk.yellow(` Flow-Nexus: Enabled`));
}
});
program
.command('verify')
.description('Verify solution accuracy')
.requiredOption('-m, --matrix <file>', 'Matrix file')
.requiredOption('-x, --solution <file>', 'Solution vector file')
.requiredOption('-b, --vector <file>', 'Right-hand side vector file')
.option('--tolerance <value>', 'Verification tolerance', '1e-8')
.option('--probes <count>', 'Number of random probes', '10')
.action(async (options) => {
const spinner = ora('Loading verification data...').start();
try {
const matrix = await loadMatrix(options.matrix);
const solution = await loadVector(options.solution);
const vector = await loadVector(options.vector);
spinner.text = 'Running verification...';
const result = await verifySolution({
matrix,
solution,
vector,
tolerance: parseFloat(options.tolerance),
probes: parseInt(options.probes)
});
if (result.verified) {
spinner.succeed('Solution verified successfully');
console.log(chalk.green(`✓ Max error: ${result.maxError.toExponential(2)}`));
console.log(chalk.green(`✓ Mean error: ${result.meanError.toExponential(2)}`));
} else {
spinner.fail('Solution verification failed');
console.log(chalk.red(`✗ Max error: ${result.maxError.toExponential(2)}`));
console.log(chalk.red(`✗ Tolerance: ${options.tolerance}`));
}
} catch (error) {
spinner.fail('Verification failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
program
.command('benchmark')
.description('Run performance benchmarks')
.option('--size <number>', 'Matrix size', '1000')
.option('--sparsity <value>', 'Matrix sparsity (0-1)', '0.01')
.option('--methods <list>', 'Comma-separated list of methods', 'jacobi,cg,hybrid')
.option('--iterations <number>', 'Benchmark iterations', '5')
.option('--output <file>', 'Output results to JSON file')
.action(async (options) => {
const spinner = ora('Setting up benchmark...').start();
try {
const results = await runBenchmark({
size: parseInt(options.size),
sparsity: parseFloat(options.sparsity),
methods: options.methods.split(','),
iterations: parseInt(options.iterations)
});
spinner.succeed('Benchmark completed');
console.log(chalk.blue('\n📊 Benchmark Results:'));
results.forEach(result => {
console.log(chalk.white(`\n${result.method}:`));
console.log(` Average time: ${result.avgTime.toFixed(2)}ms`);
console.log(` Min time: ${result.minTime.toFixed(2)}ms`);
console.log(` Max time: ${result.maxTime.toFixed(2)}ms`);
console.log(` Iterations: ${result.avgIterations.toFixed(0)}`);
console.log(` Convergence rate: ${(result.convergenceRate * 100).toFixed(1)}%`);
});
if (options.output) {
await fs.writeFile(options.output, JSON.stringify(results, null, 2));
console.log(chalk.green(`\n📁 Results saved to ${options.output}`));
}
} catch (error) {
spinner.fail('Benchmark failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
program
.command('convert')
.description('Convert between matrix formats')
.requiredOption('-i, --input <file>', 'Input file')
.requiredOption('-o, --output <file>', 'Output file')
.option('--format <type>', 'Output format (json|csv|mtx|binary)', 'json')
.option('--compress', 'Compress output')
.action(async (options) => {
const spinner = ora('Converting matrix format...').start();
try {
await convertMatrix({
input: options.input,
output: options.output,
format: options.format,
compress: options.compress
});
spinner.succeed(`Converted ${options.input} → ${options.output}`);
} catch (error) {
spinner.fail('Conversion failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
program
.command('flow-nexus')
.description('Flow-Nexus platform integration')
.option('--register', 'Register as solver service')
.option('--swarm-join <id>', 'Join swarm with specified ID')
.option('--endpoint <url>', 'Flow-Nexus endpoint URL')
.option('--token <token>', 'Authentication token')
.action(async (options) => {
const spinner = ora('Connecting to Flow-Nexus...').start();
try {
const integration = new FlowNexusIntegration({
endpoint: options.endpoint,
token: options.token
});
if (options.register) {
await integration.registerSolver();
spinner.succeed('Registered with Flow-Nexus platform');
}
if (options.swarmJoin) {
await integration.joinSwarm(options.swarmJoin);
spinner.succeed(`Joined swarm: ${options.swarmJoin}`);
}
} catch (error) {
spinner.fail('Flow-Nexus integration failed');
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
async function loadMatrix(filepath) {
const data = await fs.readFile(filepath, 'utf8');
const ext = path.extname(filepath).toLowerCase();
switch (ext) {
case '.json':
return JSON.parse(data);
case '.csv':
return parseCSVMatrix(data);
case '.mtx':
return parseMatrixMarket(data);
default:
throw new Error(`Unsupported matrix format: ${ext}`);
}
}
async function loadVector(filepath) {
const data = await fs.readFile(filepath, 'utf8');
const ext = path.extname(filepath).toLowerCase();
switch (ext) {
case '.json':
return JSON.parse(data);
case '.csv':
return data.split('\n').map(line => parseFloat(line.trim())).filter(x => !isNaN(x));
default:
throw new Error(`Unsupported vector format: ${ext}`);
}
}
function generateRandomVector(size) {
return Array.from({ length: size }, () => Math.random() * 10 - 5);
}
async function streamingSolve(solver, vector, options) {
const outputStream = options.output ? require('fs').createWriteStream(options.output) : null;
for await (const update of solver.streamSolve(vector)) {
const line = JSON.stringify({
iteration: update.iteration,
residual: update.residual,
timestamp: new Date().toISOString(),
convergence_rate: update.convergenceRate,
memory_usage: update.memoryUsage
}) + '\n';
if (outputStream) {
outputStream.write(line);
} else {
console.log(chalk.gray(`[${update.iteration}]`),
chalk.blue(`Residual: ${update.residual.toExponential(2)}`));
}
if (update.converged) {
console.log(chalk.green(`✓ Converged in ${update.iteration} iterations`));
break;
}
}
if (outputStream) {
outputStream.end();
console.log(chalk.green(`📁 Streaming output saved to ${options.output}`));
}
}
async function batchSolve(solver, vector, options) {
const startTime = Date.now();
const solution = await solver.solve(vector, {
onProgress: (update) => {
if (!options.quiet) {
process.stdout.write(`\r${chalk.blue('Progress:')} Iteration ${update.iteration}, Residual: ${update.residual.toExponential(2)}`);
}
}
});
const elapsed = Date.now() - startTime;
console.log(chalk.green(`\n✓ Solution found in ${elapsed}ms`));
console.log(` Iterations: ${solution.iterations}`);
console.log(` Final residual: ${solution.residual.toExponential(2)}`);
console.log(` Memory usage: ${solution.memoryUsage}MB`);
if (options.output) {
await fs.writeFile(options.output, JSON.stringify({
solution: solution.values,
metadata: {
iterations: solution.iterations,
residual: solution.residual,
solveTime: elapsed,
method: options.method
}
}, null, 2));
console.log(chalk.green(`📁 Solution saved to ${options.output}`));
}
}
async function verifySolution({ matrix, solution, vector, tolerance, probes }) {
const errors = [];
const residual = computeResidual(matrix, solution, vector);
const residualNorm = vectorNorm(residual);
for (let i = 0; i < probes; i++) {
const idx = Math.floor(Math.random() * matrix.rows);
const computed = multiplyMatrixRow(matrix, idx, solution);
const error = Math.abs(computed - vector[idx]);
errors.push(error);
}
const maxError = Math.max(...errors);
const meanError = errors.reduce((a, b) => a + b) / errors.length;
return {
verified: maxError < tolerance && residualNorm < tolerance,
maxError,
meanError,
residualNorm,
probeErrors: errors
};
}
async function runBenchmark({ size, sparsity, methods, iterations }) {
const results = [];
for (const method of methods) {
const times = [];
const iterationCounts = [];
let convergenceCount = 0;
for (let i = 0; i < iterations; i++) {
const matrix = generateRandomSparseMatrix(size, sparsity);
const vector = generateRandomVector(size);
const solver = await createSolver({ matrix, method });
const startTime = Date.now();
try {
const solution = await solver.solve(vector);
const elapsed = Date.now() - startTime;
times.push(elapsed);
iterationCounts.push(solution.iterations);
if (solution.converged) convergenceCount++;
} catch (error) {
console.warn(`Benchmark failed for ${method}:`, error.message);
}
}
if (times.length > 0) {
results.push({
method,
avgTime: times.reduce((a, b) => a + b) / times.length,
minTime: Math.min(...times),
maxTime: Math.max(...times),
avgIterations: iterationCounts.reduce((a, b) => a + b) / iterationCounts.length,
convergenceRate: convergenceCount / iterations
});
}
}
return results;
}
async function convertMatrix({ input, output, format, compress }) {
const matrix = await loadMatrix(input);
let converted;
switch (format.toLowerCase()) {
case 'json':
converted = JSON.stringify(matrix, null, compress ? 0 : 2);
break;
case 'csv':
converted = matrixToCSV(matrix);
break;
case 'mtx':
converted = matrixToMatrixMarket(matrix);
break;
case 'binary':
converted = matrixToBinary(matrix);
break;
default:
throw new Error(`Unsupported output format: ${format}`);
}
await fs.writeFile(output, converted);
}
function parseCSVMatrix(data) {
const lines = data.trim().split('\n');
const matrix = lines.map(line =>
line.split(',').map(val => parseFloat(val.trim()))
);
return {
rows: matrix.length,
cols: matrix[0].length,
data: matrix,
format: 'dense'
};
}
function parseMatrixMarket(data) {
const lines = data.trim().split('\n');
let headerLine = 0;
while (lines[headerLine].startsWith('%')) headerLine++;
const [rows, cols, entries] = lines[headerLine].split(' ').map(Number);
const values = [];
const rowIndices = [];
const colIndices = [];
for (let i = headerLine + 1; i < lines.length; i++) {
if (lines[i].trim()) {
const [row, col, val] = lines[i].split(' ');
rowIndices.push(parseInt(row) - 1); colIndices.push(parseInt(col) - 1);
values.push(parseFloat(val));
}
}
return {
rows,
cols,
entries,
data: { values, rowIndices, colIndices },
format: 'coo'
};
}
function computeResidual(matrix, x, b) {
const Ax = multiplyMatrixVector(matrix, x);
return Ax.map((val, i) => val - b[i]);
}
function vectorNorm(v) {
return Math.sqrt(v.reduce((sum, val) => sum + val * val, 0));
}
function multiplyMatrixVector(matrix, vector) {
if (matrix.format === 'dense') {
return matrix.data.map(row =>
row.reduce((sum, val, i) => sum + val * vector[i], 0)
);
} else if (matrix.format === 'coo') {
const result = new Array(matrix.rows).fill(0);
for (let i = 0; i < matrix.data.values.length; i++) {
const row = matrix.data.rowIndices[i];
const col = matrix.data.colIndices[i];
const val = matrix.data.values[i];
result[row] += val * vector[col];
}
return result;
}
throw new Error(`Unsupported matrix format: ${matrix.format}`);
}
function multiplyMatrixRow(matrix, rowIndex, vector) {
if (matrix.format === 'dense') {
return matrix.data[rowIndex].reduce((sum, val, i) => sum + val * vector[i], 0);
} else if (matrix.format === 'coo') {
let result = 0;
for (let i = 0; i < matrix.data.values.length; i++) {
if (matrix.data.rowIndices[i] === rowIndex) {
const col = matrix.data.colIndices[i];
const val = matrix.data.values[i];
result += val * vector[col];
}
}
return result;
}
throw new Error(`Unsupported matrix format: ${matrix.format}`);
}
function generateRandomSparseMatrix(size, sparsity) {
const values = [];
const rowIndices = [];
const colIndices = [];
const numEntries = Math.floor(size * size * sparsity);
for (let i = 0; i < numEntries; i++) {
const row = Math.floor(Math.random() * size);
const col = Math.floor(Math.random() * size);
const val = Math.random() * 10 - 5;
rowIndices.push(row);
colIndices.push(col);
values.push(val);
}
return {
rows: size,
cols: size,
entries: numEntries,
data: { values, rowIndices, colIndices },
format: 'coo'
};
}
function matrixToCSV(matrix) {
if (matrix.format === 'dense') {
return matrix.data.map(row => row.join(',')).join('\n');
}
throw new Error('CSV export only supported for dense matrices');
}
function matrixToMatrixMarket(matrix) {
let output = '%%MatrixMarket matrix coordinate real general\n';
output += `${matrix.rows} ${matrix.cols} ${matrix.entries || matrix.data.values.length}\n`;
if (matrix.format === 'coo') {
for (let i = 0; i < matrix.data.values.length; i++) {
const row = matrix.data.rowIndices[i] + 1; const col = matrix.data.colIndices[i] + 1;
const val = matrix.data.values[i];
output += `${row} ${col} ${val}\n`;
}
}
return output;
}
function matrixToBinary(matrix) {
const buffer = Buffer.alloc(8 + matrix.data.values.length * 8);
buffer.writeInt32LE(matrix.rows, 0);
buffer.writeInt32LE(matrix.cols, 4);
for (let i = 0; i < matrix.data.values.length; i++) {
buffer.writeDoubleLE(matrix.data.values[i], 8 + i * 8);
}
return buffer;
}
process.on('uncaughtException', (error) => {
console.error(chalk.red('Uncaught Exception:'), error.message);
if (program.opts().debug) console.error(error.stack);
process.exit(1);
});
process.on('unhandledRejection', (error) => {
console.error(chalk.red('Unhandled Rejection:'), error.message);
if (program.opts().debug) console.error(error.stack);
process.exit(1);
});
program.parseAsync().catch((error) => {
console.error(chalk.red('CLI Error:'), error.message);
process.exit(1);
});