528 lines
18 KiB
Rust
528 lines
18 KiB
Rust
//! Integration tests for ruvector-nervous-system-wasm
|
|
//!
|
|
//! Tests for bio-inspired neural components:
|
|
//! - HDC (Hyperdimensional Computing)
|
|
//! - BTSP (Behavioral Time-Scale Plasticity)
|
|
//! - Spiking Neural Networks
|
|
//! - Neuromorphic processing primitives
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use wasm_bindgen_test::*;
|
|
use super::super::common::*;
|
|
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
// ========================================================================
|
|
// HDC (Hyperdimensional Computing) Tests
|
|
// ========================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_hdc_vector_encoding() {
|
|
// Test hypervector encoding
|
|
let dim = 10000; // HDC typically uses very high dimensions
|
|
|
|
// TODO: When HDC is implemented:
|
|
// let encoder = HDCEncoder::new(dim);
|
|
//
|
|
// // Encode a symbol
|
|
// let hv_a = encoder.encode_symbol("A");
|
|
// let hv_b = encoder.encode_symbol("B");
|
|
//
|
|
// // Should be orthogonal (low similarity)
|
|
// let similarity = cosine_similarity(&hv_a, &hv_b);
|
|
// assert!(similarity.abs() < 0.1, "Random HVs should be near-orthogonal");
|
|
//
|
|
// // Same symbol should produce same vector
|
|
// let hv_a2 = encoder.encode_symbol("A");
|
|
// assert_vectors_approx_eq(&hv_a, &hv_a2, 1e-6);
|
|
|
|
assert!(dim >= 1000);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_hdc_bundling() {
|
|
// Test bundling (element-wise addition) operation
|
|
let dim = 10000;
|
|
|
|
// TODO: Test bundling
|
|
// let encoder = HDCEncoder::new(dim);
|
|
//
|
|
// let hv_a = encoder.encode_symbol("A");
|
|
// let hv_b = encoder.encode_symbol("B");
|
|
// let hv_c = encoder.encode_symbol("C");
|
|
//
|
|
// // Bundle A, B, C
|
|
// let bundled = HDC::bundle(&[&hv_a, &hv_b, &hv_c]);
|
|
//
|
|
// // Bundled vector should be similar to all components
|
|
// assert!(cosine_similarity(&bundled, &hv_a) > 0.3);
|
|
// assert!(cosine_similarity(&bundled, &hv_b) > 0.3);
|
|
// assert!(cosine_similarity(&bundled, &hv_c) > 0.3);
|
|
|
|
assert!(dim > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_hdc_binding() {
|
|
// Test binding (element-wise XOR or multiplication) operation
|
|
let dim = 10000;
|
|
|
|
// TODO: Test binding
|
|
// let encoder = HDCEncoder::new(dim);
|
|
//
|
|
// let hv_a = encoder.encode_symbol("A");
|
|
// let hv_b = encoder.encode_symbol("B");
|
|
//
|
|
// // Bind A with B
|
|
// let bound = HDC::bind(&hv_a, &hv_b);
|
|
//
|
|
// // Bound vector should be orthogonal to both components
|
|
// assert!(cosine_similarity(&bound, &hv_a).abs() < 0.1);
|
|
// assert!(cosine_similarity(&bound, &hv_b).abs() < 0.1);
|
|
//
|
|
// // Unbinding should recover original
|
|
// let recovered = HDC::bind(&bound, &hv_b); // bind is its own inverse
|
|
// assert!(cosine_similarity(&recovered, &hv_a) > 0.9);
|
|
|
|
assert!(dim > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_hdc_permutation() {
|
|
// Test permutation for sequence encoding
|
|
let dim = 10000;
|
|
|
|
// TODO: Test permutation
|
|
// let encoder = HDCEncoder::new(dim);
|
|
//
|
|
// let hv_a = encoder.encode_symbol("A");
|
|
//
|
|
// // Permute by position 1, 2, 3
|
|
// let hv_a_pos1 = HDC::permute(&hv_a, 1);
|
|
// let hv_a_pos2 = HDC::permute(&hv_a, 2);
|
|
//
|
|
// // Permuted vectors should be orthogonal to original
|
|
// assert!(cosine_similarity(&hv_a, &hv_a_pos1).abs() < 0.1);
|
|
//
|
|
// // Inverse permutation should recover original
|
|
// let recovered = HDC::permute_inverse(&hv_a_pos1, 1);
|
|
// assert_vectors_approx_eq(&hv_a, &recovered, 1e-6);
|
|
|
|
assert!(dim > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_hdc_associative_memory() {
|
|
// Test HDC as associative memory
|
|
let dim = 10000;
|
|
|
|
// TODO: Test associative memory
|
|
// let mut memory = HDCAssociativeMemory::new(dim);
|
|
//
|
|
// // Store key-value pairs
|
|
// let key1 = random_vector(dim);
|
|
// let value1 = random_vector(dim);
|
|
// memory.store(&key1, &value1);
|
|
//
|
|
// let key2 = random_vector(dim);
|
|
// let value2 = random_vector(dim);
|
|
// memory.store(&key2, &value2);
|
|
//
|
|
// // Retrieve by key
|
|
// let retrieved1 = memory.retrieve(&key1);
|
|
// assert!(cosine_similarity(&retrieved1, &value1) > 0.8);
|
|
//
|
|
// // Noisy key should still retrieve correct value
|
|
// let noisy_key1: Vec<f32> = key1.iter()
|
|
// .map(|x| x + (rand::random::<f32>() - 0.5) * 0.1)
|
|
// .collect();
|
|
// let retrieved_noisy = memory.retrieve(&noisy_key1);
|
|
// assert!(cosine_similarity(&retrieved_noisy, &value1) > 0.6);
|
|
|
|
assert!(dim > 0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// BTSP (Behavioral Time-Scale Plasticity) Tests
|
|
// ========================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_btsp_basic() {
|
|
// Test BTSP learning rule
|
|
let num_inputs = 100;
|
|
let num_outputs = 10;
|
|
|
|
// TODO: When BTSP is implemented:
|
|
// let mut btsp = BTSPNetwork::new(num_inputs, num_outputs);
|
|
//
|
|
// // Present input pattern
|
|
// let input = random_vector(num_inputs);
|
|
// let output = btsp.forward(&input);
|
|
//
|
|
// // Apply eligibility trace
|
|
// btsp.update_eligibility(&input);
|
|
//
|
|
// // Apply behavioral signal (reward/plateau potential)
|
|
// btsp.apply_behavioral_signal(1.0);
|
|
//
|
|
// // Weights should be modified
|
|
// let output_after = btsp.forward(&input);
|
|
//
|
|
// // Output should change due to learning
|
|
// let diff: f32 = output.iter().zip(output_after.iter())
|
|
// .map(|(a, b)| (a - b).abs())
|
|
// .sum();
|
|
// assert!(diff > 0.01, "BTSP should modify network");
|
|
|
|
assert!(num_inputs > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_btsp_eligibility_trace() {
|
|
// Test eligibility trace dynamics
|
|
let num_inputs = 50;
|
|
|
|
// TODO: Test eligibility trace
|
|
// let mut btsp = BTSPNetwork::new(num_inputs, 10);
|
|
//
|
|
// // Present input
|
|
// let input = random_vector(num_inputs);
|
|
// btsp.update_eligibility(&input);
|
|
//
|
|
// let trace_t0 = btsp.get_eligibility_trace();
|
|
//
|
|
// // Trace should decay over time
|
|
// btsp.step_time(10);
|
|
// let trace_t10 = btsp.get_eligibility_trace();
|
|
//
|
|
// let trace_t0_norm: f32 = trace_t0.iter().map(|x| x * x).sum();
|
|
// let trace_t10_norm: f32 = trace_t10.iter().map(|x| x * x).sum();
|
|
//
|
|
// assert!(trace_t10_norm < trace_t0_norm, "Eligibility should decay");
|
|
|
|
assert!(num_inputs > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_btsp_one_shot_learning() {
|
|
// BTSP should enable one-shot learning with plateau potential
|
|
let num_inputs = 100;
|
|
let num_outputs = 10;
|
|
|
|
// TODO: Test one-shot learning
|
|
// let mut btsp = BTSPNetwork::new(num_inputs, num_outputs);
|
|
//
|
|
// // Input pattern
|
|
// let input = random_vector(num_inputs);
|
|
//
|
|
// // Target activation
|
|
// let target_output = 5; // Activate neuron 5
|
|
//
|
|
// // One-shot learning: present input + apply plateau to target
|
|
// btsp.forward(&input);
|
|
// btsp.update_eligibility(&input);
|
|
// btsp.apply_plateau_potential(target_output, 1.0);
|
|
//
|
|
// // Clear state
|
|
// btsp.reset_state();
|
|
//
|
|
// // Re-present input
|
|
// let output = btsp.forward(&input);
|
|
//
|
|
// // Target neuron should be more active
|
|
// let target_activity = output[target_output];
|
|
// let other_max = output.iter()
|
|
// .enumerate()
|
|
// .filter(|(i, _)| *i != target_output)
|
|
// .map(|(_, v)| *v)
|
|
// .fold(f32::NEG_INFINITY, f32::max);
|
|
//
|
|
// assert!(target_activity > other_max, "Target should be most active after one-shot learning");
|
|
|
|
assert!(num_outputs > 0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Spiking Neural Network Tests
|
|
// ========================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_spiking_neuron_lif() {
|
|
// Test Leaky Integrate-and-Fire neuron
|
|
let threshold = 1.0;
|
|
let tau_m = 10.0; // Membrane time constant
|
|
|
|
// TODO: When SNN is implemented:
|
|
// let mut lif = LIFNeuron::new(threshold, tau_m);
|
|
//
|
|
// // Sub-threshold input should not spike
|
|
// lif.inject_current(0.5);
|
|
// for _ in 0..10 {
|
|
// let spike = lif.step(1.0);
|
|
// assert!(!spike, "Should not spike below threshold");
|
|
// }
|
|
//
|
|
// // Super-threshold input should spike
|
|
// lif.reset();
|
|
// lif.inject_current(2.0);
|
|
// let mut spiked = false;
|
|
// for _ in 0..20 {
|
|
// if lif.step(1.0) {
|
|
// spiked = true;
|
|
// break;
|
|
// }
|
|
// }
|
|
// assert!(spiked, "Should spike above threshold");
|
|
|
|
assert!(threshold > 0.0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_spiking_network_propagation() {
|
|
// Test spike propagation through network
|
|
let num_layers = 3;
|
|
let neurons_per_layer = 10;
|
|
|
|
// TODO: Test spike propagation
|
|
// let mut network = SpikingNetwork::new(&[
|
|
// neurons_per_layer,
|
|
// neurons_per_layer,
|
|
// neurons_per_layer,
|
|
// ]);
|
|
//
|
|
// // Inject strong current into first layer
|
|
// network.inject_current(0, vec![2.0; neurons_per_layer]);
|
|
//
|
|
// // Run for several timesteps
|
|
// let mut layer_spikes = vec![vec![]; num_layers];
|
|
// for t in 0..50 {
|
|
// let spikes = network.step(1.0);
|
|
// for (layer, layer_spikes_t) in spikes.iter().enumerate() {
|
|
// if layer_spikes_t.iter().any(|&s| s) {
|
|
// layer_spikes[layer].push(t);
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// // Spikes should propagate through layers
|
|
// assert!(!layer_spikes[0].is_empty(), "First layer should spike");
|
|
// assert!(!layer_spikes[2].is_empty(), "Output layer should receive spikes");
|
|
//
|
|
// // Output layer should spike after input layer
|
|
// if !layer_spikes[2].is_empty() {
|
|
// assert!(layer_spikes[2][0] > layer_spikes[0][0],
|
|
// "Causality: output should spike after input");
|
|
// }
|
|
|
|
assert!(num_layers > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_stdp_learning() {
|
|
// Test Spike-Timing-Dependent Plasticity
|
|
let a_plus = 0.01; // Potentiation coefficient
|
|
let a_minus = 0.01; // Depression coefficient
|
|
let tau = 20.0; // Time constant
|
|
|
|
// TODO: Test STDP
|
|
// let mut stdp = STDPRule::new(a_plus, a_minus, tau);
|
|
//
|
|
// let initial_weight = 0.5;
|
|
//
|
|
// // Pre before post (potentiation)
|
|
// let pre_spike_time = 0.0;
|
|
// let post_spike_time = 10.0;
|
|
// let delta_w = stdp.compute_weight_change(pre_spike_time, post_spike_time);
|
|
// assert!(delta_w > 0.0, "Pre-before-post should potentiate");
|
|
//
|
|
// // Post before pre (depression)
|
|
// let pre_spike_time = 10.0;
|
|
// let post_spike_time = 0.0;
|
|
// let delta_w = stdp.compute_weight_change(pre_spike_time, post_spike_time);
|
|
// assert!(delta_w < 0.0, "Post-before-pre should depress");
|
|
|
|
assert!(tau > 0.0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_spiking_temporal_coding() {
|
|
// Test rate vs temporal coding
|
|
let num_neurons = 10;
|
|
|
|
// TODO: Test temporal coding
|
|
// let mut network = SpikingNetwork::temporal_coding(num_neurons);
|
|
//
|
|
// // Encode value as spike time (earlier = higher value)
|
|
// let values: Vec<f32> = (0..num_neurons).map(|i| (i as f32) / (num_neurons as f32)).collect();
|
|
// network.encode_temporal(&values);
|
|
//
|
|
// // Run and record spike times
|
|
// let mut spike_times = vec![f32::INFINITY; num_neurons];
|
|
// for t in 0..100 {
|
|
// let spikes = network.step(1.0);
|
|
// for (i, &spiked) in spikes.iter().enumerate() {
|
|
// if spiked && spike_times[i] == f32::INFINITY {
|
|
// spike_times[i] = t as f32;
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// // Higher values should spike earlier
|
|
// for i in 1..num_neurons {
|
|
// if spike_times[i] < f32::INFINITY && spike_times[i-1] < f32::INFINITY {
|
|
// assert!(spike_times[i] < spike_times[i-1],
|
|
// "Higher value should spike earlier");
|
|
// }
|
|
// }
|
|
|
|
assert!(num_neurons > 0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Neuromorphic Processing Tests
|
|
// ========================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_neuromorphic_attention() {
|
|
// Test neuromorphic attention mechanism
|
|
let dim = 64;
|
|
let num_heads = 4;
|
|
|
|
// TODO: Test neuromorphic attention
|
|
// let attention = NeuromorphicAttention::new(dim, num_heads);
|
|
//
|
|
// let query = random_vector(dim);
|
|
// let keys: Vec<Vec<f32>> = (0..10).map(|_| random_vector(dim)).collect();
|
|
// let values: Vec<Vec<f32>> = (0..10).map(|_| random_vector(dim)).collect();
|
|
//
|
|
// let output = attention.forward(&query, &keys, &values);
|
|
//
|
|
// assert_eq!(output.len(), dim);
|
|
// assert_finite(&output);
|
|
|
|
assert!(dim > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_reservoir_computing() {
|
|
// Test Echo State Network / Reservoir Computing
|
|
let input_dim = 10;
|
|
let reservoir_size = 100;
|
|
let output_dim = 5;
|
|
|
|
// TODO: Test reservoir
|
|
// let reservoir = ReservoirComputer::new(input_dim, reservoir_size, output_dim);
|
|
//
|
|
// // Run sequence through reservoir
|
|
// let sequence: Vec<Vec<f32>> = (0..50).map(|_| random_vector(input_dim)).collect();
|
|
//
|
|
// for input in &sequence {
|
|
// reservoir.step(input);
|
|
// }
|
|
//
|
|
// // Get reservoir state
|
|
// let state = reservoir.get_state();
|
|
// assert_eq!(state.len(), reservoir_size);
|
|
// assert_finite(&state);
|
|
//
|
|
// // Train readout
|
|
// let targets: Vec<Vec<f32>> = (0..50).map(|_| random_vector(output_dim)).collect();
|
|
// reservoir.train_readout(&targets);
|
|
//
|
|
// // Get output
|
|
// let output = reservoir.predict();
|
|
// assert_eq!(output.len(), output_dim);
|
|
|
|
assert!(reservoir_size > 0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Integration Tests
|
|
// ========================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_hdc_snn_integration() {
|
|
// Test using HDC with SNN for efficient inference
|
|
let hd_dim = 1000;
|
|
let num_classes = 10;
|
|
|
|
// TODO: Test HDC + SNN integration
|
|
// let encoder = HDCEncoder::new(hd_dim);
|
|
// let classifier = HDCClassifier::new(hd_dim, num_classes);
|
|
//
|
|
// // Convert to spiking
|
|
// let snn = classifier.to_spiking();
|
|
//
|
|
// // Encode and classify with SNN
|
|
// let input = random_vector(hd_dim);
|
|
// let encoded = encoder.encode(&input);
|
|
//
|
|
// let output = snn.forward(&encoded);
|
|
// assert_eq!(output.len(), num_classes);
|
|
|
|
assert!(num_classes > 0);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_energy_efficiency() {
|
|
// Neuromorphic should be more energy efficient (fewer operations)
|
|
let dim = 64;
|
|
let seq_len = 100;
|
|
|
|
// TODO: Compare operation counts
|
|
// let standard_attention = StandardAttention::new(dim);
|
|
// let neuromorphic_attention = NeuromorphicAttention::new(dim, 4);
|
|
//
|
|
// let queries = (0..seq_len).map(|_| random_vector(dim)).collect();
|
|
// let keys = (0..seq_len).map(|_| random_vector(dim)).collect();
|
|
//
|
|
// let standard_ops = standard_attention.count_operations(&queries, &keys);
|
|
// let neuro_ops = neuromorphic_attention.count_operations(&queries, &keys);
|
|
//
|
|
// // Neuromorphic should use fewer ops (event-driven)
|
|
// assert!(neuro_ops < standard_ops,
|
|
// "Neuromorphic should be more efficient: {} vs {}", neuro_ops, standard_ops);
|
|
|
|
assert!(seq_len > 0);
|
|
}
|
|
|
|
// ========================================================================
|
|
// WASM-Specific Tests
|
|
// ========================================================================
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_nervous_system_wasm_initialization() {
|
|
// Test WASM module initialization
|
|
// TODO: Verify init
|
|
// ruvector_nervous_system_wasm::init();
|
|
// assert!(ruvector_nervous_system_wasm::version().len() > 0);
|
|
|
|
assert!(true);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn test_nervous_system_serialization() {
|
|
// Test serialization for WASM interop
|
|
let num_neurons = 10;
|
|
|
|
// TODO: Test serialization
|
|
// let network = SpikingNetwork::new(&[num_neurons, num_neurons]);
|
|
//
|
|
// // Serialize to JSON
|
|
// let json = network.to_json();
|
|
// assert!(json.len() > 0);
|
|
//
|
|
// // Deserialize
|
|
// let restored = SpikingNetwork::from_json(&json);
|
|
//
|
|
// // Should produce same output
|
|
// let input = random_vector(num_neurons);
|
|
// let output1 = network.forward(&input);
|
|
// let output2 = restored.forward(&input);
|
|
// assert_vectors_approx_eq(&output1, &output2, 1e-6);
|
|
|
|
assert!(num_neurons > 0);
|
|
}
|
|
}
|