mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +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:
+629
@@ -0,0 +1,629 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const compression = require('compression');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { WebSocketServer } = require('ws');
|
||||
const { EventEmitter } = require('events');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { StreamingManager } = require('./streaming');
|
||||
const { SessionManager } = require('./session-manager');
|
||||
const { FlowNexusIntegration } = require('../integrations/flow-nexus');
|
||||
|
||||
class SolverServer extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
port: options.port || 3000,
|
||||
cors: options.cors || false,
|
||||
workers: options.workers || 1,
|
||||
maxSessions: options.maxSessions || 100,
|
||||
authToken: options.authToken,
|
||||
flowNexusEnabled: options.flowNexusEnabled || false,
|
||||
...options
|
||||
};
|
||||
|
||||
this.app = express();
|
||||
this.server = null;
|
||||
this.wss = null;
|
||||
|
||||
this.sessions = new SessionManager(this.config);
|
||||
this.streaming = new StreamingManager(this.config);
|
||||
this.flowNexus = this.config.flowNexusEnabled ? new FlowNexusIntegration() : null;
|
||||
|
||||
this.setupMiddleware();
|
||||
this.setupRoutes();
|
||||
this.setupWebSocket();
|
||||
}
|
||||
|
||||
setupMiddleware() {
|
||||
// Security middleware
|
||||
this.app.use(helmet({
|
||||
contentSecurityPolicy: false, // Allow WebSocket connections
|
||||
crossOriginEmbedderPolicy: false
|
||||
}));
|
||||
|
||||
// CORS
|
||||
if (this.config.cors) {
|
||||
this.app.use(cors({
|
||||
origin: true,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Session-ID']
|
||||
}));
|
||||
}
|
||||
|
||||
// Compression
|
||||
this.app.use(compression());
|
||||
|
||||
// Rate limiting
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 1000, // Limit each IP to 1000 requests per windowMs
|
||||
message: {
|
||||
error: 'Too many requests',
|
||||
retryAfter: '15 minutes'
|
||||
}
|
||||
});
|
||||
this.app.use('/api', limiter);
|
||||
|
||||
// Body parsing
|
||||
this.app.use(express.json({ limit: '50mb' }));
|
||||
this.app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
||||
|
||||
// Request logging
|
||||
this.app.use((req, res, next) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// Authentication middleware
|
||||
this.app.use('/api/protected', this.authenticateToken.bind(this));
|
||||
}
|
||||
|
||||
setupRoutes() {
|
||||
// Health check
|
||||
this.app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
sessions: this.sessions.getStats()
|
||||
});
|
||||
});
|
||||
|
||||
// API routes
|
||||
this.app.use('/api/v1', this.createAPIRoutes());
|
||||
|
||||
// Static files for documentation
|
||||
this.app.use('/docs', express.static('docs'));
|
||||
|
||||
// Root endpoint
|
||||
this.app.get('/', (req, res) => {
|
||||
res.json({
|
||||
name: 'Sublinear Time Solver API',
|
||||
version: '1.0.0',
|
||||
endpoints: {
|
||||
health: '/health',
|
||||
api: '/api/v1',
|
||||
docs: '/docs',
|
||||
websocket: '/ws'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createAPIRoutes() {
|
||||
const router = express.Router();
|
||||
|
||||
// Start streaming solve session
|
||||
router.post('/solve-stream', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
matrix,
|
||||
vector,
|
||||
method = 'adaptive',
|
||||
options = {},
|
||||
flow_nexus = {}
|
||||
} = req.body;
|
||||
|
||||
if (!matrix || !vector) {
|
||||
return res.status(400).json({
|
||||
error: 'Matrix and vector are required',
|
||||
code: 'MISSING_INPUT'
|
||||
});
|
||||
}
|
||||
|
||||
const sessionId = uuidv4();
|
||||
const session = await this.sessions.createSession(sessionId, {
|
||||
matrix,
|
||||
vector,
|
||||
method,
|
||||
options,
|
||||
flowNexus: flow_nexus
|
||||
});
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/x-ndjson',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
'X-Session-ID': sessionId
|
||||
});
|
||||
|
||||
// Start streaming solve
|
||||
const stream = await this.streaming.startSolve(session);
|
||||
|
||||
for await (const update of stream) {
|
||||
const data = JSON.stringify({
|
||||
type: 'iteration_update',
|
||||
session_id: sessionId,
|
||||
timestamp: new Date().toISOString(),
|
||||
...update
|
||||
}) + '\n';
|
||||
|
||||
if (!res.write(data)) {
|
||||
// Backpressure handling
|
||||
await new Promise(resolve => res.once('drain', resolve));
|
||||
}
|
||||
|
||||
if (update.converged || update.error) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Solve stream error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'SOLVE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Submit solve job
|
||||
router.post('/solve', async (req, res) => {
|
||||
try {
|
||||
const { matrix, vector, method, options } = req.body;
|
||||
|
||||
if (!matrix || !vector) {
|
||||
return res.status(400).json({
|
||||
error: 'Matrix and vector are required'
|
||||
});
|
||||
}
|
||||
|
||||
const jobId = await this.sessions.submitJob({
|
||||
matrix,
|
||||
vector,
|
||||
method,
|
||||
options
|
||||
});
|
||||
|
||||
res.json({
|
||||
job_id: jobId,
|
||||
status: 'submitted',
|
||||
endpoints: {
|
||||
status: `/api/v1/jobs/${jobId}`,
|
||||
stream: `/api/v1/jobs/${jobId}/stream`
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Submit job error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'SUBMIT_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get job status
|
||||
router.get('/jobs/:jobId', async (req, res) => {
|
||||
try {
|
||||
const status = await this.sessions.getJobStatus(req.params.jobId);
|
||||
if (!status) {
|
||||
return res.status(404).json({
|
||||
error: 'Job not found',
|
||||
code: 'JOB_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
res.json(status);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Get job status error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'STATUS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Stream job updates
|
||||
router.get('/jobs/:jobId/stream', async (req, res) => {
|
||||
try {
|
||||
const stream = await this.sessions.getJobStream(req.params.jobId);
|
||||
if (!stream) {
|
||||
return res.status(404).json({
|
||||
error: 'Job not found or not streaming'
|
||||
});
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
});
|
||||
|
||||
for await (const update of stream) {
|
||||
res.write(`data: ${JSON.stringify(update)}\n\n`);
|
||||
}
|
||||
|
||||
res.end();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Job stream error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'STREAM_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Verification endpoint
|
||||
router.post('/verify', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
session_id,
|
||||
probe_count = 10,
|
||||
tolerance = 1e-8
|
||||
} = req.body;
|
||||
|
||||
if (!session_id) {
|
||||
return res.status(400).json({
|
||||
error: 'Session ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.sessions.verifySession(session_id, {
|
||||
probeCount: probe_count,
|
||||
tolerance
|
||||
});
|
||||
|
||||
res.json({
|
||||
type: 'verification_result',
|
||||
session_id,
|
||||
timestamp: new Date().toISOString(),
|
||||
...result
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Verification error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'VERIFICATION_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Session status
|
||||
router.get('/sessions/:sessionId', async (req, res) => {
|
||||
try {
|
||||
const session = await this.sessions.getSession(req.params.sessionId);
|
||||
if (!session) {
|
||||
return res.status(404).json({
|
||||
error: 'Session not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
session_id: req.params.sessionId,
|
||||
status: session.status,
|
||||
created_at: session.createdAt,
|
||||
last_activity: session.lastActivity,
|
||||
metrics: session.metrics
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Session status error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'SESSION_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Cost update endpoint for swarm coordination
|
||||
router.post('/swarm/costs', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
session_id,
|
||||
delta_costs,
|
||||
matrix_updates,
|
||||
source_node
|
||||
} = req.body;
|
||||
|
||||
if (!session_id || !delta_costs) {
|
||||
return res.status(400).json({
|
||||
error: 'Session ID and delta costs are required'
|
||||
});
|
||||
}
|
||||
|
||||
await this.sessions.updateCosts(session_id, {
|
||||
deltaCosts: delta_costs,
|
||||
matrixUpdates: matrix_updates,
|
||||
sourceNode: source_node
|
||||
});
|
||||
|
||||
res.json({
|
||||
status: 'updated',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Cost update error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'COST_UPDATE_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Swarm join endpoint
|
||||
router.post('/swarm/join', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
session_id,
|
||||
node_id,
|
||||
capabilities = []
|
||||
} = req.body;
|
||||
|
||||
if (!session_id || !node_id) {
|
||||
return res.status(400).json({
|
||||
error: 'Session ID and node ID are required'
|
||||
});
|
||||
}
|
||||
|
||||
await this.sessions.joinSwarm(session_id, {
|
||||
nodeId: node_id,
|
||||
capabilities
|
||||
});
|
||||
|
||||
res.json({
|
||||
status: 'joined',
|
||||
node_id,
|
||||
session_id
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Swarm join error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'SWARM_JOIN_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Flow-Nexus integration endpoints
|
||||
if (this.flowNexus) {
|
||||
router.post('/flow-nexus/register', async (req, res) => {
|
||||
try {
|
||||
const result = await this.flowNexus.registerSolver({
|
||||
endpoint: `http://localhost:${this.config.port}`,
|
||||
capabilities: ['streaming', 'verification', 'swarm']
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Flow-Nexus registration error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'FLOW_NEXUS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/flow-nexus/status', async (req, res) => {
|
||||
try {
|
||||
const status = await this.flowNexus.getStatus();
|
||||
res.json(status);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Flow-Nexus status error:', error);
|
||||
res.status(500).json({
|
||||
error: error.message,
|
||||
code: 'FLOW_NEXUS_ERROR'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
setupWebSocket() {
|
||||
// WebSocket setup will be done when server starts
|
||||
}
|
||||
|
||||
authenticateToken(req, res, next) {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token && this.config.authToken) {
|
||||
return res.status(401).json({
|
||||
error: 'Authentication token required',
|
||||
code: 'AUTH_REQUIRED'
|
||||
});
|
||||
}
|
||||
|
||||
if (this.config.authToken && token !== this.config.authToken) {
|
||||
return res.status(403).json({
|
||||
error: 'Invalid authentication token',
|
||||
code: 'AUTH_INVALID'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async start() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server = this.app.listen(this.config.port, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup WebSocket server
|
||||
this.wss = new WebSocketServer({
|
||||
server: this.server,
|
||||
path: '/ws'
|
||||
});
|
||||
|
||||
this.setupWebSocketHandlers();
|
||||
|
||||
console.log(`🚀 Solver server started on port ${this.config.port}`);
|
||||
this.emit('started');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupWebSocketHandlers() {
|
||||
this.wss.on('connection', (ws, req) => {
|
||||
console.log('WebSocket connection established');
|
||||
|
||||
ws.on('message', async (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
await this.handleWebSocketMessage(ws, message);
|
||||
} catch (error) {
|
||||
console.error('WebSocket message error:', error);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: error.message
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('WebSocket connection closed');
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
});
|
||||
|
||||
// Send welcome message
|
||||
ws.send(JSON.stringify({
|
||||
type: 'welcome',
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
async handleWebSocketMessage(ws, message) {
|
||||
switch (message.type) {
|
||||
case 'subscribe':
|
||||
if (message.session_id) {
|
||||
await this.subscribeToSession(ws, message.session_id);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'solve':
|
||||
const sessionId = await this.startWebSocketSolve(ws, message);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'solve_started',
|
||||
session_id: sessionId
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
ws.send(JSON.stringify({
|
||||
type: 'pong',
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
ws.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: `Unknown message type: ${message.type}`
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async subscribeToSession(ws, sessionId) {
|
||||
const stream = await this.sessions.getJobStream(sessionId);
|
||||
if (!stream) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: 'Session not found or not streaming'
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
for await (const update of stream) {
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'session_update',
|
||||
session_id: sessionId,
|
||||
...update
|
||||
}));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async startWebSocketSolve(ws, message) {
|
||||
const sessionId = uuidv4();
|
||||
const session = await this.sessions.createSession(sessionId, {
|
||||
matrix: message.matrix,
|
||||
vector: message.vector,
|
||||
method: message.method || 'adaptive',
|
||||
options: message.options || {}
|
||||
});
|
||||
|
||||
// Start streaming to WebSocket
|
||||
this.subscribeToSession(ws, sessionId);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
return new Promise((resolve) => {
|
||||
if (this.wss) {
|
||||
this.wss.close();
|
||||
}
|
||||
|
||||
if (this.server) {
|
||||
this.server.close(() => {
|
||||
console.log('Server stopped');
|
||||
this.emit('stopped');
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return {
|
||||
sessions: this.sessions.getStats(),
|
||||
server: {
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
connections: this.wss ? this.wss.clients.size : 0
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { SolverServer };
|
||||
@@ -0,0 +1,440 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { StreamingManager } = require('./streaming');
|
||||
|
||||
class SessionManager extends EventEmitter {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
maxSessions: config.maxSessions || 100,
|
||||
sessionTimeout: config.sessionTimeout || 3600000, // 1 hour
|
||||
cleanupInterval: config.cleanupInterval || 300000, // 5 minutes
|
||||
...config
|
||||
};
|
||||
|
||||
this.sessions = new Map();
|
||||
this.jobQueue = [];
|
||||
this.streaming = new StreamingManager(config);
|
||||
|
||||
// Start cleanup timer
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.cleanupStaleSessions();
|
||||
}, this.config.cleanupInterval);
|
||||
}
|
||||
|
||||
async createSession(sessionId, sessionData) {
|
||||
if (this.sessions.size >= this.config.maxSessions) {
|
||||
throw new Error('Maximum number of sessions reached');
|
||||
}
|
||||
|
||||
const session = {
|
||||
id: sessionId,
|
||||
...sessionData,
|
||||
status: 'created',
|
||||
createdAt: new Date().toISOString(),
|
||||
lastActivity: new Date().toISOString(),
|
||||
metrics: {
|
||||
iterations: 0,
|
||||
residual: Infinity,
|
||||
memoryUsage: 0,
|
||||
convergenceRate: null
|
||||
},
|
||||
swarmNodes: new Set(),
|
||||
verificationHistory: [],
|
||||
costUpdates: []
|
||||
};
|
||||
|
||||
this.sessions.set(sessionId, session);
|
||||
this.emit('session_created', session);
|
||||
|
||||
console.log(`Session created: ${sessionId}`);
|
||||
return session;
|
||||
}
|
||||
|
||||
getSession(sessionId) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
session.lastActivity = new Date().toISOString();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
updateSession(sessionId, updates) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
Object.assign(session, updates);
|
||||
session.lastActivity = new Date().toISOString();
|
||||
this.emit('session_updated', session);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
deleteSession(sessionId) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
this.sessions.delete(sessionId);
|
||||
this.streaming.stopStream(sessionId);
|
||||
this.emit('session_deleted', session);
|
||||
console.log(`Session deleted: ${sessionId}`);
|
||||
}
|
||||
}
|
||||
|
||||
async submitJob(jobData) {
|
||||
const jobId = uuidv4();
|
||||
const session = await this.createSession(jobId, {
|
||||
...jobData,
|
||||
type: 'job',
|
||||
status: 'queued'
|
||||
});
|
||||
|
||||
this.jobQueue.push(jobId);
|
||||
this.processJobQueue();
|
||||
|
||||
return jobId;
|
||||
}
|
||||
|
||||
async processJobQueue() {
|
||||
if (this.jobQueue.length === 0) return;
|
||||
|
||||
const jobId = this.jobQueue.shift();
|
||||
const session = this.getSession(jobId);
|
||||
|
||||
if (!session) return;
|
||||
|
||||
try {
|
||||
session.status = 'running';
|
||||
const stream = await this.streaming.startSolve(session);
|
||||
|
||||
// Store stream reference for later access
|
||||
session.stream = stream;
|
||||
|
||||
} catch (error) {
|
||||
session.status = 'error';
|
||||
session.error = error.message;
|
||||
console.error(`Job ${jobId} failed:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async getJobStatus(jobId) {
|
||||
const session = this.getSession(jobId);
|
||||
if (!session) return null;
|
||||
|
||||
const streamStatus = this.streaming.getStreamStatus(jobId);
|
||||
|
||||
return {
|
||||
job_id: jobId,
|
||||
status: session.status,
|
||||
created_at: session.createdAt,
|
||||
last_activity: session.lastActivity,
|
||||
metrics: session.metrics,
|
||||
stream: streamStatus,
|
||||
swarm_nodes: Array.from(session.swarmNodes || []),
|
||||
verification_count: session.verificationHistory?.length || 0
|
||||
};
|
||||
}
|
||||
|
||||
async getJobStream(jobId) {
|
||||
const session = this.getSession(jobId);
|
||||
if (!session) return null;
|
||||
|
||||
if (session.stream) {
|
||||
return session.stream;
|
||||
}
|
||||
|
||||
// If no active stream, try to start one
|
||||
if (session.status === 'created' || session.status === 'queued') {
|
||||
return await this.streaming.startSolve(session);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async verifySession(sessionId, options = {}) {
|
||||
const session = this.getSession(sessionId);
|
||||
if (!session) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
|
||||
const { probeCount = 10, tolerance = 1e-8 } = options;
|
||||
|
||||
try {
|
||||
// Implement verification logic
|
||||
const verificationResult = await this.performVerification(session, {
|
||||
probeCount,
|
||||
tolerance
|
||||
});
|
||||
|
||||
// Store verification history
|
||||
session.verificationHistory = session.verificationHistory || [];
|
||||
session.verificationHistory.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
...verificationResult
|
||||
});
|
||||
|
||||
// Keep only recent history
|
||||
if (session.verificationHistory.length > 100) {
|
||||
session.verificationHistory = session.verificationHistory.slice(-100);
|
||||
}
|
||||
|
||||
return verificationResult;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Verification failed for session ${sessionId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async performVerification(session, options) {
|
||||
// This would integrate with the actual solver verification
|
||||
// For now, return a mock result
|
||||
const errors = [];
|
||||
|
||||
for (let i = 0; i < options.probeCount; i++) {
|
||||
// Generate random probe error (mock)
|
||||
const error = Math.random() * 1e-8;
|
||||
errors.push(error);
|
||||
}
|
||||
|
||||
const maxError = Math.max(...errors);
|
||||
const meanError = errors.reduce((a, b) => a + b) / errors.length;
|
||||
|
||||
return {
|
||||
verified: maxError < options.tolerance,
|
||||
max_error: maxError,
|
||||
mean_error: meanError,
|
||||
probe_count: options.probeCount,
|
||||
tolerance: options.tolerance,
|
||||
verification_time_ms: Math.random() * 5 // Mock timing
|
||||
};
|
||||
}
|
||||
|
||||
async updateCosts(sessionId, costUpdate) {
|
||||
const session = this.getSession(sessionId);
|
||||
if (!session) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
|
||||
// Store cost update
|
||||
session.costUpdates = session.costUpdates || [];
|
||||
session.costUpdates.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
...costUpdate
|
||||
});
|
||||
|
||||
// Apply cost update to solver (if running)
|
||||
if (session.stream && session.status === 'running') {
|
||||
// This would integrate with the actual solver cost update mechanism
|
||||
console.log(`Cost update applied to session ${sessionId}`);
|
||||
}
|
||||
|
||||
this.emit('cost_update', { sessionId, costUpdate });
|
||||
}
|
||||
|
||||
async joinSwarm(sessionId, nodeData) {
|
||||
const session = this.getSession(sessionId);
|
||||
if (!session) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
|
||||
session.swarmNodes = session.swarmNodes || new Set();
|
||||
session.swarmNodes.add(nodeData.nodeId);
|
||||
|
||||
console.log(`Node ${nodeData.nodeId} joined swarm for session ${sessionId}`);
|
||||
this.emit('swarm_join', { sessionId, nodeData });
|
||||
}
|
||||
|
||||
cleanupStaleSession(sessionId) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
const now = Date.now();
|
||||
const lastActivity = new Date(session.lastActivity).getTime();
|
||||
const age = now - lastActivity;
|
||||
|
||||
if (age > this.config.sessionTimeout) {
|
||||
console.log(`Cleaning up stale session: ${sessionId} (age: ${Math.round(age / 1000)}s)`);
|
||||
this.deleteSession(sessionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanupStaleSession() {
|
||||
const staleSessions = [];
|
||||
|
||||
for (const [sessionId, session] of this.sessions) {
|
||||
if (this.cleanupStaleSession(sessionId)) {
|
||||
staleSessions.push(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
if (staleSessions.length > 0) {
|
||||
console.log(`Cleaned up ${staleSessions.length} stale sessions`);
|
||||
}
|
||||
}
|
||||
|
||||
getStats() {
|
||||
const sessions = Array.from(this.sessions.values());
|
||||
|
||||
return {
|
||||
total_sessions: this.sessions.size,
|
||||
active_sessions: sessions.filter(s => s.status === 'running').length,
|
||||
queued_jobs: this.jobQueue.length,
|
||||
sessions_by_status: {
|
||||
created: sessions.filter(s => s.status === 'created').length,
|
||||
queued: sessions.filter(s => s.status === 'queued').length,
|
||||
running: sessions.filter(s => s.status === 'running').length,
|
||||
completed: sessions.filter(s => s.status === 'completed').length,
|
||||
error: sessions.filter(s => s.status === 'error').length
|
||||
},
|
||||
streaming_stats: this.streaming.getStats(),
|
||||
memory_usage: process.memoryUsage()
|
||||
};
|
||||
}
|
||||
|
||||
async shutdown() {
|
||||
console.log('Shutting down session manager...');
|
||||
|
||||
// Clear cleanup timer
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
}
|
||||
|
||||
// Stop all active streams
|
||||
for (const sessionId of this.sessions.keys()) {
|
||||
this.streaming.stopStream(sessionId);
|
||||
}
|
||||
|
||||
// Clear all sessions
|
||||
this.sessions.clear();
|
||||
this.jobQueue.length = 0;
|
||||
|
||||
this.emit('shutdown');
|
||||
console.log('Session manager shutdown complete');
|
||||
}
|
||||
}
|
||||
|
||||
// Session data structure
|
||||
class SolverSession {
|
||||
constructor(sessionId, config) {
|
||||
this.id = sessionId;
|
||||
this.matrix = config.matrix;
|
||||
this.vector = config.vector;
|
||||
this.method = config.method || 'adaptive';
|
||||
this.options = config.options || {};
|
||||
|
||||
this.status = 'created';
|
||||
this.createdAt = new Date().toISOString();
|
||||
this.lastActivity = new Date().toISOString();
|
||||
|
||||
this.metrics = {
|
||||
iterations: 0,
|
||||
residual: Infinity,
|
||||
convergenceRate: null,
|
||||
memoryUsage: 0
|
||||
};
|
||||
|
||||
this.swarmNodes = new Set();
|
||||
this.verificationHistory = [];
|
||||
this.costUpdates = [];
|
||||
|
||||
// Flow-Nexus specific data
|
||||
this.flowNexus = config.flowNexus || {};
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
id: this.id,
|
||||
matrix: this.matrix,
|
||||
vector: this.vector,
|
||||
method: this.method,
|
||||
options: this.options,
|
||||
status: this.status,
|
||||
createdAt: this.createdAt,
|
||||
lastActivity: this.lastActivity,
|
||||
metrics: this.metrics,
|
||||
swarmNodes: Array.from(this.swarmNodes),
|
||||
verificationHistory: this.verificationHistory,
|
||||
costUpdates: this.costUpdates,
|
||||
flowNexus: this.flowNexus
|
||||
};
|
||||
}
|
||||
|
||||
static deserialize(data) {
|
||||
const session = new SolverSession(data.id, {
|
||||
matrix: data.matrix,
|
||||
vector: data.vector,
|
||||
method: data.method,
|
||||
options: data.options,
|
||||
flowNexus: data.flowNexus
|
||||
});
|
||||
|
||||
session.status = data.status;
|
||||
session.createdAt = data.createdAt;
|
||||
session.lastActivity = data.lastActivity;
|
||||
session.metrics = data.metrics;
|
||||
session.swarmNodes = new Set(data.swarmNodes || []);
|
||||
session.verificationHistory = data.verificationHistory || [];
|
||||
session.costUpdates = data.costUpdates || [];
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
updateMetrics(metrics) {
|
||||
Object.assign(this.metrics, metrics);
|
||||
this.lastActivity = new Date().toISOString();
|
||||
}
|
||||
|
||||
addSwarmNode(nodeId) {
|
||||
this.swarmNodes.add(nodeId);
|
||||
this.lastActivity = new Date().toISOString();
|
||||
}
|
||||
|
||||
removeSwarmNode(nodeId) {
|
||||
this.swarmNodes.delete(nodeId);
|
||||
this.lastActivity = new Date().toISOString();
|
||||
}
|
||||
|
||||
addVerificationResult(result) {
|
||||
this.verificationHistory.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
...result
|
||||
});
|
||||
|
||||
// Keep only recent history
|
||||
if (this.verificationHistory.length > 100) {
|
||||
this.verificationHistory.shift();
|
||||
}
|
||||
|
||||
this.lastActivity = new Date().toISOString();
|
||||
}
|
||||
|
||||
addCostUpdate(update) {
|
||||
this.costUpdates.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
...update
|
||||
});
|
||||
|
||||
// Keep only recent updates
|
||||
if (this.costUpdates.length > 1000) {
|
||||
this.costUpdates = this.costUpdates.slice(-1000);
|
||||
}
|
||||
|
||||
this.lastActivity = new Date().toISOString();
|
||||
}
|
||||
|
||||
getAge() {
|
||||
return Date.now() - new Date(this.createdAt).getTime();
|
||||
}
|
||||
|
||||
getInactiveTime() {
|
||||
return Date.now() - new Date(this.lastActivity).getTime();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SessionManager,
|
||||
SolverSession
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
|
||||
|
||||
if (!isMainThread) {
|
||||
// Worker thread code
|
||||
const { createSolver } = require('../src/solver');
|
||||
|
||||
let currentSolver = null;
|
||||
let currentSession = null;
|
||||
let solving = false;
|
||||
|
||||
parentPort.on('message', async (message) => {
|
||||
try {
|
||||
switch (message.type) {
|
||||
case 'solve':
|
||||
await handleSolve(message);
|
||||
break;
|
||||
|
||||
case 'stop':
|
||||
await handleStop(message);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
handleStatus(message);
|
||||
break;
|
||||
|
||||
default:
|
||||
parentPort.postMessage({
|
||||
type: 'error',
|
||||
sessionId: message.sessionId,
|
||||
error: `Unknown message type: ${message.type}`
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
parentPort.postMessage({
|
||||
type: 'error',
|
||||
sessionId: message.sessionId,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSolve(message) {
|
||||
if (solving) {
|
||||
throw new Error('Worker is already solving a problem');
|
||||
}
|
||||
|
||||
solving = true;
|
||||
currentSession = message.sessionId;
|
||||
|
||||
try {
|
||||
// Create solver with provided configuration
|
||||
currentSolver = await createSolver({
|
||||
matrix: message.matrix,
|
||||
method: message.method || 'adaptive',
|
||||
tolerance: message.options?.tolerance || 1e-10,
|
||||
maxIterations: message.options?.maxIterations || 1000,
|
||||
enableVerification: message.options?.enableVerification || false
|
||||
});
|
||||
|
||||
// Start streaming solve
|
||||
const startTime = Date.now();
|
||||
let lastMemoryCheck = startTime;
|
||||
|
||||
for await (const update of currentSolver.streamSolve(message.vector)) {
|
||||
// Check if we should stop
|
||||
if (!solving || currentSession !== message.sessionId) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Memory usage tracking
|
||||
const now = Date.now();
|
||||
let memoryUsage = 0;
|
||||
|
||||
if (now - lastMemoryCheck > 5000) { // Check every 5 seconds
|
||||
const memInfo = process.memoryUsage();
|
||||
memoryUsage = Math.round(memInfo.heapUsed / 1024 / 1024); // MB
|
||||
lastMemoryCheck = now;
|
||||
}
|
||||
|
||||
// Send iteration update
|
||||
parentPort.postMessage({
|
||||
type: 'iteration',
|
||||
sessionId: message.sessionId,
|
||||
iteration: update.iteration,
|
||||
residual: update.residual,
|
||||
convergenceRate: update.convergenceRate,
|
||||
memoryUsage,
|
||||
verified: update.verified || false,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Check for convergence
|
||||
if (update.converged) {
|
||||
parentPort.postMessage({
|
||||
type: 'converged',
|
||||
sessionId: message.sessionId,
|
||||
solution: update.solution,
|
||||
stats: {
|
||||
iterations: update.iteration,
|
||||
residual: update.residual,
|
||||
solveTime: now - startTime,
|
||||
memoryUsage,
|
||||
converged: true
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
parentPort.postMessage({
|
||||
type: 'error',
|
||||
sessionId: message.sessionId,
|
||||
error: error.message
|
||||
});
|
||||
} finally {
|
||||
solving = false;
|
||||
currentSolver = null;
|
||||
currentSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStop(message) {
|
||||
if (currentSession === message.sessionId) {
|
||||
solving = false;
|
||||
|
||||
if (currentSolver && typeof currentSolver.stop === 'function') {
|
||||
await currentSolver.stop();
|
||||
}
|
||||
|
||||
parentPort.postMessage({
|
||||
type: 'stopped',
|
||||
sessionId: message.sessionId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleStatus(message) {
|
||||
parentPort.postMessage({
|
||||
type: 'status',
|
||||
sessionId: message.sessionId,
|
||||
solving,
|
||||
currentSession,
|
||||
memory: process.memoryUsage(),
|
||||
uptime: process.uptime()
|
||||
});
|
||||
}
|
||||
|
||||
// Handle worker errors
|
||||
process.on('uncaughtException', (error) => {
|
||||
parentPort.postMessage({
|
||||
type: 'error',
|
||||
sessionId: currentSession,
|
||||
error: error.message,
|
||||
fatal: true
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (error) => {
|
||||
parentPort.postMessage({
|
||||
type: 'error',
|
||||
sessionId: currentSession,
|
||||
error: error.message,
|
||||
fatal: true
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Send ready signal
|
||||
parentPort.postMessage({
|
||||
type: 'ready',
|
||||
workerId: process.pid
|
||||
});
|
||||
|
||||
} else {
|
||||
// Main thread code - export worker creation utility
|
||||
const path = require('path');
|
||||
|
||||
function createSolverWorker() {
|
||||
return new Worker(__filename);
|
||||
}
|
||||
|
||||
module.exports = { createSolverWorker };
|
||||
}
|
||||
+520
@@ -0,0 +1,520 @@
|
||||
const { EventEmitter } = require('events');
|
||||
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const path = require('path');
|
||||
|
||||
class StreamingManager extends EventEmitter {
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
maxConcurrentStreams: config.maxConcurrentStreams || 100,
|
||||
workerPoolSize: config.workers || 1,
|
||||
streamTimeout: config.streamTimeout || 300000, // 5 minutes
|
||||
heartbeatInterval: config.heartbeatInterval || 15000, // 15 seconds
|
||||
...config
|
||||
};
|
||||
|
||||
this.activeStreams = new Map();
|
||||
this.workerPool = [];
|
||||
this.availableWorkers = [];
|
||||
this.jobQueue = [];
|
||||
|
||||
this.initializeWorkerPool();
|
||||
}
|
||||
|
||||
initializeWorkerPool() {
|
||||
for (let i = 0; i < this.config.workerPoolSize; i++) {
|
||||
this.createWorker();
|
||||
}
|
||||
}
|
||||
|
||||
createWorker() {
|
||||
const worker = new Worker(path.join(__dirname, 'solver-worker.js'));
|
||||
|
||||
worker.on('message', (message) => {
|
||||
this.handleWorkerMessage(worker, message);
|
||||
});
|
||||
|
||||
worker.on('error', (error) => {
|
||||
console.error('Worker error:', error);
|
||||
this.replaceWorker(worker);
|
||||
});
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
console.error(`Worker stopped with exit code ${code}`);
|
||||
this.replaceWorker(worker);
|
||||
}
|
||||
});
|
||||
|
||||
worker.id = uuidv4();
|
||||
this.workerPool.push(worker);
|
||||
this.availableWorkers.push(worker);
|
||||
|
||||
return worker;
|
||||
}
|
||||
|
||||
replaceWorker(deadWorker) {
|
||||
// Remove dead worker
|
||||
this.workerPool = this.workerPool.filter(w => w.id !== deadWorker.id);
|
||||
this.availableWorkers = this.availableWorkers.filter(w => w.id !== deadWorker.id);
|
||||
|
||||
// Create replacement
|
||||
this.createWorker();
|
||||
}
|
||||
|
||||
async startSolve(session) {
|
||||
if (this.activeStreams.size >= this.config.maxConcurrentStreams) {
|
||||
throw new Error('Maximum concurrent streams reached');
|
||||
}
|
||||
|
||||
const stream = new SolverStream(session, this);
|
||||
this.activeStreams.set(session.id, stream);
|
||||
|
||||
// Set up cleanup timeout
|
||||
setTimeout(() => {
|
||||
if (this.activeStreams.has(session.id)) {
|
||||
this.activeStreams.delete(session.id);
|
||||
stream.destroy();
|
||||
}
|
||||
}, this.config.streamTimeout);
|
||||
|
||||
return stream.start();
|
||||
}
|
||||
|
||||
getWorker() {
|
||||
if (this.availableWorkers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.availableWorkers.shift();
|
||||
}
|
||||
|
||||
releaseWorker(worker) {
|
||||
if (this.workerPool.includes(worker)) {
|
||||
this.availableWorkers.push(worker);
|
||||
}
|
||||
}
|
||||
|
||||
handleWorkerMessage(worker, message) {
|
||||
const stream = this.activeStreams.get(message.sessionId);
|
||||
if (stream) {
|
||||
stream.handleWorkerMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
stopStream(sessionId) {
|
||||
const stream = this.activeStreams.get(sessionId);
|
||||
if (stream) {
|
||||
stream.stop();
|
||||
this.activeStreams.delete(sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
getStreamStatus(sessionId) {
|
||||
const stream = this.activeStreams.get(sessionId);
|
||||
return stream ? stream.getStatus() : null;
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return {
|
||||
activeStreams: this.activeStreams.size,
|
||||
availableWorkers: this.availableWorkers.length,
|
||||
totalWorkers: this.workerPool.length,
|
||||
queuedJobs: this.jobQueue.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class SolverStream extends EventEmitter {
|
||||
constructor(session, manager) {
|
||||
super();
|
||||
|
||||
this.session = session;
|
||||
this.manager = manager;
|
||||
this.worker = null;
|
||||
this.status = 'pending';
|
||||
this.startTime = Date.now();
|
||||
this.lastUpdate = Date.now();
|
||||
|
||||
this.updates = [];
|
||||
this.currentIteration = 0;
|
||||
this.currentResidual = Infinity;
|
||||
this.converged = false;
|
||||
this.error = null;
|
||||
|
||||
// Heartbeat to detect stalled streams
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
this.checkHeartbeat();
|
||||
}, manager.config.heartbeatInterval);
|
||||
}
|
||||
|
||||
async start() {
|
||||
try {
|
||||
this.status = 'starting';
|
||||
this.worker = this.manager.getWorker();
|
||||
|
||||
if (!this.worker) {
|
||||
throw new Error('No available workers');
|
||||
}
|
||||
|
||||
// Send solve job to worker
|
||||
this.worker.postMessage({
|
||||
type: 'solve',
|
||||
sessionId: this.session.id,
|
||||
matrix: this.session.matrix,
|
||||
vector: this.session.vector,
|
||||
method: this.session.method,
|
||||
options: this.session.options
|
||||
});
|
||||
|
||||
this.status = 'running';
|
||||
|
||||
return this.createAsyncIterator();
|
||||
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
this.status = 'error';
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async *createAsyncIterator() {
|
||||
let done = false;
|
||||
|
||||
while (!done) {
|
||||
// Wait for next update
|
||||
const update = await this.waitForUpdate();
|
||||
|
||||
if (update.type === 'iteration') {
|
||||
this.currentIteration = update.iteration;
|
||||
this.currentResidual = update.residual;
|
||||
this.lastUpdate = Date.now();
|
||||
|
||||
yield {
|
||||
iteration: update.iteration,
|
||||
residual: update.residual,
|
||||
convergence_rate: update.convergenceRate,
|
||||
memory_usage: update.memoryUsage,
|
||||
estimated_time_remaining: this.estimateTimeRemaining(update),
|
||||
verified: update.verified || false,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
} else if (update.type === 'converged') {
|
||||
this.converged = true;
|
||||
this.status = 'completed';
|
||||
done = true;
|
||||
|
||||
yield {
|
||||
iteration: this.currentIteration,
|
||||
residual: this.currentResidual,
|
||||
converged: true,
|
||||
solution: update.solution,
|
||||
stats: update.stats,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
} else if (update.type === 'error') {
|
||||
this.error = new Error(update.error);
|
||||
this.status = 'error';
|
||||
done = true;
|
||||
|
||||
yield {
|
||||
error: update.error,
|
||||
iteration: this.currentIteration,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
waitForUpdate() {
|
||||
return new Promise((resolve) => {
|
||||
this.once('update', resolve);
|
||||
});
|
||||
}
|
||||
|
||||
handleWorkerMessage(message) {
|
||||
this.emit('update', message);
|
||||
}
|
||||
|
||||
estimateTimeRemaining(update) {
|
||||
if (this.currentIteration === 0) return null;
|
||||
|
||||
const elapsed = Date.now() - this.startTime;
|
||||
const iterationsPerMs = this.currentIteration / elapsed;
|
||||
|
||||
if (update.convergenceRate && update.convergenceRate > 0) {
|
||||
// Estimate based on convergence rate
|
||||
const iterationsToConverge = Math.log(this.session.options.tolerance || 1e-10) /
|
||||
Math.log(update.convergenceRate);
|
||||
const remainingIterations = iterationsToConverge - this.currentIteration;
|
||||
|
||||
return Math.max(0, remainingIterations / iterationsPerMs);
|
||||
}
|
||||
|
||||
// Fallback to linear estimation
|
||||
const maxIterations = this.session.options.maxIterations || 1000;
|
||||
const remainingIterations = maxIterations - this.currentIteration;
|
||||
|
||||
return remainingIterations / iterationsPerMs;
|
||||
}
|
||||
|
||||
checkHeartbeat() {
|
||||
const timeSinceUpdate = Date.now() - this.lastUpdate;
|
||||
|
||||
if (timeSinceUpdate > this.manager.config.heartbeatInterval * 3) {
|
||||
// Stream appears stalled
|
||||
console.warn(`Stream ${this.session.id} appears stalled`);
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({
|
||||
type: 'stop',
|
||||
sessionId: this.session.id
|
||||
});
|
||||
}
|
||||
|
||||
this.status = 'stopped';
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
|
||||
if (this.worker) {
|
||||
this.manager.releaseWorker(this.worker);
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
this.removeAllListeners();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stop();
|
||||
this.status = 'destroyed';
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
status: this.status,
|
||||
sessionId: this.session.id,
|
||||
currentIteration: this.currentIteration,
|
||||
currentResidual: this.currentResidual,
|
||||
converged: this.converged,
|
||||
error: this.error ? this.error.message : null,
|
||||
startTime: this.startTime,
|
||||
lastUpdate: this.lastUpdate,
|
||||
elapsed: Date.now() - this.startTime
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class VerificationLoop {
|
||||
constructor(options = {}) {
|
||||
this.enabled = options.enabled || false;
|
||||
this.interval = options.interval || 100;
|
||||
this.probeCount = options.probeCount || 10;
|
||||
this.tolerance = options.tolerance || 1e-8;
|
||||
|
||||
this.lastVerification = 0;
|
||||
this.verificationHistory = [];
|
||||
}
|
||||
|
||||
shouldVerify(iteration) {
|
||||
if (!this.enabled) return false;
|
||||
return iteration - this.lastVerification >= this.interval;
|
||||
}
|
||||
|
||||
async verify(matrix, solution, vector) {
|
||||
this.lastVerification = Date.now();
|
||||
|
||||
try {
|
||||
// Generate random probes
|
||||
const probes = this.generateProbes(solution.length);
|
||||
const errors = [];
|
||||
|
||||
// Test each probe
|
||||
for (const probe of probes) {
|
||||
const computed = this.computeMatrixVectorProbe(matrix, solution, probe.indices);
|
||||
const expected = probe.indices.map(idx => vector[idx]);
|
||||
|
||||
const error = this.computeError(computed, expected);
|
||||
errors.push(error);
|
||||
}
|
||||
|
||||
const maxError = Math.max(...errors);
|
||||
const meanError = errors.reduce((a, b) => a + b) / errors.length;
|
||||
const verified = maxError < this.tolerance;
|
||||
|
||||
const result = {
|
||||
verified,
|
||||
maxError,
|
||||
meanError,
|
||||
probeCount: probes.length,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
this.verificationHistory.push(result);
|
||||
|
||||
// Keep only recent history
|
||||
if (this.verificationHistory.length > 100) {
|
||||
this.verificationHistory.shift();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Verification error:', error);
|
||||
return {
|
||||
verified: false,
|
||||
error: error.message,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
generateProbes(vectorLength) {
|
||||
const probes = [];
|
||||
|
||||
for (let i = 0; i < this.probeCount; i++) {
|
||||
const probeSize = Math.min(50, Math.floor(vectorLength * 0.1));
|
||||
const indices = [];
|
||||
|
||||
for (let j = 0; j < probeSize; j++) {
|
||||
const idx = Math.floor(Math.random() * vectorLength);
|
||||
if (!indices.includes(idx)) {
|
||||
indices.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
probes.push({ indices: indices.sort() });
|
||||
}
|
||||
|
||||
return probes;
|
||||
}
|
||||
|
||||
computeMatrixVectorProbe(matrix, vector, indices) {
|
||||
// Simplified matrix-vector multiplication for probe indices
|
||||
const result = [];
|
||||
|
||||
for (const rowIdx of indices) {
|
||||
let sum = 0;
|
||||
|
||||
if (matrix.format === 'coo') {
|
||||
// Coordinate format
|
||||
for (let i = 0; i < matrix.data.values.length; i++) {
|
||||
if (matrix.data.rowIndices[i] === rowIdx) {
|
||||
const col = matrix.data.colIndices[i];
|
||||
const val = matrix.data.values[i];
|
||||
sum += val * vector[col];
|
||||
}
|
||||
}
|
||||
} else if (matrix.format === 'dense') {
|
||||
// Dense format
|
||||
for (let col = 0; col < matrix.cols; col++) {
|
||||
sum += matrix.data[rowIdx][col] * vector[col];
|
||||
}
|
||||
}
|
||||
|
||||
result.push(sum);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
computeError(computed, expected) {
|
||||
let maxError = 0;
|
||||
|
||||
for (let i = 0; i < computed.length; i++) {
|
||||
const error = Math.abs(computed[i] - expected[i]);
|
||||
maxError = Math.max(maxError, error);
|
||||
}
|
||||
|
||||
return maxError;
|
||||
}
|
||||
|
||||
getVerificationStats() {
|
||||
if (this.verificationHistory.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const recent = this.verificationHistory.slice(-10);
|
||||
const verificationRate = recent.filter(v => v.verified).length / recent.length;
|
||||
const avgError = recent.reduce((sum, v) => sum + (v.meanError || 0), 0) / recent.length;
|
||||
|
||||
return {
|
||||
verificationRate,
|
||||
avgError,
|
||||
recentVerifications: recent.length,
|
||||
totalVerifications: this.verificationHistory.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Session management utilities
|
||||
class SessionManager {
|
||||
constructor(config = {}) {
|
||||
this.sessions = new Map();
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
async createSession(sessionId, sessionData) {
|
||||
const session = {
|
||||
id: sessionId,
|
||||
...sessionData,
|
||||
status: 'created',
|
||||
createdAt: new Date().toISOString(),
|
||||
lastActivity: new Date().toISOString(),
|
||||
metrics: {
|
||||
iterations: 0,
|
||||
residual: Infinity,
|
||||
memoryUsage: 0
|
||||
}
|
||||
};
|
||||
|
||||
this.sessions.set(sessionId, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
getSession(sessionId) {
|
||||
return this.sessions.get(sessionId);
|
||||
}
|
||||
|
||||
updateSession(sessionId, updates) {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
Object.assign(session, updates);
|
||||
session.lastActivity = new Date().toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
deleteSession(sessionId) {
|
||||
this.sessions.delete(sessionId);
|
||||
}
|
||||
|
||||
getStats() {
|
||||
return {
|
||||
totalSessions: this.sessions.size,
|
||||
activeSessions: Array.from(this.sessions.values())
|
||||
.filter(s => s.status === 'running').length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
StreamingManager,
|
||||
SolverStream,
|
||||
VerificationLoop,
|
||||
SessionManager
|
||||
};
|
||||
Reference in New Issue
Block a user