517 lines
16 KiB
JavaScript
517 lines
16 KiB
JavaScript
/**
|
|
* Learning Module Lifecycle Simulation Tests
|
|
* Tests pattern storage, trajectory recording, spike attention, and multi-head routing
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
|
|
// Mock WASM module for testing
|
|
const createMockLearning = () => ({
|
|
ReasoningBank: class {
|
|
constructor() {
|
|
this.patterns = new Map();
|
|
this.nextId = 0;
|
|
}
|
|
|
|
store(patternJson) {
|
|
try {
|
|
const pattern = JSON.parse(patternJson);
|
|
const id = this.nextId++;
|
|
this.patterns.set(id, {
|
|
pattern,
|
|
usageCount: 0,
|
|
lastUsed: Date.now()
|
|
});
|
|
return id;
|
|
} catch {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
lookup(queryJson, k) {
|
|
try {
|
|
const query = JSON.parse(queryJson);
|
|
const results = [];
|
|
|
|
for (const [id, entry] of this.patterns.entries()) {
|
|
const similarity = this.cosineSimilarity(query, entry.pattern.centroid);
|
|
results.push({
|
|
id,
|
|
similarity,
|
|
confidence: entry.pattern.confidence,
|
|
optimal_allocation: entry.pattern.optimal_allocation,
|
|
optimal_energy: entry.pattern.optimal_energy
|
|
});
|
|
}
|
|
|
|
results.sort((a, b) => (b.similarity * b.confidence) - (a.similarity * a.confidence));
|
|
return JSON.stringify(results.slice(0, k));
|
|
} catch {
|
|
return '[]';
|
|
}
|
|
}
|
|
|
|
cosineSimilarity(a, b) {
|
|
if (a.length !== b.length) return 0;
|
|
let dot = 0, normA = 0, normB = 0;
|
|
for (let i = 0; i < a.length; i++) {
|
|
dot += a[i] * b[i];
|
|
normA += a[i] * a[i];
|
|
normB += b[i] * b[i];
|
|
}
|
|
normA = Math.sqrt(normA);
|
|
normB = Math.sqrt(normB);
|
|
return normA === 0 || normB === 0 ? 0 : dot / (normA * normB);
|
|
}
|
|
|
|
prune(minUsage, minConfidence) {
|
|
let removed = 0;
|
|
for (const [id, entry] of this.patterns.entries()) {
|
|
if (entry.usageCount < minUsage || entry.pattern.confidence < minConfidence) {
|
|
this.patterns.delete(id);
|
|
removed++;
|
|
}
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
count() {
|
|
return this.patterns.size;
|
|
}
|
|
|
|
getStats() {
|
|
if (this.patterns.size === 0) return '{"total":0}';
|
|
|
|
const entries = Array.from(this.patterns.values());
|
|
const totalSamples = entries.reduce((sum, e) => sum + e.pattern.sample_count, 0);
|
|
const avgConfidence = entries.reduce((sum, e) => sum + e.pattern.confidence, 0) / entries.length;
|
|
const totalUsage = entries.reduce((sum, e) => sum + e.usageCount, 0);
|
|
|
|
return JSON.stringify({
|
|
total_patterns: this.patterns.size,
|
|
total_samples: totalSamples,
|
|
avg_confidence: avgConfidence,
|
|
total_usage: totalUsage
|
|
});
|
|
}
|
|
},
|
|
|
|
TrajectoryTracker: class {
|
|
constructor(maxSize) {
|
|
this.trajectories = [];
|
|
this.maxSize = maxSize;
|
|
this.writePos = 0;
|
|
}
|
|
|
|
record(trajectoryJson) {
|
|
try {
|
|
const traj = JSON.parse(trajectoryJson);
|
|
if (this.trajectories.length < this.maxSize) {
|
|
this.trajectories.push(traj);
|
|
} else {
|
|
this.trajectories[this.writePos] = traj;
|
|
}
|
|
this.writePos = (this.writePos + 1) % this.maxSize;
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
getStats() {
|
|
if (this.trajectories.length === 0) return '{"total":0}';
|
|
|
|
const total = this.trajectories.length;
|
|
const successful = this.trajectories.filter(t => t.success).length;
|
|
const avgLatency = this.trajectories.reduce((sum, t) => sum + t.latency_ms, 0) / total;
|
|
const avgEfficiency = this.trajectories.reduce((sum, t) => {
|
|
return sum + (t.energy_spent === 0 ? 0 : t.energy_earned / t.energy_spent);
|
|
}, 0) / total;
|
|
|
|
return JSON.stringify({
|
|
total,
|
|
successful,
|
|
success_rate: successful / total,
|
|
avg_latency_ms: avgLatency,
|
|
avg_efficiency: avgEfficiency
|
|
});
|
|
}
|
|
|
|
count() {
|
|
return this.trajectories.length;
|
|
}
|
|
},
|
|
|
|
SpikeDrivenAttention: class {
|
|
energyRatio(seqLen, hiddenDim) {
|
|
if (seqLen === 0 || hiddenDim === 0) return 1.0;
|
|
|
|
const standardMults = 2 * seqLen * seqLen * hiddenDim;
|
|
const avgSpikesPerNeuron = 8 * 0.3;
|
|
const spikeAdds = seqLen * avgSpikesPerNeuron * hiddenDim;
|
|
const multEnergyFactor = 3.7;
|
|
|
|
const standardEnergy = standardMults * multEnergyFactor;
|
|
const spikeEnergy = spikeAdds;
|
|
|
|
return spikeEnergy === 0 ? 1.0 : standardEnergy / spikeEnergy;
|
|
}
|
|
},
|
|
|
|
MultiHeadAttention: class {
|
|
constructor(dim, numHeads) {
|
|
this.dimValue = dim;
|
|
this.numHeadsValue = numHeads;
|
|
}
|
|
|
|
dim() { return this.dimValue; }
|
|
numHeads() { return this.numHeadsValue; }
|
|
},
|
|
|
|
NetworkLearning: class {
|
|
constructor() {
|
|
const mocks = createMockLearning();
|
|
this.bank = new mocks.ReasoningBank();
|
|
this.tracker = new mocks.TrajectoryTracker(1000);
|
|
this.spike = new mocks.SpikeDrivenAttention();
|
|
this.attention = new mocks.MultiHeadAttention(64, 4);
|
|
}
|
|
|
|
recordTrajectory(json) { return this.tracker.record(json); }
|
|
storePattern(json) { return this.bank.store(json); }
|
|
lookupPatterns(json, k) { return this.bank.lookup(json, k); }
|
|
getEnergyRatio(seq, hidden) { return this.spike.energyRatio(seq, hidden); }
|
|
|
|
getStats() {
|
|
const bankStats = this.bank.getStats();
|
|
const trajStats = this.tracker.getStats();
|
|
const energyRatio = this.spike.energyRatio(64, 256);
|
|
|
|
return JSON.stringify({
|
|
reasoning_bank: JSON.parse(bankStats),
|
|
trajectories: JSON.parse(trajStats),
|
|
spike_energy_ratio: energyRatio,
|
|
learning_rate: 0.01
|
|
});
|
|
}
|
|
|
|
trajectoryCount() { return this.tracker.count(); }
|
|
patternCount() { return this.bank.count(); }
|
|
prune(minUsage, minConf) { return this.bank.prune(minUsage, minConf); }
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Test 1: Pattern Storage and Retrieval Cycles
|
|
*/
|
|
function testPatternStorageRetrieval() {
|
|
console.log('\n=== Test 1: Pattern Storage and Retrieval Cycles ===');
|
|
|
|
const wasm = createMockLearning();
|
|
const learning = new wasm.NetworkLearning();
|
|
|
|
const patterns = [
|
|
{
|
|
centroid: [1.0, 0.0, 0.0],
|
|
optimal_allocation: 0.8,
|
|
optimal_energy: 100,
|
|
confidence: 0.9,
|
|
sample_count: 10,
|
|
avg_latency_ms: 50.0,
|
|
avg_success_rate: 0.95
|
|
},
|
|
{
|
|
centroid: [0.0, 1.0, 0.0],
|
|
optimal_allocation: 0.7,
|
|
optimal_energy: 120,
|
|
confidence: 0.85,
|
|
sample_count: 8,
|
|
avg_latency_ms: 60.0,
|
|
avg_success_rate: 0.90
|
|
},
|
|
{
|
|
centroid: [0.707, 0.707, 0.0],
|
|
optimal_allocation: 0.75,
|
|
optimal_energy: 110,
|
|
confidence: 0.88,
|
|
sample_count: 9,
|
|
avg_latency_ms: 55.0,
|
|
avg_success_rate: 0.92
|
|
}
|
|
];
|
|
|
|
// Store patterns
|
|
const ids = patterns.map(p => learning.storePattern(JSON.stringify(p)));
|
|
console.log(`✓ Stored ${ids.length} patterns`);
|
|
assert.strictEqual(learning.patternCount(), 3);
|
|
|
|
// Lookup similar patterns
|
|
const query = [0.9, 0.1, 0.0];
|
|
const results = JSON.parse(learning.lookupPatterns(JSON.stringify(query), 2));
|
|
console.log(`✓ Retrieved ${results.length} similar patterns`);
|
|
assert.strictEqual(results.length, 2);
|
|
assert.ok(results[0].similarity > results[1].similarity);
|
|
|
|
// Verify pattern quality
|
|
const stats = JSON.parse(learning.getStats());
|
|
console.log(`✓ Pattern bank stats:`, stats.reasoning_bank);
|
|
assert.strictEqual(stats.reasoning_bank.total_patterns, 3);
|
|
assert.ok(stats.reasoning_bank.avg_confidence > 0.8);
|
|
|
|
console.log('✅ Pattern Storage and Retrieval Test PASSED');
|
|
return {
|
|
patterns_stored: ids.length,
|
|
retrieval_accuracy: results[0].similarity,
|
|
avg_confidence: stats.reasoning_bank.avg_confidence
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 2: Trajectory Recording and Analysis
|
|
*/
|
|
function testTrajectoryRecording() {
|
|
console.log('\n=== Test 2: Trajectory Recording and Analysis ===');
|
|
|
|
const wasm = createMockLearning();
|
|
const learning = new wasm.NetworkLearning();
|
|
|
|
// Record diverse trajectories
|
|
const trajectories = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
const success = Math.random() > 0.2; // 80% success rate
|
|
const traj = {
|
|
task_vector: Array(16).fill(0).map(() => Math.random()),
|
|
latency_ms: 50 + Math.random() * 100,
|
|
energy_spent: 50 + Math.floor(Math.random() * 50),
|
|
energy_earned: success ? 100 + Math.floor(Math.random() * 50) : 0,
|
|
success,
|
|
executor_id: `node-${i % 10}`,
|
|
timestamp: Date.now() + i * 1000
|
|
};
|
|
trajectories.push(traj);
|
|
learning.recordTrajectory(JSON.stringify(traj));
|
|
}
|
|
|
|
console.log(`✓ Recorded ${trajectories.length} trajectories`);
|
|
assert.strictEqual(learning.trajectoryCount(), 100);
|
|
|
|
// Analyze statistics
|
|
const stats = JSON.parse(learning.getStats());
|
|
const trajStats = stats.trajectories;
|
|
console.log(`✓ Trajectory stats:`, trajStats);
|
|
|
|
assert.ok(trajStats.success_rate > 0.7);
|
|
assert.ok(trajStats.avg_latency_ms > 50 && trajStats.avg_latency_ms < 150);
|
|
assert.ok(trajStats.avg_efficiency > 1.0);
|
|
|
|
console.log('✅ Trajectory Recording Test PASSED');
|
|
return {
|
|
total_trajectories: trajStats.total,
|
|
success_rate: trajStats.success_rate,
|
|
avg_efficiency: trajStats.avg_efficiency
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Test 3: Spike-Driven Attention Energy Efficiency
|
|
*/
|
|
function testSpikeAttentionEnergy() {
|
|
console.log('\n=== Test 3: Spike-Driven Attention Energy Efficiency ===');
|
|
|
|
const wasm = createMockLearning();
|
|
const learning = new wasm.NetworkLearning();
|
|
|
|
const testCases = [
|
|
{ seqLen: 64, hiddenDim: 256, expectedMin: 50, expectedMax: 250 },
|
|
{ seqLen: 128, hiddenDim: 512, expectedMin: 70, expectedMax: 500 },
|
|
{ seqLen: 32, hiddenDim: 128, expectedMin: 40, expectedMax: 150 }
|
|
];
|
|
|
|
const results = testCases.map(tc => {
|
|
const ratio = learning.getEnergyRatio(tc.seqLen, tc.hiddenDim);
|
|
console.log(`✓ Seq=${tc.seqLen}, Hidden=${tc.hiddenDim}: ${ratio.toFixed(2)}x energy savings`);
|
|
|
|
assert.ok(ratio >= tc.expectedMin, `Expected >= ${tc.expectedMin}, got ${ratio}`);
|
|
assert.ok(ratio <= tc.expectedMax, `Expected <= ${tc.expectedMax}, got ${ratio}`);
|
|
|
|
return { seqLen: tc.seqLen, hiddenDim: tc.hiddenDim, ratio };
|
|
});
|
|
|
|
// Verify edge cases
|
|
const emptyRatio = learning.getEnergyRatio(0, 0);
|
|
assert.strictEqual(emptyRatio, 1.0);
|
|
console.log('✓ Empty case handled correctly');
|
|
|
|
console.log('✅ Spike Attention Energy Test PASSED');
|
|
return { energy_savings: results };
|
|
}
|
|
|
|
/**
|
|
* Test 4: Multi-Head Attention Task Routing
|
|
*/
|
|
function testMultiHeadRouting() {
|
|
console.log('\n=== Test 4: Multi-Head Attention Task Routing ===');
|
|
|
|
const wasm = createMockLearning();
|
|
const attention = new wasm.MultiHeadAttention(64, 4);
|
|
|
|
assert.strictEqual(attention.dim(), 64);
|
|
assert.strictEqual(attention.numHeads(), 4);
|
|
console.log(`✓ Multi-head attention: ${attention.numHeads()} heads, ${attention.dim()} dims`);
|
|
|
|
// Test different configurations
|
|
const configs = [
|
|
{ dim: 128, heads: 8 },
|
|
{ dim: 256, heads: 16 },
|
|
{ dim: 512, heads: 32 }
|
|
];
|
|
|
|
configs.forEach(cfg => {
|
|
const attn = new wasm.MultiHeadAttention(cfg.dim, cfg.heads);
|
|
assert.strictEqual(attn.dim(), cfg.dim);
|
|
assert.strictEqual(attn.numHeads(), cfg.heads);
|
|
console.log(`✓ Config validated: ${cfg.heads} heads x ${cfg.dim} dims`);
|
|
});
|
|
|
|
console.log('✅ Multi-Head Routing Test PASSED');
|
|
return { configurations_tested: configs.length };
|
|
}
|
|
|
|
/**
|
|
* Test 5: Pattern Pruning and Memory Management
|
|
*/
|
|
function testPatternPruning() {
|
|
console.log('\n=== Test 5: Pattern Pruning and Memory Management ===');
|
|
|
|
const wasm = createMockLearning();
|
|
const learning = new wasm.NetworkLearning();
|
|
|
|
// Store high and low quality patterns
|
|
const patterns = [
|
|
{ centroid: [1, 0, 0], optimal_allocation: 0.9, optimal_energy: 100, confidence: 0.95, sample_count: 20, avg_latency_ms: 50, avg_success_rate: 0.98 },
|
|
{ centroid: [0, 1, 0], optimal_allocation: 0.5, optimal_energy: 100, confidence: 0.4, sample_count: 2, avg_latency_ms: 200, avg_success_rate: 0.5 },
|
|
{ centroid: [0, 0, 1], optimal_allocation: 0.3, optimal_energy: 100, confidence: 0.3, sample_count: 1, avg_latency_ms: 300, avg_success_rate: 0.3 }
|
|
];
|
|
|
|
patterns.forEach(p => learning.storePattern(JSON.stringify(p)));
|
|
console.log(`✓ Stored ${learning.patternCount()} patterns (mixed quality)`);
|
|
|
|
// Prune low quality patterns
|
|
const pruned = learning.prune(5, 0.5);
|
|
console.log(`✓ Pruned ${pruned} low-quality patterns`);
|
|
|
|
assert.ok(pruned >= 1);
|
|
assert.ok(learning.patternCount() < patterns.length);
|
|
|
|
console.log('✅ Pattern Pruning Test PASSED');
|
|
return { patterns_pruned: pruned, patterns_remaining: learning.patternCount() };
|
|
}
|
|
|
|
/**
|
|
* Test 6: High-Throughput Learning Pipeline
|
|
*/
|
|
function testHighThroughputLearning() {
|
|
console.log('\n=== Test 6: High-Throughput Learning Pipeline ===');
|
|
|
|
const wasm = createMockLearning();
|
|
const learning = new wasm.NetworkLearning();
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Simulate high-throughput scenario
|
|
const trajCount = 1000;
|
|
const patternCount = 100;
|
|
|
|
for (let i = 0; i < trajCount; i++) {
|
|
learning.recordTrajectory(JSON.stringify({
|
|
task_vector: [Math.random(), Math.random(), Math.random()],
|
|
latency_ms: 50 + Math.random() * 50,
|
|
energy_spent: 50,
|
|
energy_earned: Math.random() > 0.2 ? 100 : 0,
|
|
success: Math.random() > 0.2,
|
|
executor_id: `node-${i % 10}`,
|
|
timestamp: Date.now() + i
|
|
}));
|
|
}
|
|
|
|
for (let i = 0; i < patternCount; i++) {
|
|
learning.storePattern(JSON.stringify({
|
|
centroid: [Math.random(), Math.random(), Math.random()],
|
|
optimal_allocation: 0.5 + Math.random() * 0.5,
|
|
optimal_energy: 100,
|
|
confidence: 0.5 + Math.random() * 0.5,
|
|
sample_count: 5 + Math.floor(Math.random() * 15),
|
|
avg_latency_ms: 50 + Math.random() * 100,
|
|
avg_success_rate: 0.7 + Math.random() * 0.3
|
|
}));
|
|
}
|
|
|
|
const duration = Date.now() - startTime;
|
|
const throughput = (trajCount + patternCount) / (duration / 1000);
|
|
|
|
console.log(`✓ Processed ${trajCount} trajectories + ${patternCount} patterns in ${duration}ms`);
|
|
console.log(`✓ Throughput: ${throughput.toFixed(2)} ops/sec`);
|
|
|
|
assert.strictEqual(learning.trajectoryCount(), trajCount);
|
|
assert.strictEqual(learning.patternCount(), patternCount);
|
|
|
|
console.log('✅ High-Throughput Learning Test PASSED');
|
|
return { throughput_ops_per_sec: throughput, duration_ms: duration };
|
|
}
|
|
|
|
/**
|
|
* Run all learning lifecycle tests
|
|
*/
|
|
function runLearningTests() {
|
|
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
console.log('║ Learning Module Lifecycle Simulation Tests ║');
|
|
console.log('╚══════════════════════════════════════════════════════╝');
|
|
|
|
const results = {
|
|
timestamp: new Date().toISOString(),
|
|
test_suite: 'learning_lifecycle',
|
|
tests: {}
|
|
};
|
|
|
|
try {
|
|
results.tests.pattern_storage = testPatternStorageRetrieval();
|
|
results.tests.trajectory_recording = testTrajectoryRecording();
|
|
results.tests.spike_attention = testSpikeAttentionEnergy();
|
|
results.tests.multi_head_routing = testMultiHeadRouting();
|
|
results.tests.pattern_pruning = testPatternPruning();
|
|
results.tests.high_throughput = testHighThroughputLearning();
|
|
|
|
results.summary = {
|
|
total_tests: 6,
|
|
passed: 6,
|
|
failed: 0,
|
|
success_rate: 1.0
|
|
};
|
|
|
|
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
console.log('║ All Learning Lifecycle Tests PASSED ✅ ║');
|
|
console.log('╚══════════════════════════════════════════════════════╝\n');
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Test failed:', error.message);
|
|
console.error(error.stack);
|
|
results.summary = { total_tests: 6, passed: 0, failed: 1, error: error.message };
|
|
process.exit(1);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
const results = runLearningTests();
|
|
const fs = require('fs');
|
|
fs.writeFileSync(
|
|
'./reports/learning-lifecycle-results.json',
|
|
JSON.stringify(results, null, 2)
|
|
);
|
|
console.log('📊 Results saved to: reports/learning-lifecycle-results.json');
|
|
}
|
|
|
|
module.exports = { runLearningTests, createMockLearning };
|