Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
47
vendor/ruvector/crates/ruvector-nervous-system-wasm/Cargo.toml
vendored
Normal file
47
vendor/ruvector/crates/ruvector-nervous-system-wasm/Cargo.toml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "ruvector-nervous-system-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "WASM bindings for ruvector-nervous-system bio-inspired AI components"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/ruvnet/ruvector"
|
||||
documentation = "https://ruv.io/ruvector"
|
||||
keywords = ["wasm", "neural", "hdс", "btsp", "neuromorphic"]
|
||||
categories = ["wasm", "science", "algorithms"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
# WASM bindings
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3"
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
# RNG for WASM
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
rand = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
|
||||
[profile.release]
|
||||
# Optimize for size (<100KB target)
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
|
||||
[profile.release.package."*"]
|
||||
opt-level = "z"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
411
vendor/ruvector/crates/ruvector-nervous-system-wasm/README.md
vendored
Normal file
411
vendor/ruvector/crates/ruvector-nervous-system-wasm/README.md
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
# ruvector-nervous-system-wasm
|
||||
|
||||
Bio-inspired neural system components for browser execution via WebAssembly.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install ruvector-nervous-system-wasm
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
import init, {
|
||||
BTSPLayer,
|
||||
BTSPAssociativeMemory,
|
||||
Hypervector,
|
||||
HdcMemory,
|
||||
WTALayer,
|
||||
KWTALayer,
|
||||
GlobalWorkspace,
|
||||
WorkspaceItem,
|
||||
version,
|
||||
available_mechanisms,
|
||||
performance_targets,
|
||||
} from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Initialize WASM module (required before using any components)
|
||||
await init();
|
||||
|
||||
console.log('Version:', version());
|
||||
console.log('Available mechanisms:', available_mechanisms());
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### 1. BTSP (Behavioral Timescale Synaptic Plasticity)
|
||||
|
||||
One-shot learning based on Bittner et al. 2017 hippocampal place field formation.
|
||||
|
||||
#### BTSPLayer
|
||||
|
||||
```javascript
|
||||
// Create a BTSP layer with 100 synapses and 2000ms time constant
|
||||
const btsp = new BTSPLayer(100, 2000.0);
|
||||
|
||||
// One-shot learning: associate pattern with target value immediately
|
||||
const pattern = new Float32Array(100).fill(0.1);
|
||||
btsp.one_shot_associate(pattern, 1.0);
|
||||
|
||||
// Forward pass: compute output for input pattern
|
||||
const output = btsp.forward(pattern);
|
||||
console.log('Output:', output);
|
||||
|
||||
// Get layer properties
|
||||
console.log('Size:', btsp.size);
|
||||
console.log('Weights:', btsp.get_weights());
|
||||
|
||||
// Reset layer to initial random state
|
||||
btsp.reset();
|
||||
```
|
||||
|
||||
#### BTSPSynapse
|
||||
|
||||
```javascript
|
||||
// Create individual synapse with initial weight and time constant
|
||||
const synapse = new BTSPSynapse(0.5, 2000.0);
|
||||
|
||||
// Update synapse based on neural activity
|
||||
synapse.update(
|
||||
true, // presynaptic_active: presynaptic neuron is firing
|
||||
true, // plateau_signal: dendritic plateau detected
|
||||
10.0 // dt: time step in milliseconds
|
||||
);
|
||||
|
||||
// Get synapse state
|
||||
console.log('Weight:', synapse.weight);
|
||||
console.log('Eligibility trace:', synapse.eligibility_trace);
|
||||
|
||||
// Compute synaptic output
|
||||
const output = synapse.forward(0.8);
|
||||
```
|
||||
|
||||
#### BTSPAssociativeMemory
|
||||
|
||||
```javascript
|
||||
// Create key-value associative memory (input_size, output_size)
|
||||
const memory = new BTSPAssociativeMemory(128, 64);
|
||||
|
||||
// Store key-value pair in one shot (no iteration needed)
|
||||
const key = new Float32Array(128).fill(0.1);
|
||||
const value = new Float32Array(64).fill(0.5);
|
||||
memory.store_one_shot(key, value);
|
||||
|
||||
// Retrieve value from key
|
||||
const retrieved = memory.retrieve(key);
|
||||
console.log('Retrieved value:', retrieved);
|
||||
|
||||
// Get memory dimensions
|
||||
console.log('Dimensions:', memory.dimensions());
|
||||
```
|
||||
|
||||
### 2. HDC (Hyperdimensional Computing)
|
||||
|
||||
10,000-bit binary hypervectors with ultra-fast operations.
|
||||
|
||||
#### Hypervector
|
||||
|
||||
```javascript
|
||||
// Create hypervectors
|
||||
const hv1 = new Hypervector(); // Zero vector
|
||||
const hv2 = Hypervector.random(); // Random (~50% bits set)
|
||||
const hv3 = Hypervector.from_seed(42); // Reproducible from seed
|
||||
|
||||
// Binding (XOR) - associative, commutative, self-inverse
|
||||
const bound = hv2.bind(hv3);
|
||||
console.log('Binding is self-inverse:', hv2.similarity(bound.bind(hv3)) > 0.99);
|
||||
|
||||
// Similarity: 1.0 = identical, 0.0 = orthogonal, -1.0 = opposite
|
||||
const sim = hv2.similarity(hv3);
|
||||
console.log('Similarity:', sim);
|
||||
|
||||
// Hamming distance (differing bits)
|
||||
const distance = hv2.hamming_distance(hv3);
|
||||
console.log('Hamming distance:', distance);
|
||||
|
||||
// Population count (set bits)
|
||||
console.log('Popcount:', hv2.popcount());
|
||||
|
||||
// Bundle 3 vectors by majority voting
|
||||
const bundled = Hypervector.bundle_3(hv1, hv2, hv3);
|
||||
|
||||
// Serialization
|
||||
const bytes = hv2.to_bytes();
|
||||
const restored = Hypervector.from_bytes(bytes);
|
||||
console.log('Restored correctly:', hv2.similarity(restored) === 1.0);
|
||||
|
||||
// Properties
|
||||
console.log('Dimension:', hv2.dimension); // 10000
|
||||
```
|
||||
|
||||
#### HdcMemory
|
||||
|
||||
```javascript
|
||||
// Create memory store
|
||||
const hdcMem = new HdcMemory();
|
||||
|
||||
// Store labeled hypervectors
|
||||
const apple = Hypervector.random();
|
||||
const orange = Hypervector.random();
|
||||
const banana = Hypervector.random();
|
||||
|
||||
hdcMem.store("apple", apple);
|
||||
hdcMem.store("orange", orange);
|
||||
hdcMem.store("banana", banana);
|
||||
|
||||
// Retrieve similar vectors above threshold
|
||||
const results = hdcMem.retrieve(apple, 0.5);
|
||||
console.log('Similar to apple:', results);
|
||||
// Returns: [["apple", 1.0], ...]
|
||||
|
||||
// Find top-k most similar
|
||||
const topK = hdcMem.top_k(apple, 2);
|
||||
console.log('Top 2:', topK);
|
||||
|
||||
// Query memory
|
||||
console.log('Size:', hdcMem.size);
|
||||
console.log('Has apple:', hdcMem.has("apple"));
|
||||
|
||||
// Get specific vector
|
||||
const appleVec = hdcMem.get("apple");
|
||||
|
||||
// Clear memory
|
||||
hdcMem.clear();
|
||||
```
|
||||
|
||||
### 3. WTA (Winner-Take-All)
|
||||
|
||||
Instant decisions via neural competition.
|
||||
|
||||
#### WTALayer
|
||||
|
||||
```javascript
|
||||
// Create WTA layer with 1000 neurons, threshold 0.5, inhibition strength 0.8
|
||||
const wta = new WTALayer(1000, 0.5, 0.8);
|
||||
|
||||
// Competition: returns winning neuron index (or -1 if none exceeds threshold)
|
||||
const activations = new Float32Array(1000);
|
||||
activations[42] = 0.9; // Make neuron 42 the winner
|
||||
activations[100] = 0.7;
|
||||
|
||||
const winner = wta.compete(activations);
|
||||
console.log('Winner:', winner); // 42
|
||||
|
||||
// Soft competition: normalized activations (softmax-like)
|
||||
const softActivations = wta.compete_soft(activations);
|
||||
console.log('Soft activations:', softActivations);
|
||||
|
||||
// Get membrane potentials
|
||||
const membranes = wta.get_membranes();
|
||||
|
||||
// Reset layer state
|
||||
wta.reset();
|
||||
|
||||
// Configure refractory period (prevents winner from winning again immediately)
|
||||
wta.set_refractory_period(20);
|
||||
|
||||
// Properties
|
||||
console.log('Size:', wta.size);
|
||||
```
|
||||
|
||||
#### KWTALayer
|
||||
|
||||
```javascript
|
||||
// Create K-WTA layer: 1000 neurons, select top 50
|
||||
const kwta = new KWTALayer(1000, 50);
|
||||
|
||||
// Optional: set activation threshold
|
||||
kwta.with_threshold(0.1);
|
||||
|
||||
const activations = new Float32Array(1000);
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
activations[i] = Math.random();
|
||||
}
|
||||
|
||||
// Select top-k neuron indices (sorted by activation, descending)
|
||||
const winners = kwta.select(activations);
|
||||
console.log('Winner indices:', winners); // Uint32Array of 50 indices
|
||||
|
||||
// Select with values: array of [index, value] pairs
|
||||
const winnersWithValues = kwta.select_with_values(activations);
|
||||
console.log('Winners with values:', winnersWithValues);
|
||||
|
||||
// Get sparse activation vector (only top-k preserved, rest zeroed)
|
||||
const sparse = kwta.sparse_activations(activations);
|
||||
console.log('Sparse vector:', sparse);
|
||||
|
||||
// Properties
|
||||
console.log('k:', kwta.k); // 50
|
||||
console.log('Size:', kwta.size); // 1000
|
||||
```
|
||||
|
||||
### 4. Global Workspace
|
||||
|
||||
Attention bottleneck based on Global Workspace Theory (Baars, Dehaene).
|
||||
|
||||
#### WorkspaceItem
|
||||
|
||||
```javascript
|
||||
// Create a workspace item
|
||||
const content = new Float32Array([1.0, 2.0, 3.0, 4.0]);
|
||||
const item = new WorkspaceItem(
|
||||
content, // content vector
|
||||
0.9, // salience (importance)
|
||||
1, // source_module ID
|
||||
Date.now() // timestamp
|
||||
);
|
||||
|
||||
// Create with custom decay and lifetime
|
||||
const itemWithDecay = WorkspaceItem.with_decay(
|
||||
content,
|
||||
0.9, // salience
|
||||
1, // source_module
|
||||
Date.now(), // timestamp
|
||||
0.95, // decay_rate per timestep
|
||||
5000 // lifetime in ms
|
||||
);
|
||||
|
||||
// Access item properties
|
||||
console.log('Salience:', item.salience);
|
||||
console.log('Source module:', item.source_module);
|
||||
console.log('Timestamp:', item.timestamp);
|
||||
console.log('ID:', item.id);
|
||||
console.log('Content:', item.get_content());
|
||||
console.log('Magnitude:', item.magnitude());
|
||||
|
||||
// Update salience
|
||||
item.update_salience(0.8);
|
||||
|
||||
// Apply temporal decay
|
||||
item.apply_decay(1.0); // dt = 1.0
|
||||
|
||||
// Check expiration
|
||||
console.log('Expired:', item.is_expired(Date.now() + 10000));
|
||||
```
|
||||
|
||||
#### GlobalWorkspace
|
||||
|
||||
```javascript
|
||||
// Create workspace with capacity 7 (Miller's Law: 7 +/- 2)
|
||||
const workspace = new GlobalWorkspace(7);
|
||||
|
||||
// Or with custom salience threshold
|
||||
const workspace2 = GlobalWorkspace.with_threshold(7, 0.2);
|
||||
|
||||
// Configure decay rate
|
||||
workspace.set_decay_rate(0.95);
|
||||
|
||||
// Broadcast items to workspace (returns true if accepted)
|
||||
const item1 = new WorkspaceItem(new Float32Array([1, 2, 3]), 0.9, 1, Date.now());
|
||||
const item2 = new WorkspaceItem(new Float32Array([4, 5, 6]), 0.7, 2, Date.now());
|
||||
|
||||
const accepted1 = workspace.broadcast(item1);
|
||||
const accepted2 = workspace.broadcast(item2);
|
||||
console.log('Item 1 accepted:', accepted1);
|
||||
console.log('Item 2 accepted:', accepted2);
|
||||
|
||||
// Run competitive dynamics (decay + pruning)
|
||||
workspace.compete();
|
||||
|
||||
// Retrieve all current representations
|
||||
const allItems = workspace.retrieve();
|
||||
console.log('All items:', allItems);
|
||||
// Returns: [{ content: [...], salience: ..., source_module: ..., timestamp: ..., id: ... }, ...]
|
||||
|
||||
// Retrieve top-k most salient
|
||||
const topItems = workspace.retrieve_top_k(3);
|
||||
console.log('Top 3:', topItems);
|
||||
|
||||
// Get most salient item
|
||||
const mostSalient = workspace.most_salient();
|
||||
if (mostSalient) {
|
||||
console.log('Most salient:', mostSalient.salience);
|
||||
}
|
||||
|
||||
// Query workspace state
|
||||
console.log('Length:', workspace.len);
|
||||
console.log('Capacity:', workspace.capacity);
|
||||
console.log('Is full:', workspace.is_full());
|
||||
console.log('Is empty:', workspace.is_empty());
|
||||
console.log('Available slots:', workspace.available_slots());
|
||||
console.log('Current load:', workspace.current_load()); // 0.0 to 1.0
|
||||
console.log('Average salience:', workspace.average_salience());
|
||||
|
||||
// Clear workspace
|
||||
workspace.clear();
|
||||
```
|
||||
|
||||
## Performance Targets
|
||||
|
||||
| Component | Target | Method |
|
||||
|-----------|--------|--------|
|
||||
| BTSP one_shot_associate | Immediate | Gradient normalization |
|
||||
| HDC bind | <50ns | XOR operation |
|
||||
| HDC similarity | <100ns | Hamming distance + unrolled popcount |
|
||||
| WTA compete | <1us | Single-pass argmax |
|
||||
| K-WTA select | <10us | Partial sort (O(n + k log k)) |
|
||||
| Workspace broadcast | <10us | Competition |
|
||||
|
||||
## Bundle Size
|
||||
|
||||
- WASM binary: ~178 KB
|
||||
- JavaScript glue: ~54 KB
|
||||
- TypeScript definitions: ~17 KB
|
||||
|
||||
## Biological References
|
||||
|
||||
| Mechanism | Reference |
|
||||
|-----------|-----------|
|
||||
| BTSP | Bittner et al. 2017 - Hippocampal place fields |
|
||||
| HDC | Kanerva 1988, Plate 2003 - Hyperdimensional computing |
|
||||
| WTA | Cortical microcircuits - Lateral inhibition |
|
||||
| Global Workspace | Baars 1988, Dehaene 2014 - Consciousness and attention |
|
||||
|
||||
## Utility Functions
|
||||
|
||||
```javascript
|
||||
// Get crate version
|
||||
console.log(version()); // "0.1.0"
|
||||
|
||||
// List available mechanisms with descriptions
|
||||
console.log(available_mechanisms());
|
||||
// [["btsp", "Behavioral Timescale Synaptic Plasticity - One-shot learning"], ...]
|
||||
|
||||
// Get performance targets
|
||||
console.log(performance_targets());
|
||||
// [["btsp_one_shot", "Immediate (no iteration)"], ...]
|
||||
|
||||
// Get biological references
|
||||
console.log(biological_references());
|
||||
// [["BTSP", "Bittner et al. 2017 - Hippocampal place fields"], ...]
|
||||
```
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
Full TypeScript definitions are included. All classes and functions are fully typed:
|
||||
|
||||
```typescript
|
||||
import init, {
|
||||
BTSPLayer,
|
||||
BTSPSynapse,
|
||||
BTSPAssociativeMemory,
|
||||
Hypervector,
|
||||
HdcMemory,
|
||||
WTALayer,
|
||||
KWTALayer,
|
||||
GlobalWorkspace,
|
||||
WorkspaceItem,
|
||||
} from 'ruvector-nervous-system-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
const layer: BTSPLayer = new BTSPLayer(100, 2000.0);
|
||||
const hv: Hypervector = Hypervector.random();
|
||||
const wta: WTALayer = new WTALayer(1000, 0.5, 0.8);
|
||||
const ws: GlobalWorkspace = new GlobalWorkspace(7);
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
410
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/README.md
vendored
Normal file
410
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/README.md
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
# @ruvector/nervous-system-wasm - Bio-Inspired AI for WebAssembly
|
||||
|
||||
[](https://www.npmjs.com/package/ruvector-nervous-system-wasm)
|
||||
[](https://github.com/ruvnet/ruvector)
|
||||
[](https://www.npmjs.com/package/ruvector-nervous-system-wasm)
|
||||
[](https://webassembly.org/)
|
||||
|
||||
**Bio-inspired neural system components** for browser execution. Implements neuromorphic computing primitives including Hyperdimensional Computing (HDC), Behavioral Timescale Synaptic Plasticity (BTSP), Winner-Take-All networks, and Global Workspace attention.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Hyperdimensional Computing (HDC)**: 10,000-bit binary hypervectors for similarity-preserving encoding
|
||||
- **BTSP (Behavioral Timescale Synaptic Plasticity)**: One-shot learning without iteration
|
||||
- **Winner-Take-All (WTA)**: Sub-microsecond instant decisions through lateral inhibition
|
||||
- **K-WTA (K-Winner-Take-All)**: Sparse distributed coding for neural representations
|
||||
- **Global Workspace**: 4-7 item attention bottleneck inspired by conscious access
|
||||
- **WASM-Optimized**: Designed for browser ML and edge inference
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install ruvector-nervous-system-wasm
|
||||
# or
|
||||
yarn add ruvector-nervous-system-wasm
|
||||
# or
|
||||
pnpm add ruvector-nervous-system-wasm
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import init, {
|
||||
BTSPLayer,
|
||||
Hypervector,
|
||||
HdcMemory,
|
||||
WTALayer,
|
||||
KWTALayer,
|
||||
GlobalWorkspace,
|
||||
WorkspaceItem
|
||||
} from 'ruvector-nervous-system-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
// One-shot learning with BTSP
|
||||
const btsp = new BTSPLayer(100, 2000.0);
|
||||
const pattern = new Float32Array(100).fill(0.1);
|
||||
btsp.one_shot_associate(pattern, 1.0);
|
||||
|
||||
// Hyperdimensional computing
|
||||
const apple = Hypervector.random();
|
||||
const orange = Hypervector.random();
|
||||
const similarity = apple.similarity(orange);
|
||||
|
||||
// Winner-take-all decisions
|
||||
const wta = new WTALayer(1000, 0.5, 0.8);
|
||||
const activations = new Float32Array(1000);
|
||||
const winner = wta.compete(activations);
|
||||
```
|
||||
|
||||
## Hyperdimensional Computing (HDC)
|
||||
|
||||
HDC represents information using high-dimensional binary vectors (~10,000 bits). Similar concepts have similar vectors, enabling robust pattern matching.
|
||||
|
||||
### Key Properties
|
||||
|
||||
- **High Dimensionality**: 10,000 bits provides exponential capacity
|
||||
- **Holographic**: Information distributed across entire vector
|
||||
- **Noise Tolerant**: Robust to bit flips and partial corruption
|
||||
- **Single-Operation Learning**: No iterative training needed
|
||||
|
||||
```typescript
|
||||
import { Hypervector, HdcMemory } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create random hypervectors for concepts
|
||||
const apple = Hypervector.random();
|
||||
const red = Hypervector.random();
|
||||
const fruit = Hypervector.random();
|
||||
|
||||
// Bind: Associate concepts (XOR operation)
|
||||
// Binding is self-inverse: a.bind(b).bind(b) == a
|
||||
const redApple = apple.bind(red);
|
||||
|
||||
// Bundle: Combine multiple concepts (majority voting)
|
||||
const fruitConcept = Hypervector.bundle_3(apple, orange, banana);
|
||||
|
||||
// Measure similarity (-1.0 to 1.0)
|
||||
const sim = apple.similarity(redApple);
|
||||
console.log(`Apple-RedApple similarity: ${sim.toFixed(3)}`);
|
||||
|
||||
// Hamming distance (number of differing bits)
|
||||
const distance = apple.hamming_distance(orange);
|
||||
console.log(`Hamming distance: ${distance}`);
|
||||
|
||||
// Reproducible vectors from seed
|
||||
const seededVector = Hypervector.from_seed(42n);
|
||||
|
||||
// Serialize/deserialize
|
||||
const bytes = apple.to_bytes();
|
||||
const restored = Hypervector.from_bytes(bytes);
|
||||
```
|
||||
|
||||
### HDC Memory Store
|
||||
|
||||
```typescript
|
||||
import { HdcMemory, Hypervector } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
const memory = new HdcMemory();
|
||||
|
||||
// Store concept vectors
|
||||
memory.store("apple", Hypervector.random());
|
||||
memory.store("banana", Hypervector.random());
|
||||
memory.store("car", Hypervector.random());
|
||||
|
||||
// Retrieve similar concepts
|
||||
const query = memory.get("apple")!;
|
||||
const results = memory.retrieve(query, 0.8); // threshold
|
||||
console.log(`Found ${results.length} similar concepts`);
|
||||
|
||||
// Get top-k most similar
|
||||
const topK = memory.top_k(query, 3);
|
||||
for (const [label, similarity] of topK) {
|
||||
console.log(`${label}: ${similarity.toFixed(3)}`);
|
||||
}
|
||||
|
||||
// Check existence
|
||||
if (memory.has("apple")) {
|
||||
const vec = memory.get("apple");
|
||||
}
|
||||
```
|
||||
|
||||
## BTSP (Behavioral Timescale Synaptic Plasticity)
|
||||
|
||||
BTSP enables **one-shot learning** - learning patterns in a single exposure, inspired by hippocampal place field formation (Bittner et al., 2017).
|
||||
|
||||
```typescript
|
||||
import { BTSPLayer, BTSPSynapse, BTSPAssociativeMemory } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create BTSP layer
|
||||
const btsp = new BTSPLayer(256, 2000.0); // 256 synapses, 2s time constant
|
||||
|
||||
// One-shot association: learn pattern -> target immediately
|
||||
const pattern = new Float32Array(256);
|
||||
pattern.fill(0.1);
|
||||
pattern[0] = 0.9; pattern[42] = 0.8;
|
||||
|
||||
btsp.one_shot_associate(pattern, 1.0); // Target value = 1.0
|
||||
|
||||
// Forward pass - retrieves learned pattern
|
||||
const output = btsp.forward(pattern);
|
||||
console.log(`Retrieved value: ${output.toFixed(3)}`);
|
||||
|
||||
// Get learned weights
|
||||
const weights = btsp.get_weights();
|
||||
```
|
||||
|
||||
### Individual Synapse Control
|
||||
|
||||
```typescript
|
||||
import { BTSPSynapse } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create synapse with initial weight
|
||||
const synapse = new BTSPSynapse(0.5, 2000.0);
|
||||
|
||||
// Update based on neural activity
|
||||
synapse.update(
|
||||
true, // presynaptic active
|
||||
true, // plateau signal detected
|
||||
10.0 // dt in milliseconds
|
||||
);
|
||||
|
||||
console.log(`Weight: ${synapse.weight.toFixed(3)}`);
|
||||
console.log(`Eligibility: ${synapse.eligibility_trace.toFixed(3)}`);
|
||||
```
|
||||
|
||||
### Associative Memory
|
||||
|
||||
```typescript
|
||||
import { BTSPAssociativeMemory } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create key-value associative memory
|
||||
const assocMem = new BTSPAssociativeMemory(64, 128); // 64-dim keys -> 128-dim values
|
||||
|
||||
// Store associations in one shot
|
||||
const key = new Float32Array(64).fill(0.1);
|
||||
const value = new Float32Array(128).fill(0.5);
|
||||
assocMem.store_one_shot(key, value);
|
||||
|
||||
// Retrieve from partial/noisy key
|
||||
const query = new Float32Array(64).fill(0.1);
|
||||
const retrieved = assocMem.retrieve(query);
|
||||
```
|
||||
|
||||
## Winner-Take-All (WTA)
|
||||
|
||||
WTA implements competitive neural dynamics where only the strongest activation survives - enabling ultra-fast decision making.
|
||||
|
||||
```typescript
|
||||
import { WTALayer } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create WTA layer: 1000 neurons, 0.5 threshold, 0.8 inhibition
|
||||
const wta = new WTALayer(1000, 0.5, 0.8);
|
||||
|
||||
// Compete for winner
|
||||
const activations = new Float32Array(1000);
|
||||
activations[42] = 0.9;
|
||||
activations[100] = 0.7;
|
||||
|
||||
const winner = wta.compete(activations);
|
||||
console.log(`Winner index: ${winner}`); // 42, or -1 if none exceed threshold
|
||||
|
||||
// Soft competition (softmax-like)
|
||||
const softActivations = wta.compete_soft(activations);
|
||||
|
||||
// Get membrane potentials
|
||||
const membranes = wta.get_membranes();
|
||||
|
||||
// Configure refractory period
|
||||
wta.set_refractory_period(5.0);
|
||||
|
||||
// Reset layer state
|
||||
wta.reset();
|
||||
```
|
||||
|
||||
## K-Winner-Take-All (K-WTA)
|
||||
|
||||
K-WTA selects the top-k neurons, enabling sparse distributed coding.
|
||||
|
||||
```typescript
|
||||
import { KWTALayer } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create K-WTA: 1000 neurons, select top 50
|
||||
const kwta = new KWTALayer(1000, 50);
|
||||
|
||||
const activations = new Float32Array(1000);
|
||||
// Fill with random values
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
activations[i] = Math.random();
|
||||
}
|
||||
|
||||
// Get indices of top-k winners (sorted descending by value)
|
||||
const winnerIndices = kwta.select(activations);
|
||||
console.log(`Top 50 winners: ${winnerIndices}`);
|
||||
|
||||
// Get winners with their values
|
||||
const winnersWithValues = kwta.select_with_values(activations);
|
||||
for (const [index, value] of winnersWithValues) {
|
||||
console.log(`Neuron ${index}: ${value.toFixed(3)}`);
|
||||
}
|
||||
|
||||
// Create sparse activation vector
|
||||
const sparse = kwta.sparse_activations(activations);
|
||||
// Only top-k values preserved, rest are 0
|
||||
```
|
||||
|
||||
## Global Workspace
|
||||
|
||||
Implements the Global Workspace Theory of consciousness - a limited-capacity "workspace" where only the most salient information gains access.
|
||||
|
||||
```typescript
|
||||
import { GlobalWorkspace, WorkspaceItem } from 'ruvector-nervous-system-wasm';
|
||||
|
||||
// Create workspace with capacity 7 (Miller's Law: 7 +/- 2)
|
||||
const workspace = new GlobalWorkspace(7);
|
||||
|
||||
// Create workspace items
|
||||
const content = new Float32Array([1.0, 2.0, 3.0, 4.0]);
|
||||
const item1 = new WorkspaceItem(
|
||||
content,
|
||||
0.9, // salience
|
||||
1, // source module ID
|
||||
BigInt(Date.now())
|
||||
);
|
||||
|
||||
const item2 = WorkspaceItem.with_decay(
|
||||
content,
|
||||
0.7, // salience
|
||||
2, // source module
|
||||
BigInt(Date.now()),
|
||||
0.1, // decay rate
|
||||
5000n // lifetime ms
|
||||
);
|
||||
|
||||
// Broadcast to workspace (returns true if accepted)
|
||||
if (workspace.broadcast(item1)) {
|
||||
console.log("Item accepted into workspace");
|
||||
}
|
||||
|
||||
// Run competitive dynamics
|
||||
workspace.compete(); // Lower salience items decay/get pruned
|
||||
|
||||
// Retrieve most salient
|
||||
const mostSalient = workspace.most_salient();
|
||||
if (mostSalient) {
|
||||
console.log(`Most salient: ${mostSalient.salience}`);
|
||||
}
|
||||
|
||||
// Get all current items
|
||||
const allItems = workspace.retrieve();
|
||||
|
||||
// Get top-k items
|
||||
const topItems = workspace.retrieve_top_k(3);
|
||||
|
||||
// Check workspace state
|
||||
console.log(`Items: ${workspace.len} / ${workspace.capacity}`);
|
||||
console.log(`Load: ${(workspace.current_load() * 100).toFixed(1)}%`);
|
||||
console.log(`Average salience: ${workspace.average_salience().toFixed(2)}`);
|
||||
|
||||
// Configure decay
|
||||
workspace.set_decay_rate(0.05);
|
||||
```
|
||||
|
||||
## Performance Benchmarks
|
||||
|
||||
| Component | Operation | Target Latency |
|
||||
|-----------|-----------|----------------|
|
||||
| BTSP | one_shot_associate | Immediate (no iteration) |
|
||||
| HDC | bind (XOR) | < 50ns |
|
||||
| HDC | similarity | < 100ns |
|
||||
| WTA | compete | < 1us |
|
||||
| K-WTA | select (k=50, n=1000) | < 10us |
|
||||
| Workspace | broadcast | < 10us |
|
||||
|
||||
## Biological References
|
||||
|
||||
| Component | Biological Inspiration | Reference |
|
||||
|-----------|----------------------|-----------|
|
||||
| BTSP | Hippocampal place fields | Bittner et al., 2017 |
|
||||
| HDC | Cortical sparse coding | Kanerva, 1988; Plate, 2003 |
|
||||
| WTA | Lateral inhibition | Cortical microcircuits |
|
||||
| Global Workspace | Conscious access | Baars, 1988; Dehaene, 2014 |
|
||||
|
||||
## API Reference
|
||||
|
||||
### Hypervector
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `random()` | Create random hypervector (static) |
|
||||
| `from_seed(seed)` | Reproducible from seed (static) |
|
||||
| `bind(other)` | XOR binding (associative, self-inverse) |
|
||||
| `bundle_3(a, b, c)` | Majority voting bundle (static) |
|
||||
| `similarity(other)` | Cosine-like similarity (-1 to 1) |
|
||||
| `hamming_distance(other)` | Number of differing bits |
|
||||
| `to_bytes()` / `from_bytes()` | Serialization |
|
||||
|
||||
### BTSPLayer
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `new(size, tau)` | Create layer |
|
||||
| `one_shot_associate(pattern, target)` | Single-step learning |
|
||||
| `forward(input)` | Compute output |
|
||||
| `get_weights()` | Get learned weights |
|
||||
| `reset()` | Reset to initial state |
|
||||
|
||||
### WTALayer / KWTALayer
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `new(size, threshold, inhibition)` | Create WTA |
|
||||
| `new(size, k)` | Create K-WTA |
|
||||
| `compete(inputs)` | Get winner index |
|
||||
| `select(inputs)` | Get top-k indices |
|
||||
| `sparse_activations(inputs)` | Sparse output |
|
||||
|
||||
### GlobalWorkspace
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `new(capacity)` | Create workspace (4-7 typical) |
|
||||
| `broadcast(item)` | Add item to workspace |
|
||||
| `compete()` | Run competitive dynamics |
|
||||
| `most_salient()` | Get top item |
|
||||
| `retrieve_top_k(k)` | Get top k items |
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Neuromorphic Computing**: Brain-inspired computing architectures
|
||||
- **One-Shot Learning**: Learn from single examples
|
||||
- **Attention Mechanisms**: Biologically-plausible attention
|
||||
- **Sparse Coding**: Efficient neural representations
|
||||
- **Symbol Binding**: Compositional representations with HDC
|
||||
- **Fast Decision Making**: Ultra-low-latency neural decisions
|
||||
- **Memory Systems**: Associative and content-addressable memory
|
||||
|
||||
## Bundle Size
|
||||
|
||||
- **WASM binary**: ~174KB (uncompressed)
|
||||
- **Gzip compressed**: ~65KB
|
||||
- **JavaScript glue**: ~8KB
|
||||
|
||||
## Related Packages
|
||||
|
||||
- [ruvector-attention-unified-wasm](https://www.npmjs.com/package/ruvector-attention-unified-wasm) - 18+ attention mechanisms
|
||||
- [ruvector-learning-wasm](https://www.npmjs.com/package/ruvector-learning-wasm) - MicroLoRA adaptation
|
||||
- [ruvector-exotic-wasm](https://www.npmjs.com/package/ruvector-exotic-wasm) - NAO governance, exotic AI
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Links
|
||||
|
||||
- [GitHub Repository](https://github.com/ruvnet/ruvector)
|
||||
- [Full Documentation](https://ruv.io)
|
||||
- [Bug Reports](https://github.com/ruvnet/ruvector/issues)
|
||||
|
||||
---
|
||||
|
||||
**Keywords**: hyperdimensional computing, HDC, BTSP, behavioral timescale synaptic plasticity, neuromorphic, winner-take-all, WTA, K-WTA, sparse coding, neural networks, one-shot learning, WebAssembly, WASM, bio-inspired, brain-inspired, neural competition, lateral inhibition, global workspace, attention, consciousness, associative memory
|
||||
43
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/package.json
vendored
Normal file
43
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/package.json
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@ruvector/nervous-system-wasm",
|
||||
"type": "module",
|
||||
"collaborators": [
|
||||
"RuVector Team"
|
||||
],
|
||||
"author": "RuVector Team <ruvnet@users.noreply.github.com>",
|
||||
"description": "WASM bindings for ruvector-nervous-system bio-inspired AI components - HDC, BTSP, neuromorphic computing",
|
||||
"version": "0.1.29",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ruvnet/ruvector"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ruvnet/ruvector/issues"
|
||||
},
|
||||
"files": [
|
||||
"ruvector_nervous_system_wasm_bg.wasm",
|
||||
"ruvector_nervous_system_wasm.js",
|
||||
"ruvector_nervous_system_wasm.d.ts",
|
||||
"ruvector_nervous_system_wasm_bg.wasm.d.ts",
|
||||
"README.md"
|
||||
],
|
||||
"main": "ruvector_nervous_system_wasm.js",
|
||||
"homepage": "https://ruv.io",
|
||||
"types": "ruvector_nervous_system_wasm.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
],
|
||||
"keywords": [
|
||||
"wasm",
|
||||
"neural",
|
||||
"hdc",
|
||||
"btsp",
|
||||
"neuromorphic",
|
||||
"ruvector",
|
||||
"webassembly",
|
||||
"hyperdimensional-computing",
|
||||
"spiking-neural-networks",
|
||||
"bio-inspired"
|
||||
]
|
||||
}
|
||||
548
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/ruvector_nervous_system_wasm.d.ts
vendored
Normal file
548
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/ruvector_nervous_system_wasm.d.ts
vendored
Normal file
@@ -0,0 +1,548 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export class BTSPAssociativeMemory {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Get memory dimensions
|
||||
*/
|
||||
dimensions(): any;
|
||||
/**
|
||||
* Store key-value association in one shot
|
||||
*/
|
||||
store_one_shot(key: Float32Array, value: Float32Array): void;
|
||||
/**
|
||||
* Create new associative memory
|
||||
*
|
||||
* # Arguments
|
||||
* * `input_size` - Dimension of key vectors
|
||||
* * `output_size` - Dimension of value vectors
|
||||
*/
|
||||
constructor(input_size: number, output_size: number);
|
||||
/**
|
||||
* Retrieve value from key
|
||||
*/
|
||||
retrieve(query: Float32Array): Float32Array;
|
||||
}
|
||||
|
||||
export class BTSPLayer {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Get weights as Float32Array
|
||||
*/
|
||||
get_weights(): Float32Array;
|
||||
/**
|
||||
* One-shot association: learn pattern -> target in single step
|
||||
*
|
||||
* This is the key BTSP capability: immediate learning without iteration.
|
||||
* Uses gradient normalization for single-step convergence.
|
||||
*/
|
||||
one_shot_associate(pattern: Float32Array, target: number): void;
|
||||
/**
|
||||
* Create a new BTSP layer
|
||||
*
|
||||
* # Arguments
|
||||
* * `size` - Number of synapses (input dimension)
|
||||
* * `tau` - Time constant in milliseconds (2000ms default)
|
||||
*/
|
||||
constructor(size: number, tau: number);
|
||||
/**
|
||||
* Reset layer to initial state
|
||||
*/
|
||||
reset(): void;
|
||||
/**
|
||||
* Forward pass: compute layer output
|
||||
*/
|
||||
forward(input: Float32Array): number;
|
||||
/**
|
||||
* Get number of synapses
|
||||
*/
|
||||
readonly size: number;
|
||||
}
|
||||
|
||||
export class BTSPSynapse {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Create a new BTSP synapse
|
||||
*
|
||||
* # Arguments
|
||||
* * `initial_weight` - Starting weight (0.0 to 1.0)
|
||||
* * `tau_btsp` - Time constant in milliseconds (1000-3000ms recommended)
|
||||
*/
|
||||
constructor(initial_weight: number, tau_btsp: number);
|
||||
/**
|
||||
* Update synapse based on activity and plateau signal
|
||||
*
|
||||
* # Arguments
|
||||
* * `presynaptic_active` - Is presynaptic neuron firing?
|
||||
* * `plateau_signal` - Dendritic plateau potential detected?
|
||||
* * `dt` - Time step in milliseconds
|
||||
*/
|
||||
update(presynaptic_active: boolean, plateau_signal: boolean, dt: number): void;
|
||||
/**
|
||||
* Compute synaptic output
|
||||
*/
|
||||
forward(input: number): number;
|
||||
/**
|
||||
* Get eligibility trace
|
||||
*/
|
||||
readonly eligibility_trace: number;
|
||||
/**
|
||||
* Get current weight
|
||||
*/
|
||||
readonly weight: number;
|
||||
}
|
||||
|
||||
export class GlobalWorkspace {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Get current load (0.0 to 1.0)
|
||||
*/
|
||||
current_load(): number;
|
||||
/**
|
||||
* Get most salient item
|
||||
*/
|
||||
most_salient(): WorkspaceItem | undefined;
|
||||
/**
|
||||
* Retrieve top-k most salient representations
|
||||
*/
|
||||
retrieve_top_k(k: number): any;
|
||||
/**
|
||||
* Set salience decay rate
|
||||
*/
|
||||
set_decay_rate(decay: number): void;
|
||||
/**
|
||||
* Create with custom threshold
|
||||
*/
|
||||
static with_threshold(capacity: number, threshold: number): GlobalWorkspace;
|
||||
/**
|
||||
* Get available slots
|
||||
*/
|
||||
available_slots(): number;
|
||||
/**
|
||||
* Get average salience
|
||||
*/
|
||||
average_salience(): number;
|
||||
/**
|
||||
* Create a new global workspace
|
||||
*
|
||||
* # Arguments
|
||||
* * `capacity` - Maximum number of representations (typically 4-7)
|
||||
*/
|
||||
constructor(capacity: number);
|
||||
/**
|
||||
* Clear all representations
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Run competitive dynamics (salience decay and pruning)
|
||||
*/
|
||||
compete(): void;
|
||||
/**
|
||||
* Check if workspace is at capacity
|
||||
*/
|
||||
is_full(): boolean;
|
||||
/**
|
||||
* Check if workspace is empty
|
||||
*/
|
||||
is_empty(): boolean;
|
||||
/**
|
||||
* Retrieve all current representations as JSON
|
||||
*/
|
||||
retrieve(): any;
|
||||
/**
|
||||
* Broadcast a representation to the workspace
|
||||
*
|
||||
* Returns true if accepted, false if rejected.
|
||||
*/
|
||||
broadcast(item: WorkspaceItem): boolean;
|
||||
/**
|
||||
* Get current number of representations
|
||||
*/
|
||||
readonly len: number;
|
||||
/**
|
||||
* Get workspace capacity
|
||||
*/
|
||||
readonly capacity: number;
|
||||
}
|
||||
|
||||
export class HdcMemory {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Get a vector by label
|
||||
*/
|
||||
get(label: string): Hypervector | undefined;
|
||||
/**
|
||||
* Check if a label exists
|
||||
*/
|
||||
has(label: string): boolean;
|
||||
/**
|
||||
* Create a new empty HDC memory
|
||||
*/
|
||||
constructor();
|
||||
/**
|
||||
* Clear all stored vectors
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Store a hypervector with a label
|
||||
*/
|
||||
store(label: string, vector: Hypervector): void;
|
||||
/**
|
||||
* Find the k most similar vectors to query
|
||||
*/
|
||||
top_k(query: Hypervector, k: number): any;
|
||||
/**
|
||||
* Retrieve vectors similar to query above threshold
|
||||
*
|
||||
* Returns array of [label, similarity] pairs
|
||||
*/
|
||||
retrieve(query: Hypervector, threshold: number): any;
|
||||
/**
|
||||
* Get number of stored vectors
|
||||
*/
|
||||
readonly size: number;
|
||||
}
|
||||
|
||||
export class Hypervector {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Create from raw bytes
|
||||
*/
|
||||
static from_bytes(bytes: Uint8Array): Hypervector;
|
||||
/**
|
||||
* Compute similarity between two hypervectors
|
||||
*
|
||||
* Returns a value in [-1.0, 1.0] where:
|
||||
* - 1.0 = identical vectors
|
||||
* - 0.0 = random/orthogonal vectors
|
||||
* - -1.0 = completely opposite vectors
|
||||
*/
|
||||
similarity(other: Hypervector): number;
|
||||
/**
|
||||
* Compute Hamming distance (number of differing bits)
|
||||
*/
|
||||
hamming_distance(other: Hypervector): number;
|
||||
/**
|
||||
* Create a zero hypervector
|
||||
*/
|
||||
constructor();
|
||||
/**
|
||||
* Bind two hypervectors using XOR
|
||||
*
|
||||
* Binding is associative, commutative, and self-inverse:
|
||||
* - a.bind(b) == b.bind(a)
|
||||
* - a.bind(b).bind(b) == a
|
||||
*/
|
||||
bind(other: Hypervector): Hypervector;
|
||||
/**
|
||||
* Create a random hypervector with ~50% bits set
|
||||
*/
|
||||
static random(): Hypervector;
|
||||
/**
|
||||
* Bundle multiple vectors by majority voting on each bit
|
||||
*/
|
||||
static bundle_3(a: Hypervector, b: Hypervector, c: Hypervector): Hypervector;
|
||||
/**
|
||||
* Count the number of set bits (population count)
|
||||
*/
|
||||
popcount(): number;
|
||||
/**
|
||||
* Get the raw bits as Uint8Array (for serialization)
|
||||
*/
|
||||
to_bytes(): Uint8Array;
|
||||
/**
|
||||
* Create a hypervector from a seed for reproducibility
|
||||
*/
|
||||
static from_seed(seed: bigint): Hypervector;
|
||||
/**
|
||||
* Get number of bits
|
||||
*/
|
||||
readonly dimension: number;
|
||||
}
|
||||
|
||||
export class KWTALayer {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Set activation threshold
|
||||
*/
|
||||
with_threshold(threshold: number): void;
|
||||
/**
|
||||
* Select top-k neurons with their activation values
|
||||
*
|
||||
* Returns array of [index, value] pairs.
|
||||
*/
|
||||
select_with_values(inputs: Float32Array): any;
|
||||
/**
|
||||
* Create sparse activation vector (only top-k preserved)
|
||||
*/
|
||||
sparse_activations(inputs: Float32Array): Float32Array;
|
||||
/**
|
||||
* Create a new K-WTA layer
|
||||
*
|
||||
* # Arguments
|
||||
* * `size` - Total number of neurons
|
||||
* * `k` - Number of winners to select
|
||||
*/
|
||||
constructor(size: number, k: number);
|
||||
/**
|
||||
* Select top-k neurons
|
||||
*
|
||||
* Returns indices of k neurons with highest activations, sorted descending.
|
||||
*/
|
||||
select(inputs: Float32Array): Uint32Array;
|
||||
/**
|
||||
* Get number of winners
|
||||
*/
|
||||
readonly k: number;
|
||||
/**
|
||||
* Get layer size
|
||||
*/
|
||||
readonly size: number;
|
||||
}
|
||||
|
||||
export class WTALayer {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Soft competition with normalized activations
|
||||
*
|
||||
* Returns activation levels for all neurons after softmax-like normalization.
|
||||
*/
|
||||
compete_soft(inputs: Float32Array): Float32Array;
|
||||
/**
|
||||
* Get current membrane potentials
|
||||
*/
|
||||
get_membranes(): Float32Array;
|
||||
/**
|
||||
* Set refractory period
|
||||
*/
|
||||
set_refractory_period(period: number): void;
|
||||
/**
|
||||
* Create a new WTA layer
|
||||
*
|
||||
* # Arguments
|
||||
* * `size` - Number of competing neurons
|
||||
* * `threshold` - Activation threshold for firing
|
||||
* * `inhibition` - Lateral inhibition strength (0.0-1.0)
|
||||
*/
|
||||
constructor(size: number, threshold: number, inhibition: number);
|
||||
/**
|
||||
* Reset layer state
|
||||
*/
|
||||
reset(): void;
|
||||
/**
|
||||
* Run winner-take-all competition
|
||||
*
|
||||
* Returns the index of the winning neuron, or -1 if no neuron exceeds threshold.
|
||||
*/
|
||||
compete(inputs: Float32Array): number;
|
||||
/**
|
||||
* Get layer size
|
||||
*/
|
||||
readonly size: number;
|
||||
}
|
||||
|
||||
export class WorkspaceItem {
|
||||
free(): void;
|
||||
[Symbol.dispose](): void;
|
||||
/**
|
||||
* Check if expired
|
||||
*/
|
||||
is_expired(current_time: bigint): boolean;
|
||||
/**
|
||||
* Create with custom decay and lifetime
|
||||
*/
|
||||
static with_decay(content: Float32Array, salience: number, source_module: number, timestamp: bigint, decay_rate: number, lifetime: bigint): WorkspaceItem;
|
||||
/**
|
||||
* Apply temporal decay
|
||||
*/
|
||||
apply_decay(dt: number): void;
|
||||
/**
|
||||
* Get content as Float32Array
|
||||
*/
|
||||
get_content(): Float32Array;
|
||||
/**
|
||||
* Update salience
|
||||
*/
|
||||
update_salience(new_salience: number): void;
|
||||
/**
|
||||
* Create a new workspace item
|
||||
*/
|
||||
constructor(content: Float32Array, salience: number, source_module: number, timestamp: bigint);
|
||||
/**
|
||||
* Compute content magnitude (L2 norm)
|
||||
*/
|
||||
magnitude(): number;
|
||||
/**
|
||||
* Get source module
|
||||
*/
|
||||
readonly source_module: number;
|
||||
/**
|
||||
* Get ID
|
||||
*/
|
||||
readonly id: bigint;
|
||||
/**
|
||||
* Get salience
|
||||
*/
|
||||
readonly salience: number;
|
||||
/**
|
||||
* Get timestamp
|
||||
*/
|
||||
readonly timestamp: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about available bio-inspired mechanisms
|
||||
*/
|
||||
export function available_mechanisms(): any;
|
||||
|
||||
/**
|
||||
* Get biological references for the mechanisms
|
||||
*/
|
||||
export function biological_references(): any;
|
||||
|
||||
/**
|
||||
* Initialize the WASM module with panic hook
|
||||
*/
|
||||
export function init(): void;
|
||||
|
||||
/**
|
||||
* Get performance targets for each mechanism
|
||||
*/
|
||||
export function performance_targets(): any;
|
||||
|
||||
/**
|
||||
* Get the version of the crate
|
||||
*/
|
||||
export function version(): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly __wbg_btspassociativememory_free: (a: number, b: number) => void;
|
||||
readonly __wbg_btsplayer_free: (a: number, b: number) => void;
|
||||
readonly __wbg_btspsynapse_free: (a: number, b: number) => void;
|
||||
readonly __wbg_globalworkspace_free: (a: number, b: number) => void;
|
||||
readonly __wbg_hdcmemory_free: (a: number, b: number) => void;
|
||||
readonly __wbg_hypervector_free: (a: number, b: number) => void;
|
||||
readonly __wbg_kwtalayer_free: (a: number, b: number) => void;
|
||||
readonly __wbg_workspaceitem_free: (a: number, b: number) => void;
|
||||
readonly __wbg_wtalayer_free: (a: number, b: number) => void;
|
||||
readonly available_mechanisms: () => number;
|
||||
readonly biological_references: () => number;
|
||||
readonly btspassociativememory_dimensions: (a: number) => number;
|
||||
readonly btspassociativememory_new: (a: number, b: number) => number;
|
||||
readonly btspassociativememory_retrieve: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly btspassociativememory_store_one_shot: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
||||
readonly btsplayer_forward: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly btsplayer_get_weights: (a: number) => number;
|
||||
readonly btsplayer_new: (a: number, b: number) => number;
|
||||
readonly btsplayer_one_shot_associate: (a: number, b: number, c: number, d: number, e: number) => void;
|
||||
readonly btsplayer_reset: (a: number) => void;
|
||||
readonly btsplayer_size: (a: number) => number;
|
||||
readonly btspsynapse_eligibility_trace: (a: number) => number;
|
||||
readonly btspsynapse_forward: (a: number, b: number) => number;
|
||||
readonly btspsynapse_new: (a: number, b: number, c: number) => void;
|
||||
readonly btspsynapse_update: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly btspsynapse_weight: (a: number) => number;
|
||||
readonly globalworkspace_available_slots: (a: number) => number;
|
||||
readonly globalworkspace_average_salience: (a: number) => number;
|
||||
readonly globalworkspace_broadcast: (a: number, b: number) => number;
|
||||
readonly globalworkspace_capacity: (a: number) => number;
|
||||
readonly globalworkspace_clear: (a: number) => void;
|
||||
readonly globalworkspace_compete: (a: number) => void;
|
||||
readonly globalworkspace_current_load: (a: number) => number;
|
||||
readonly globalworkspace_is_empty: (a: number) => number;
|
||||
readonly globalworkspace_is_full: (a: number) => number;
|
||||
readonly globalworkspace_len: (a: number) => number;
|
||||
readonly globalworkspace_most_salient: (a: number) => number;
|
||||
readonly globalworkspace_new: (a: number) => number;
|
||||
readonly globalworkspace_retrieve: (a: number) => number;
|
||||
readonly globalworkspace_retrieve_top_k: (a: number, b: number) => number;
|
||||
readonly globalworkspace_set_decay_rate: (a: number, b: number) => void;
|
||||
readonly globalworkspace_with_threshold: (a: number, b: number) => number;
|
||||
readonly hdcmemory_clear: (a: number) => void;
|
||||
readonly hdcmemory_get: (a: number, b: number, c: number) => number;
|
||||
readonly hdcmemory_has: (a: number, b: number, c: number) => number;
|
||||
readonly hdcmemory_new: () => number;
|
||||
readonly hdcmemory_retrieve: (a: number, b: number, c: number) => number;
|
||||
readonly hdcmemory_size: (a: number) => number;
|
||||
readonly hdcmemory_store: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly hdcmemory_top_k: (a: number, b: number, c: number) => number;
|
||||
readonly hypervector_bind: (a: number, b: number) => number;
|
||||
readonly hypervector_bundle_3: (a: number, b: number, c: number) => number;
|
||||
readonly hypervector_dimension: (a: number) => number;
|
||||
readonly hypervector_from_bytes: (a: number, b: number, c: number) => void;
|
||||
readonly hypervector_from_seed: (a: bigint) => number;
|
||||
readonly hypervector_hamming_distance: (a: number, b: number) => number;
|
||||
readonly hypervector_new: () => number;
|
||||
readonly hypervector_popcount: (a: number) => number;
|
||||
readonly hypervector_random: () => number;
|
||||
readonly hypervector_similarity: (a: number, b: number) => number;
|
||||
readonly hypervector_to_bytes: (a: number) => number;
|
||||
readonly kwtalayer_k: (a: number) => number;
|
||||
readonly kwtalayer_new: (a: number, b: number, c: number) => void;
|
||||
readonly kwtalayer_select: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly kwtalayer_select_with_values: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly kwtalayer_size: (a: number) => number;
|
||||
readonly kwtalayer_sparse_activations: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly kwtalayer_with_threshold: (a: number, b: number) => void;
|
||||
readonly performance_targets: () => number;
|
||||
readonly version: (a: number) => void;
|
||||
readonly workspaceitem_apply_decay: (a: number, b: number) => void;
|
||||
readonly workspaceitem_get_content: (a: number) => number;
|
||||
readonly workspaceitem_id: (a: number) => bigint;
|
||||
readonly workspaceitem_is_expired: (a: number, b: bigint) => number;
|
||||
readonly workspaceitem_magnitude: (a: number) => number;
|
||||
readonly workspaceitem_new: (a: number, b: number, c: number, d: number, e: bigint) => number;
|
||||
readonly workspaceitem_salience: (a: number) => number;
|
||||
readonly workspaceitem_source_module: (a: number) => number;
|
||||
readonly workspaceitem_timestamp: (a: number) => bigint;
|
||||
readonly workspaceitem_update_salience: (a: number, b: number) => void;
|
||||
readonly workspaceitem_with_decay: (a: number, b: number, c: number, d: number, e: bigint, f: number, g: bigint) => number;
|
||||
readonly wtalayer_compete: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly wtalayer_compete_soft: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly wtalayer_get_membranes: (a: number) => number;
|
||||
readonly wtalayer_new: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly wtalayer_reset: (a: number) => void;
|
||||
readonly wtalayer_set_refractory_period: (a: number, b: number) => void;
|
||||
readonly init: () => void;
|
||||
readonly wtalayer_size: (a: number) => number;
|
||||
readonly __wbindgen_export: (a: number, b: number) => number;
|
||||
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_export3: (a: number) => void;
|
||||
readonly __wbindgen_export4: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
1647
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/ruvector_nervous_system_wasm.js
vendored
Normal file
1647
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/ruvector_nervous_system_wasm.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/ruvector_nervous_system_wasm_bg.wasm
vendored
Normal file
BIN
vendor/ruvector/crates/ruvector-nervous-system-wasm/pkg/ruvector_nervous_system_wasm_bg.wasm
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,98 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const __wbg_btspassociativememory_free: (a: number, b: number) => void;
|
||||
export const __wbg_btsplayer_free: (a: number, b: number) => void;
|
||||
export const __wbg_btspsynapse_free: (a: number, b: number) => void;
|
||||
export const __wbg_globalworkspace_free: (a: number, b: number) => void;
|
||||
export const __wbg_hdcmemory_free: (a: number, b: number) => void;
|
||||
export const __wbg_hypervector_free: (a: number, b: number) => void;
|
||||
export const __wbg_kwtalayer_free: (a: number, b: number) => void;
|
||||
export const __wbg_workspaceitem_free: (a: number, b: number) => void;
|
||||
export const __wbg_wtalayer_free: (a: number, b: number) => void;
|
||||
export const available_mechanisms: () => number;
|
||||
export const biological_references: () => number;
|
||||
export const btspassociativememory_dimensions: (a: number) => number;
|
||||
export const btspassociativememory_new: (a: number, b: number) => number;
|
||||
export const btspassociativememory_retrieve: (a: number, b: number, c: number, d: number) => void;
|
||||
export const btspassociativememory_store_one_shot: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
||||
export const btsplayer_forward: (a: number, b: number, c: number, d: number) => void;
|
||||
export const btsplayer_get_weights: (a: number) => number;
|
||||
export const btsplayer_new: (a: number, b: number) => number;
|
||||
export const btsplayer_one_shot_associate: (a: number, b: number, c: number, d: number, e: number) => void;
|
||||
export const btsplayer_reset: (a: number) => void;
|
||||
export const btsplayer_size: (a: number) => number;
|
||||
export const btspsynapse_eligibility_trace: (a: number) => number;
|
||||
export const btspsynapse_forward: (a: number, b: number) => number;
|
||||
export const btspsynapse_new: (a: number, b: number, c: number) => void;
|
||||
export const btspsynapse_update: (a: number, b: number, c: number, d: number) => void;
|
||||
export const btspsynapse_weight: (a: number) => number;
|
||||
export const globalworkspace_available_slots: (a: number) => number;
|
||||
export const globalworkspace_average_salience: (a: number) => number;
|
||||
export const globalworkspace_broadcast: (a: number, b: number) => number;
|
||||
export const globalworkspace_capacity: (a: number) => number;
|
||||
export const globalworkspace_clear: (a: number) => void;
|
||||
export const globalworkspace_compete: (a: number) => void;
|
||||
export const globalworkspace_current_load: (a: number) => number;
|
||||
export const globalworkspace_is_empty: (a: number) => number;
|
||||
export const globalworkspace_is_full: (a: number) => number;
|
||||
export const globalworkspace_len: (a: number) => number;
|
||||
export const globalworkspace_most_salient: (a: number) => number;
|
||||
export const globalworkspace_new: (a: number) => number;
|
||||
export const globalworkspace_retrieve: (a: number) => number;
|
||||
export const globalworkspace_retrieve_top_k: (a: number, b: number) => number;
|
||||
export const globalworkspace_set_decay_rate: (a: number, b: number) => void;
|
||||
export const globalworkspace_with_threshold: (a: number, b: number) => number;
|
||||
export const hdcmemory_clear: (a: number) => void;
|
||||
export const hdcmemory_get: (a: number, b: number, c: number) => number;
|
||||
export const hdcmemory_has: (a: number, b: number, c: number) => number;
|
||||
export const hdcmemory_new: () => number;
|
||||
export const hdcmemory_retrieve: (a: number, b: number, c: number) => number;
|
||||
export const hdcmemory_size: (a: number) => number;
|
||||
export const hdcmemory_store: (a: number, b: number, c: number, d: number) => void;
|
||||
export const hdcmemory_top_k: (a: number, b: number, c: number) => number;
|
||||
export const hypervector_bind: (a: number, b: number) => number;
|
||||
export const hypervector_bundle_3: (a: number, b: number, c: number) => number;
|
||||
export const hypervector_dimension: (a: number) => number;
|
||||
export const hypervector_from_bytes: (a: number, b: number, c: number) => void;
|
||||
export const hypervector_from_seed: (a: bigint) => number;
|
||||
export const hypervector_hamming_distance: (a: number, b: number) => number;
|
||||
export const hypervector_new: () => number;
|
||||
export const hypervector_popcount: (a: number) => number;
|
||||
export const hypervector_random: () => number;
|
||||
export const hypervector_similarity: (a: number, b: number) => number;
|
||||
export const hypervector_to_bytes: (a: number) => number;
|
||||
export const kwtalayer_k: (a: number) => number;
|
||||
export const kwtalayer_new: (a: number, b: number, c: number) => void;
|
||||
export const kwtalayer_select: (a: number, b: number, c: number, d: number) => void;
|
||||
export const kwtalayer_select_with_values: (a: number, b: number, c: number, d: number) => void;
|
||||
export const kwtalayer_size: (a: number) => number;
|
||||
export const kwtalayer_sparse_activations: (a: number, b: number, c: number, d: number) => void;
|
||||
export const kwtalayer_with_threshold: (a: number, b: number) => void;
|
||||
export const performance_targets: () => number;
|
||||
export const version: (a: number) => void;
|
||||
export const workspaceitem_apply_decay: (a: number, b: number) => void;
|
||||
export const workspaceitem_get_content: (a: number) => number;
|
||||
export const workspaceitem_id: (a: number) => bigint;
|
||||
export const workspaceitem_is_expired: (a: number, b: bigint) => number;
|
||||
export const workspaceitem_magnitude: (a: number) => number;
|
||||
export const workspaceitem_new: (a: number, b: number, c: number, d: number, e: bigint) => number;
|
||||
export const workspaceitem_salience: (a: number) => number;
|
||||
export const workspaceitem_source_module: (a: number) => number;
|
||||
export const workspaceitem_timestamp: (a: number) => bigint;
|
||||
export const workspaceitem_update_salience: (a: number, b: number) => void;
|
||||
export const workspaceitem_with_decay: (a: number, b: number, c: number, d: number, e: bigint, f: number, g: bigint) => number;
|
||||
export const wtalayer_compete: (a: number, b: number, c: number, d: number) => void;
|
||||
export const wtalayer_compete_soft: (a: number, b: number, c: number, d: number) => void;
|
||||
export const wtalayer_get_membranes: (a: number) => number;
|
||||
export const wtalayer_new: (a: number, b: number, c: number, d: number) => void;
|
||||
export const wtalayer_reset: (a: number) => void;
|
||||
export const wtalayer_set_refractory_period: (a: number, b: number) => void;
|
||||
export const init: () => void;
|
||||
export const wtalayer_size: (a: number) => number;
|
||||
export const __wbindgen_export: (a: number, b: number) => number;
|
||||
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_export3: (a: number) => void;
|
||||
export const __wbindgen_export4: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
export const __wbindgen_start: () => void;
|
||||
310
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/btsp.rs
vendored
Normal file
310
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/btsp.rs
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
//! BTSP (Behavioral Timescale Synaptic Plasticity) WASM bindings
|
||||
//!
|
||||
//! One-shot learning for immediate pattern-target associations.
|
||||
//! Based on Bittner et al. 2017 hippocampal place field formation.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// BTSP synapse with eligibility trace and bidirectional plasticity
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct BTSPSynapse {
|
||||
weight: f32,
|
||||
eligibility_trace: f32,
|
||||
tau_btsp: f32,
|
||||
min_weight: f32,
|
||||
max_weight: f32,
|
||||
ltp_rate: f32,
|
||||
ltd_rate: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BTSPSynapse {
|
||||
/// Create a new BTSP synapse
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `initial_weight` - Starting weight (0.0 to 1.0)
|
||||
/// * `tau_btsp` - Time constant in milliseconds (1000-3000ms recommended)
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(initial_weight: f32, tau_btsp: f32) -> Result<BTSPSynapse, JsValue> {
|
||||
if !(0.0..=1.0).contains(&initial_weight) {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Invalid weight: {} (must be 0.0-1.0)",
|
||||
initial_weight
|
||||
)));
|
||||
}
|
||||
if tau_btsp <= 0.0 {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Invalid time constant: {} (must be > 0)",
|
||||
tau_btsp
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
weight: initial_weight,
|
||||
eligibility_trace: 0.0,
|
||||
tau_btsp,
|
||||
min_weight: 0.0,
|
||||
max_weight: 1.0,
|
||||
ltp_rate: 0.1,
|
||||
ltd_rate: 0.05,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update synapse based on activity and plateau signal
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `presynaptic_active` - Is presynaptic neuron firing?
|
||||
/// * `plateau_signal` - Dendritic plateau potential detected?
|
||||
/// * `dt` - Time step in milliseconds
|
||||
#[wasm_bindgen]
|
||||
pub fn update(&mut self, presynaptic_active: bool, plateau_signal: bool, dt: f32) {
|
||||
// Decay eligibility trace exponentially
|
||||
self.eligibility_trace *= (-dt / self.tau_btsp).exp();
|
||||
|
||||
// Accumulate trace when presynaptic neuron fires
|
||||
if presynaptic_active {
|
||||
self.eligibility_trace += 1.0;
|
||||
}
|
||||
|
||||
// Bidirectional plasticity gated by plateau potential
|
||||
if plateau_signal && self.eligibility_trace > 0.01 {
|
||||
let delta = if self.weight < 0.5 {
|
||||
self.ltp_rate // Potentiation
|
||||
} else {
|
||||
-self.ltd_rate // Depression
|
||||
};
|
||||
|
||||
self.weight += delta * self.eligibility_trace;
|
||||
self.weight = self.weight.clamp(self.min_weight, self.max_weight);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current weight
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn weight(&self) -> f32 {
|
||||
self.weight
|
||||
}
|
||||
|
||||
/// Get eligibility trace
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn eligibility_trace(&self) -> f32 {
|
||||
self.eligibility_trace
|
||||
}
|
||||
|
||||
/// Compute synaptic output
|
||||
#[wasm_bindgen]
|
||||
pub fn forward(&self, input: f32) -> f32 {
|
||||
self.weight * input
|
||||
}
|
||||
}
|
||||
|
||||
/// BTSP Layer for one-shot learning
|
||||
///
|
||||
/// # Performance
|
||||
/// - One-shot learning: immediate, no iteration
|
||||
/// - Forward pass: <10us for 10K synapses
|
||||
#[wasm_bindgen]
|
||||
pub struct BTSPLayer {
|
||||
weights: Vec<f32>,
|
||||
eligibility_traces: Vec<f32>,
|
||||
#[allow(dead_code)]
|
||||
tau_btsp: f32,
|
||||
#[allow(dead_code)]
|
||||
plateau_threshold: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BTSPLayer {
|
||||
/// Create a new BTSP layer
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `size` - Number of synapses (input dimension)
|
||||
/// * `tau` - Time constant in milliseconds (2000ms default)
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(size: usize, tau: f32) -> BTSPLayer {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let weights: Vec<f32> = (0..size).map(|_| rng.gen_range(0.0..0.1)).collect();
|
||||
let eligibility_traces = vec![0.0; size];
|
||||
|
||||
Self {
|
||||
weights,
|
||||
eligibility_traces,
|
||||
tau_btsp: tau,
|
||||
plateau_threshold: 0.7,
|
||||
}
|
||||
}
|
||||
|
||||
/// Forward pass: compute layer output
|
||||
#[wasm_bindgen]
|
||||
pub fn forward(&self, input: &[f32]) -> Result<f32, JsValue> {
|
||||
if input.len() != self.weights.len() {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Input size mismatch: expected {}, got {}",
|
||||
self.weights.len(),
|
||||
input.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.weights
|
||||
.iter()
|
||||
.zip(input.iter())
|
||||
.map(|(&w, &x)| w * x)
|
||||
.sum())
|
||||
}
|
||||
|
||||
/// One-shot association: learn pattern -> target in single step
|
||||
///
|
||||
/// This is the key BTSP capability: immediate learning without iteration.
|
||||
/// Uses gradient normalization for single-step convergence.
|
||||
#[wasm_bindgen]
|
||||
pub fn one_shot_associate(&mut self, pattern: &[f32], target: f32) -> Result<(), JsValue> {
|
||||
if pattern.len() != self.weights.len() {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Pattern size mismatch: expected {}, got {}",
|
||||
self.weights.len(),
|
||||
pattern.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Current output
|
||||
let current: f32 = self
|
||||
.weights
|
||||
.iter()
|
||||
.zip(pattern.iter())
|
||||
.map(|(&w, &x)| w * x)
|
||||
.sum();
|
||||
|
||||
// Compute required weight change
|
||||
let error = target - current;
|
||||
|
||||
// Compute sum of squared inputs for gradient normalization
|
||||
let sum_squared: f32 = pattern.iter().map(|&x| x * x).sum();
|
||||
if sum_squared < 1e-8 {
|
||||
return Ok(()); // No active inputs
|
||||
}
|
||||
|
||||
// Set eligibility traces and update weights
|
||||
for (i, &input_val) in pattern.iter().enumerate() {
|
||||
if input_val.abs() > 0.01 {
|
||||
// Set trace proportional to input
|
||||
self.eligibility_traces[i] = input_val;
|
||||
|
||||
// Direct weight update: delta = error * x / sum(x^2)
|
||||
let delta = error * input_val / sum_squared;
|
||||
self.weights[i] += delta;
|
||||
self.weights[i] = self.weights[i].clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get number of synapses
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.weights.len()
|
||||
}
|
||||
|
||||
/// Get weights as Float32Array
|
||||
#[wasm_bindgen]
|
||||
pub fn get_weights(&self) -> js_sys::Float32Array {
|
||||
js_sys::Float32Array::from(self.weights.as_slice())
|
||||
}
|
||||
|
||||
/// Reset layer to initial state
|
||||
#[wasm_bindgen]
|
||||
pub fn reset(&mut self) {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
for w in &mut self.weights {
|
||||
*w = rng.gen_range(0.0..0.1);
|
||||
}
|
||||
self.eligibility_traces.fill(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Associative memory using BTSP for key-value storage
|
||||
#[wasm_bindgen]
|
||||
pub struct BTSPAssociativeMemory {
|
||||
layers: Vec<BTSPLayer>,
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl BTSPAssociativeMemory {
|
||||
/// Create new associative memory
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `input_size` - Dimension of key vectors
|
||||
/// * `output_size` - Dimension of value vectors
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(input_size: usize, output_size: usize) -> BTSPAssociativeMemory {
|
||||
let tau = 2000.0;
|
||||
let layers = (0..output_size)
|
||||
.map(|_| BTSPLayer::new(input_size, tau))
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
layers,
|
||||
input_size,
|
||||
output_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Store key-value association in one shot
|
||||
#[wasm_bindgen]
|
||||
pub fn store_one_shot(&mut self, key: &[f32], value: &[f32]) -> Result<(), JsValue> {
|
||||
if key.len() != self.input_size {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Key size mismatch: expected {}, got {}",
|
||||
self.input_size,
|
||||
key.len()
|
||||
)));
|
||||
}
|
||||
if value.len() != self.output_size {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Value size mismatch: expected {}, got {}",
|
||||
self.output_size,
|
||||
value.len()
|
||||
)));
|
||||
}
|
||||
|
||||
for (layer, &target) in self.layers.iter_mut().zip(value.iter()) {
|
||||
layer.one_shot_associate(key, target)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve value from key
|
||||
#[wasm_bindgen]
|
||||
pub fn retrieve(&self, query: &[f32]) -> Result<js_sys::Float32Array, JsValue> {
|
||||
if query.len() != self.input_size {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Query size mismatch: expected {}, got {}",
|
||||
self.input_size,
|
||||
query.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let output: Vec<f32> = self
|
||||
.layers
|
||||
.iter()
|
||||
.map(|layer| layer.forward(query).unwrap_or(0.0))
|
||||
.collect();
|
||||
|
||||
Ok(js_sys::Float32Array::from(output.as_slice()))
|
||||
}
|
||||
|
||||
/// Get memory dimensions
|
||||
#[wasm_bindgen]
|
||||
pub fn dimensions(&self) -> JsValue {
|
||||
let dims = serde_wasm_bindgen::to_value(&(self.input_size, self.output_size));
|
||||
dims.unwrap_or(JsValue::NULL)
|
||||
}
|
||||
}
|
||||
272
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/hdc.rs
vendored
Normal file
272
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/hdc.rs
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
//! Hyperdimensional Computing (HDC) WASM bindings
|
||||
//!
|
||||
//! 10,000-bit binary hypervectors with ultra-fast operations:
|
||||
//! - XOR binding: <50ns
|
||||
//! - Hamming similarity: <100ns via SIMD
|
||||
//! - 10^40 representational capacity
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Number of bits in a hypervector
|
||||
const HYPERVECTOR_BITS: usize = 10_000;
|
||||
|
||||
/// Number of u64 words needed (ceil(10000/64) = 157)
|
||||
const HYPERVECTOR_U64_LEN: usize = 157;
|
||||
|
||||
/// A binary hypervector with 10,000 bits
|
||||
///
|
||||
/// # Performance
|
||||
/// - Memory: 1,248 bytes per vector
|
||||
/// - XOR binding: <50ns
|
||||
/// - Similarity: <100ns with SIMD popcount
|
||||
#[wasm_bindgen]
|
||||
pub struct Hypervector {
|
||||
bits: Vec<u64>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Hypervector {
|
||||
/// Create a zero hypervector
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Hypervector {
|
||||
Self {
|
||||
bits: vec![0u64; HYPERVECTOR_U64_LEN],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a random hypervector with ~50% bits set
|
||||
#[wasm_bindgen]
|
||||
pub fn random() -> Hypervector {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
let bits: Vec<u64> = (0..HYPERVECTOR_U64_LEN).map(|_| rng.gen()).collect();
|
||||
Self { bits }
|
||||
}
|
||||
|
||||
/// Create a hypervector from a seed for reproducibility
|
||||
#[wasm_bindgen]
|
||||
pub fn from_seed(seed: u64) -> Hypervector {
|
||||
use rand::{Rng, SeedableRng};
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
|
||||
let bits: Vec<u64> = (0..HYPERVECTOR_U64_LEN).map(|_| rng.gen()).collect();
|
||||
Self { bits }
|
||||
}
|
||||
|
||||
/// Bind two hypervectors using XOR
|
||||
///
|
||||
/// Binding is associative, commutative, and self-inverse:
|
||||
/// - a.bind(b) == b.bind(a)
|
||||
/// - a.bind(b).bind(b) == a
|
||||
#[wasm_bindgen]
|
||||
pub fn bind(&self, other: &Hypervector) -> Hypervector {
|
||||
let bits: Vec<u64> = self
|
||||
.bits
|
||||
.iter()
|
||||
.zip(other.bits.iter())
|
||||
.map(|(&a, &b)| a ^ b)
|
||||
.collect();
|
||||
Self { bits }
|
||||
}
|
||||
|
||||
/// Compute similarity between two hypervectors
|
||||
///
|
||||
/// Returns a value in [-1.0, 1.0] where:
|
||||
/// - 1.0 = identical vectors
|
||||
/// - 0.0 = random/orthogonal vectors
|
||||
/// - -1.0 = completely opposite vectors
|
||||
#[wasm_bindgen]
|
||||
pub fn similarity(&self, other: &Hypervector) -> f32 {
|
||||
let hamming = self.hamming_distance(other);
|
||||
1.0 - (2.0 * hamming as f32 / HYPERVECTOR_BITS as f32)
|
||||
}
|
||||
|
||||
/// Compute Hamming distance (number of differing bits)
|
||||
#[wasm_bindgen]
|
||||
pub fn hamming_distance(&self, other: &Hypervector) -> u32 {
|
||||
// Unrolled loop for better instruction-level parallelism
|
||||
let mut d0 = 0u32;
|
||||
let mut d1 = 0u32;
|
||||
let mut d2 = 0u32;
|
||||
let mut d3 = 0u32;
|
||||
|
||||
let chunks = HYPERVECTOR_U64_LEN / 4;
|
||||
let remainder = HYPERVECTOR_U64_LEN % 4;
|
||||
|
||||
for i in 0..chunks {
|
||||
let base = i * 4;
|
||||
d0 += (self.bits[base] ^ other.bits[base]).count_ones();
|
||||
d1 += (self.bits[base + 1] ^ other.bits[base + 1]).count_ones();
|
||||
d2 += (self.bits[base + 2] ^ other.bits[base + 2]).count_ones();
|
||||
d3 += (self.bits[base + 3] ^ other.bits[base + 3]).count_ones();
|
||||
}
|
||||
|
||||
let base = chunks * 4;
|
||||
for i in 0..remainder {
|
||||
d0 += (self.bits[base + i] ^ other.bits[base + i]).count_ones();
|
||||
}
|
||||
|
||||
d0 + d1 + d2 + d3
|
||||
}
|
||||
|
||||
/// Count the number of set bits (population count)
|
||||
#[wasm_bindgen]
|
||||
pub fn popcount(&self) -> u32 {
|
||||
self.bits.iter().map(|&w| w.count_ones()).sum()
|
||||
}
|
||||
|
||||
/// Bundle multiple vectors by majority voting on each bit
|
||||
#[wasm_bindgen]
|
||||
pub fn bundle_3(a: &Hypervector, b: &Hypervector, c: &Hypervector) -> Hypervector {
|
||||
// Majority of 3 bits: (a & b) | (b & c) | (a & c)
|
||||
let bits: Vec<u64> = (0..HYPERVECTOR_U64_LEN)
|
||||
.map(|i| {
|
||||
let wa = a.bits[i];
|
||||
let wb = b.bits[i];
|
||||
let wc = c.bits[i];
|
||||
(wa & wb) | (wb & wc) | (wa & wc)
|
||||
})
|
||||
.collect();
|
||||
Self { bits }
|
||||
}
|
||||
|
||||
/// Get the raw bits as Uint8Array (for serialization)
|
||||
#[wasm_bindgen]
|
||||
pub fn to_bytes(&self) -> js_sys::Uint8Array {
|
||||
let bytes: Vec<u8> = self.bits.iter().flat_map(|&w| w.to_le_bytes()).collect();
|
||||
js_sys::Uint8Array::from(bytes.as_slice())
|
||||
}
|
||||
|
||||
/// Create from raw bytes
|
||||
#[wasm_bindgen]
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Hypervector, JsValue> {
|
||||
if bytes.len() != HYPERVECTOR_U64_LEN * 8 {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Invalid byte length: expected {}, got {}",
|
||||
HYPERVECTOR_U64_LEN * 8,
|
||||
bytes.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let bits: Vec<u64> = bytes
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
|
||||
.collect();
|
||||
|
||||
Ok(Self { bits })
|
||||
}
|
||||
|
||||
/// Get number of bits
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn dimension(&self) -> usize {
|
||||
HYPERVECTOR_BITS
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Hypervector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// HDC Memory for storing and retrieving hypervectors by label
|
||||
#[wasm_bindgen]
|
||||
pub struct HdcMemory {
|
||||
labels: Vec<String>,
|
||||
vectors: Vec<Hypervector>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl HdcMemory {
|
||||
/// Create a new empty HDC memory
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> HdcMemory {
|
||||
Self {
|
||||
labels: Vec::new(),
|
||||
vectors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Store a hypervector with a label
|
||||
#[wasm_bindgen]
|
||||
pub fn store(&mut self, label: &str, vector: Hypervector) {
|
||||
// Check if label exists
|
||||
if let Some(idx) = self.labels.iter().position(|l| l == label) {
|
||||
self.vectors[idx] = vector;
|
||||
} else {
|
||||
self.labels.push(label.to_string());
|
||||
self.vectors.push(vector);
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve vectors similar to query above threshold
|
||||
///
|
||||
/// Returns array of [label, similarity] pairs
|
||||
#[wasm_bindgen]
|
||||
pub fn retrieve(&self, query: &Hypervector, threshold: f32) -> JsValue {
|
||||
let mut results: Vec<(String, f32)> = Vec::new();
|
||||
|
||||
for (label, vector) in self.labels.iter().zip(self.vectors.iter()) {
|
||||
let sim = query.similarity(vector);
|
||||
if sim >= threshold {
|
||||
results.push((label.clone(), sim));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by similarity descending
|
||||
results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
serde_wasm_bindgen::to_value(&results).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Find the k most similar vectors to query
|
||||
#[wasm_bindgen]
|
||||
pub fn top_k(&self, query: &Hypervector, k: usize) -> JsValue {
|
||||
let mut similarities: Vec<(String, f32)> = self
|
||||
.labels
|
||||
.iter()
|
||||
.zip(self.vectors.iter())
|
||||
.map(|(label, vector)| (label.clone(), query.similarity(vector)))
|
||||
.collect();
|
||||
|
||||
similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||
similarities.truncate(k);
|
||||
|
||||
serde_wasm_bindgen::to_value(&similarities).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Get number of stored vectors
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.vectors.len()
|
||||
}
|
||||
|
||||
/// Clear all stored vectors
|
||||
#[wasm_bindgen]
|
||||
pub fn clear(&mut self) {
|
||||
self.labels.clear();
|
||||
self.vectors.clear();
|
||||
}
|
||||
|
||||
/// Check if a label exists
|
||||
#[wasm_bindgen]
|
||||
pub fn has(&self, label: &str) -> bool {
|
||||
self.labels.iter().any(|l| l == label)
|
||||
}
|
||||
|
||||
/// Get a vector by label
|
||||
#[wasm_bindgen]
|
||||
pub fn get(&self, label: &str) -> Option<Hypervector> {
|
||||
self.labels
|
||||
.iter()
|
||||
.position(|l| l == label)
|
||||
.map(|idx| Hypervector {
|
||||
bits: self.vectors[idx].bits.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HdcMemory {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
156
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/lib.rs
vendored
Normal file
156
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/lib.rs
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
//! # RuVector Nervous System WASM
|
||||
//!
|
||||
//! Bio-inspired neural system components for browser execution.
|
||||
//!
|
||||
//! ## Components
|
||||
//!
|
||||
//! - **BTSP** (Behavioral Timescale Synaptic Plasticity) - One-shot learning
|
||||
//! - **HDC** (Hyperdimensional Computing) - 10,000-bit binary hypervectors
|
||||
//! - **WTA** (Winner-Take-All) - <1us instant decisions
|
||||
//! - **Global Workspace** - 4-7 item attention bottleneck
|
||||
//!
|
||||
//! ## Performance Targets
|
||||
//!
|
||||
//! | Component | Target | Method |
|
||||
//! |-----------|--------|--------|
|
||||
//! | BTSP one_shot_associate | Immediate | Gradient normalization |
|
||||
//! | HDC bind | <50ns | XOR operation |
|
||||
//! | HDC similarity | <100ns | Hamming distance + SIMD |
|
||||
//! | WTA compete | <1us | Single-pass argmax |
|
||||
//! | K-WTA select | <10us | Partial sort |
|
||||
//! | Workspace broadcast | <10us | Competition |
|
||||
//!
|
||||
//! ## Bundle Size
|
||||
//!
|
||||
//! Target: <100KB with all bio-inspired mechanisms.
|
||||
//!
|
||||
//! ## Example Usage (JavaScript)
|
||||
//!
|
||||
//! ```javascript
|
||||
//! import init, {
|
||||
//! BTSPLayer,
|
||||
//! Hypervector,
|
||||
//! HdcMemory,
|
||||
//! WTALayer,
|
||||
//! KWTALayer,
|
||||
//! GlobalWorkspace,
|
||||
//! WorkspaceItem,
|
||||
//! } from 'ruvector-nervous-system-wasm';
|
||||
//!
|
||||
//! await init();
|
||||
//!
|
||||
//! // One-shot learning with BTSP
|
||||
//! const btsp = new BTSPLayer(100, 2000.0);
|
||||
//! const pattern = new Float32Array(100).fill(0.1);
|
||||
//! btsp.one_shot_associate(pattern, 1.0);
|
||||
//! const output = btsp.forward(pattern);
|
||||
//!
|
||||
//! // Hyperdimensional computing
|
||||
//! const apple = Hypervector.random();
|
||||
//! const orange = Hypervector.random();
|
||||
//! const fruit = apple.bind(orange);
|
||||
//! const similarity = apple.similarity(orange);
|
||||
//!
|
||||
//! const memory = new HdcMemory();
|
||||
//! memory.store("apple", apple);
|
||||
//! const results = memory.retrieve(apple, 0.9);
|
||||
//!
|
||||
//! // Instant decisions with WTA
|
||||
//! const wta = new WTALayer(1000, 0.5, 0.8);
|
||||
//! const activations = new Float32Array(1000);
|
||||
//! const winner = wta.compete(activations);
|
||||
//!
|
||||
//! // Sparse coding with K-WTA
|
||||
//! const kwta = new KWTALayer(1000, 50);
|
||||
//! const winners = kwta.select(activations);
|
||||
//!
|
||||
//! // Attention bottleneck with Global Workspace
|
||||
//! const workspace = new GlobalWorkspace(7); // Miller's Law: 7 +/- 2
|
||||
//! const item = new WorkspaceItem(new Float32Array([1, 2, 3]), 0.9, 1, Date.now());
|
||||
//! workspace.broadcast(item);
|
||||
//! ```
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod btsp;
|
||||
pub mod hdc;
|
||||
pub mod workspace;
|
||||
pub mod wta;
|
||||
|
||||
// Re-export all public types
|
||||
pub use btsp::{BTSPAssociativeMemory, BTSPLayer, BTSPSynapse};
|
||||
pub use hdc::{HdcMemory, Hypervector};
|
||||
pub use workspace::{GlobalWorkspace, WorkspaceItem};
|
||||
pub use wta::{KWTALayer, WTALayer};
|
||||
|
||||
/// Initialize the WASM module with panic hook
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
|
||||
/// Get the version of the crate
|
||||
#[wasm_bindgen]
|
||||
pub fn version() -> String {
|
||||
env!("CARGO_PKG_VERSION").to_string()
|
||||
}
|
||||
|
||||
/// Get information about available bio-inspired mechanisms
|
||||
#[wasm_bindgen]
|
||||
pub fn available_mechanisms() -> JsValue {
|
||||
let mechanisms = vec![
|
||||
(
|
||||
"btsp",
|
||||
"Behavioral Timescale Synaptic Plasticity - One-shot learning",
|
||||
),
|
||||
("hdc", "Hyperdimensional Computing - 10,000-bit vectors"),
|
||||
("wta", "Winner-Take-All - <1us decisions"),
|
||||
("kwta", "K-Winner-Take-All - Sparse distributed coding"),
|
||||
("workspace", "Global Workspace - 4-7 item attention"),
|
||||
];
|
||||
serde_wasm_bindgen::to_value(&mechanisms).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Get performance targets for each mechanism
|
||||
#[wasm_bindgen]
|
||||
pub fn performance_targets() -> JsValue {
|
||||
let targets = vec![
|
||||
("btsp_one_shot", "Immediate (no iteration)"),
|
||||
("hdc_bind", "<50ns"),
|
||||
("hdc_similarity", "<100ns"),
|
||||
("wta_compete", "<1us"),
|
||||
("kwta_select", "<10us (k=50, n=1000)"),
|
||||
("workspace_broadcast", "<10us"),
|
||||
];
|
||||
serde_wasm_bindgen::to_value(&targets).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Get biological references for the mechanisms
|
||||
#[wasm_bindgen]
|
||||
pub fn biological_references() -> JsValue {
|
||||
let refs = vec![
|
||||
("BTSP", "Bittner et al. 2017 - Hippocampal place fields"),
|
||||
(
|
||||
"HDC",
|
||||
"Kanerva 1988, Plate 2003 - Hyperdimensional computing",
|
||||
),
|
||||
("WTA", "Cortical microcircuits - Lateral inhibition"),
|
||||
(
|
||||
"Global Workspace",
|
||||
"Baars 1988, Dehaene 2014 - Consciousness",
|
||||
),
|
||||
];
|
||||
serde_wasm_bindgen::to_value(&refs).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_version() {
|
||||
let v = version();
|
||||
assert!(!v.is_empty());
|
||||
}
|
||||
}
|
||||
343
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/workspace.rs
vendored
Normal file
343
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/workspace.rs
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
//! Global Workspace WASM bindings
|
||||
//!
|
||||
//! Based on Global Workspace Theory (Baars, Dehaene):
|
||||
//! - 4-7 item capacity (Miller's law)
|
||||
//! - Broadcast/compete architecture
|
||||
//! - Relevance-based ignition
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Item in the global workspace
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct WorkspaceItem {
|
||||
content: Vec<f32>,
|
||||
salience: f32,
|
||||
source_module: u16,
|
||||
timestamp: u64,
|
||||
decay_rate: f32,
|
||||
lifetime: u64,
|
||||
id: u64,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WorkspaceItem {
|
||||
/// Create a new workspace item
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
content: &[f32],
|
||||
salience: f32,
|
||||
source_module: u16,
|
||||
timestamp: u64,
|
||||
) -> WorkspaceItem {
|
||||
Self {
|
||||
content: content.to_vec(),
|
||||
salience,
|
||||
source_module,
|
||||
timestamp,
|
||||
decay_rate: 0.95,
|
||||
lifetime: 1000,
|
||||
id: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with custom decay and lifetime
|
||||
#[wasm_bindgen]
|
||||
pub fn with_decay(
|
||||
content: &[f32],
|
||||
salience: f32,
|
||||
source_module: u16,
|
||||
timestamp: u64,
|
||||
decay_rate: f32,
|
||||
lifetime: u64,
|
||||
) -> WorkspaceItem {
|
||||
Self {
|
||||
content: content.to_vec(),
|
||||
salience,
|
||||
source_module,
|
||||
timestamp,
|
||||
decay_rate,
|
||||
lifetime,
|
||||
id: timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get content as Float32Array
|
||||
#[wasm_bindgen]
|
||||
pub fn get_content(&self) -> js_sys::Float32Array {
|
||||
js_sys::Float32Array::from(self.content.as_slice())
|
||||
}
|
||||
|
||||
/// Get salience
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn salience(&self) -> f32 {
|
||||
self.salience
|
||||
}
|
||||
|
||||
/// Get source module
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn source_module(&self) -> u16 {
|
||||
self.source_module
|
||||
}
|
||||
|
||||
/// Get timestamp
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn timestamp(&self) -> u64 {
|
||||
self.timestamp
|
||||
}
|
||||
|
||||
/// Get ID
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Compute content magnitude (L2 norm)
|
||||
#[wasm_bindgen]
|
||||
pub fn magnitude(&self) -> f32 {
|
||||
self.content.iter().map(|x| x * x).sum::<f32>().sqrt()
|
||||
}
|
||||
|
||||
/// Update salience
|
||||
#[wasm_bindgen]
|
||||
pub fn update_salience(&mut self, new_salience: f32) {
|
||||
self.salience = new_salience.max(0.0);
|
||||
}
|
||||
|
||||
/// Apply temporal decay
|
||||
#[wasm_bindgen]
|
||||
pub fn apply_decay(&mut self, dt: f32) {
|
||||
self.salience *= self.decay_rate.powf(dt);
|
||||
}
|
||||
|
||||
/// Check if expired
|
||||
#[wasm_bindgen]
|
||||
pub fn is_expired(&self, current_time: u64) -> bool {
|
||||
current_time.saturating_sub(self.timestamp) > self.lifetime
|
||||
}
|
||||
}
|
||||
|
||||
/// Global workspace with limited capacity and competitive dynamics
|
||||
///
|
||||
/// Implements attention and conscious access mechanisms based on
|
||||
/// Global Workspace Theory.
|
||||
#[wasm_bindgen]
|
||||
pub struct GlobalWorkspace {
|
||||
buffer: Vec<WorkspaceItem>,
|
||||
capacity: usize,
|
||||
salience_threshold: f32,
|
||||
timestamp: u64,
|
||||
salience_decay: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl GlobalWorkspace {
|
||||
/// Create a new global workspace
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `capacity` - Maximum number of representations (typically 4-7)
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(capacity: usize) -> GlobalWorkspace {
|
||||
Self {
|
||||
buffer: Vec::with_capacity(capacity),
|
||||
capacity,
|
||||
salience_threshold: 0.1,
|
||||
timestamp: 0,
|
||||
salience_decay: 0.95,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with custom threshold
|
||||
#[wasm_bindgen]
|
||||
pub fn with_threshold(capacity: usize, threshold: f32) -> GlobalWorkspace {
|
||||
Self {
|
||||
buffer: Vec::with_capacity(capacity),
|
||||
capacity,
|
||||
salience_threshold: threshold,
|
||||
timestamp: 0,
|
||||
salience_decay: 0.95,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set salience decay rate
|
||||
#[wasm_bindgen]
|
||||
pub fn set_decay_rate(&mut self, decay: f32) {
|
||||
self.salience_decay = decay.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Broadcast a representation to the workspace
|
||||
///
|
||||
/// Returns true if accepted, false if rejected.
|
||||
#[wasm_bindgen]
|
||||
pub fn broadcast(&mut self, item: WorkspaceItem) -> bool {
|
||||
self.timestamp += 1;
|
||||
let mut item = item;
|
||||
item.timestamp = self.timestamp;
|
||||
|
||||
// Reject if below threshold
|
||||
if item.salience < self.salience_threshold {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If workspace not full, add directly
|
||||
if self.buffer.len() < self.capacity {
|
||||
self.buffer.push(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If full, compete with weakest item
|
||||
if let Some(min_idx) = self.find_weakest() {
|
||||
if self.buffer[min_idx].salience < item.salience {
|
||||
self.buffer.swap_remove(min_idx);
|
||||
self.buffer.push(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Run competitive dynamics (salience decay and pruning)
|
||||
#[wasm_bindgen]
|
||||
pub fn compete(&mut self) {
|
||||
// Apply salience decay
|
||||
for item in self.buffer.iter_mut() {
|
||||
item.salience *= self.salience_decay;
|
||||
}
|
||||
|
||||
// Remove items below threshold
|
||||
self.buffer
|
||||
.retain(|item| item.salience >= self.salience_threshold);
|
||||
}
|
||||
|
||||
/// Retrieve all current representations as JSON
|
||||
#[wasm_bindgen]
|
||||
pub fn retrieve(&self) -> JsValue {
|
||||
let items: Vec<_> = self
|
||||
.buffer
|
||||
.iter()
|
||||
.map(|item| {
|
||||
serde_json::json!({
|
||||
"content": item.content,
|
||||
"salience": item.salience,
|
||||
"source_module": item.source_module,
|
||||
"timestamp": item.timestamp,
|
||||
"id": item.id
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
serde_wasm_bindgen::to_value(&items).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Retrieve top-k most salient representations
|
||||
#[wasm_bindgen]
|
||||
pub fn retrieve_top_k(&self, k: usize) -> JsValue {
|
||||
let mut items: Vec<_> = self.buffer.iter().collect();
|
||||
items.sort_by(|a, b| {
|
||||
b.salience
|
||||
.partial_cmp(&a.salience)
|
||||
.unwrap_or(std::cmp::Ordering::Less)
|
||||
});
|
||||
items.truncate(k);
|
||||
|
||||
let result: Vec<_> = items
|
||||
.iter()
|
||||
.map(|item| {
|
||||
serde_json::json!({
|
||||
"content": item.content,
|
||||
"salience": item.salience,
|
||||
"source_module": item.source_module,
|
||||
"timestamp": item.timestamp,
|
||||
"id": item.id
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
serde_wasm_bindgen::to_value(&result).unwrap_or(JsValue::NULL)
|
||||
}
|
||||
|
||||
/// Get most salient item
|
||||
#[wasm_bindgen]
|
||||
pub fn most_salient(&self) -> Option<WorkspaceItem> {
|
||||
self.buffer
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.salience
|
||||
.partial_cmp(&b.salience)
|
||||
.unwrap_or(std::cmp::Ordering::Less)
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Check if workspace is at capacity
|
||||
#[wasm_bindgen]
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.buffer.len() >= self.capacity
|
||||
}
|
||||
|
||||
/// Check if workspace is empty
|
||||
#[wasm_bindgen]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
|
||||
/// Get current number of representations
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.buffer.len()
|
||||
}
|
||||
|
||||
/// Get workspace capacity
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
/// Clear all representations
|
||||
#[wasm_bindgen]
|
||||
pub fn clear(&mut self) {
|
||||
self.buffer.clear();
|
||||
}
|
||||
|
||||
/// Get average salience
|
||||
#[wasm_bindgen]
|
||||
pub fn average_salience(&self) -> f32 {
|
||||
if self.buffer.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let sum: f32 = self.buffer.iter().map(|r| r.salience).sum();
|
||||
sum / self.buffer.len() as f32
|
||||
}
|
||||
|
||||
/// Get available slots
|
||||
#[wasm_bindgen]
|
||||
pub fn available_slots(&self) -> usize {
|
||||
self.capacity.saturating_sub(self.buffer.len())
|
||||
}
|
||||
|
||||
/// Get current load (0.0 to 1.0)
|
||||
#[wasm_bindgen]
|
||||
pub fn current_load(&self) -> f32 {
|
||||
self.buffer.len() as f32 / self.capacity as f32
|
||||
}
|
||||
|
||||
/// Find index of weakest representation
|
||||
fn find_weakest(&self) -> Option<usize> {
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut min_idx = 0;
|
||||
let mut min_salience = self.buffer[0].salience;
|
||||
|
||||
for (i, item) in self.buffer.iter().enumerate().skip(1) {
|
||||
if item.salience < min_salience {
|
||||
min_salience = item.salience;
|
||||
min_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
Some(min_idx)
|
||||
}
|
||||
}
|
||||
334
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/wta.rs
vendored
Normal file
334
vendor/ruvector/crates/ruvector-nervous-system-wasm/src/wta.rs
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
//! Winner-Take-All (WTA) WASM bindings
|
||||
//!
|
||||
//! Instant decisions via neural competition:
|
||||
//! - Single winner: <1us for 1000 neurons
|
||||
//! - K-WTA: <10us for k=50
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Winner-Take-All competition layer
|
||||
///
|
||||
/// Implements neural competition where the highest-activation neuron
|
||||
/// wins and suppresses others through lateral inhibition.
|
||||
///
|
||||
/// # Performance
|
||||
/// - <1us winner selection for 1000 neurons
|
||||
#[wasm_bindgen]
|
||||
pub struct WTALayer {
|
||||
membranes: Vec<f32>,
|
||||
threshold: f32,
|
||||
inhibition_strength: f32,
|
||||
refractory_period: u32,
|
||||
refractory_counters: Vec<u32>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WTALayer {
|
||||
/// Create a new WTA layer
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `size` - Number of competing neurons
|
||||
/// * `threshold` - Activation threshold for firing
|
||||
/// * `inhibition` - Lateral inhibition strength (0.0-1.0)
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(size: usize, threshold: f32, inhibition: f32) -> Result<WTALayer, JsValue> {
|
||||
if size == 0 {
|
||||
return Err(JsValue::from_str("Size must be > 0"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
membranes: vec![0.0; size],
|
||||
threshold,
|
||||
inhibition_strength: inhibition.clamp(0.0, 1.0),
|
||||
refractory_period: 10,
|
||||
refractory_counters: vec![0; size],
|
||||
})
|
||||
}
|
||||
|
||||
/// Run winner-take-all competition
|
||||
///
|
||||
/// Returns the index of the winning neuron, or -1 if no neuron exceeds threshold.
|
||||
#[wasm_bindgen]
|
||||
pub fn compete(&mut self, inputs: &[f32]) -> Result<i32, JsValue> {
|
||||
if inputs.len() != self.membranes.len() {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Input size mismatch: expected {}, got {}",
|
||||
self.membranes.len(),
|
||||
inputs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Single-pass: update membrane potentials and find max
|
||||
let mut best_idx: Option<usize> = None;
|
||||
let mut best_val = f32::NEG_INFINITY;
|
||||
|
||||
for (i, &input) in inputs.iter().enumerate() {
|
||||
if self.refractory_counters[i] == 0 {
|
||||
self.membranes[i] = input;
|
||||
if input > best_val {
|
||||
best_val = input;
|
||||
best_idx = Some(i);
|
||||
}
|
||||
} else {
|
||||
self.refractory_counters[i] = self.refractory_counters[i].saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
let winner_idx = match best_idx {
|
||||
Some(idx) => idx,
|
||||
None => return Ok(-1),
|
||||
};
|
||||
|
||||
// Check if winner exceeds threshold
|
||||
if best_val < self.threshold {
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
// Apply lateral inhibition
|
||||
for (i, membrane) in self.membranes.iter_mut().enumerate() {
|
||||
if i != winner_idx {
|
||||
*membrane *= 1.0 - self.inhibition_strength;
|
||||
}
|
||||
}
|
||||
|
||||
// Set refractory period for winner
|
||||
self.refractory_counters[winner_idx] = self.refractory_period;
|
||||
|
||||
Ok(winner_idx as i32)
|
||||
}
|
||||
|
||||
/// Soft competition with normalized activations
|
||||
///
|
||||
/// Returns activation levels for all neurons after softmax-like normalization.
|
||||
#[wasm_bindgen]
|
||||
pub fn compete_soft(&mut self, inputs: &[f32]) -> Result<js_sys::Float32Array, JsValue> {
|
||||
if inputs.len() != self.membranes.len() {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Input size mismatch: expected {}, got {}",
|
||||
self.membranes.len(),
|
||||
inputs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Update membrane potentials
|
||||
self.membranes.copy_from_slice(inputs);
|
||||
|
||||
// Find max for numerical stability
|
||||
let max_val = self
|
||||
.membranes
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0);
|
||||
|
||||
// Softmax with temperature
|
||||
let temperature = 1.0 / (1.0 + self.inhibition_strength);
|
||||
let mut activations: Vec<f32> = self
|
||||
.membranes
|
||||
.iter()
|
||||
.map(|&x| ((x - max_val) / temperature).exp())
|
||||
.collect();
|
||||
|
||||
// Normalize
|
||||
let sum: f32 = activations.iter().sum();
|
||||
if sum > 0.0 {
|
||||
for a in &mut activations {
|
||||
*a /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(js_sys::Float32Array::from(activations.as_slice()))
|
||||
}
|
||||
|
||||
/// Reset layer state
|
||||
#[wasm_bindgen]
|
||||
pub fn reset(&mut self) {
|
||||
self.membranes.fill(0.0);
|
||||
self.refractory_counters.fill(0);
|
||||
}
|
||||
|
||||
/// Get current membrane potentials
|
||||
#[wasm_bindgen]
|
||||
pub fn get_membranes(&self) -> js_sys::Float32Array {
|
||||
js_sys::Float32Array::from(self.membranes.as_slice())
|
||||
}
|
||||
|
||||
/// Set refractory period
|
||||
#[wasm_bindgen]
|
||||
pub fn set_refractory_period(&mut self, period: u32) {
|
||||
self.refractory_period = period;
|
||||
}
|
||||
|
||||
/// Get layer size
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.membranes.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// K-Winner-Take-All layer for sparse distributed coding
|
||||
///
|
||||
/// Selects top-k neurons with highest activations.
|
||||
///
|
||||
/// # Performance
|
||||
/// - O(n + k log k) using partial sorting
|
||||
/// - <10us for 1000 neurons, k=50
|
||||
#[wasm_bindgen]
|
||||
pub struct KWTALayer {
|
||||
size: usize,
|
||||
k: usize,
|
||||
threshold: Option<f32>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KWTALayer {
|
||||
/// Create a new K-WTA layer
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `size` - Total number of neurons
|
||||
/// * `k` - Number of winners to select
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(size: usize, k: usize) -> Result<KWTALayer, JsValue> {
|
||||
if k == 0 {
|
||||
return Err(JsValue::from_str("k must be > 0"));
|
||||
}
|
||||
if k > size {
|
||||
return Err(JsValue::from_str("k cannot exceed layer size"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
size,
|
||||
k,
|
||||
threshold: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set activation threshold
|
||||
#[wasm_bindgen]
|
||||
pub fn with_threshold(&mut self, threshold: f32) {
|
||||
self.threshold = Some(threshold);
|
||||
}
|
||||
|
||||
/// Select top-k neurons
|
||||
///
|
||||
/// Returns indices of k neurons with highest activations, sorted descending.
|
||||
#[wasm_bindgen]
|
||||
pub fn select(&self, inputs: &[f32]) -> Result<js_sys::Uint32Array, JsValue> {
|
||||
if inputs.len() != self.size {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Input size mismatch: expected {}, got {}",
|
||||
self.size,
|
||||
inputs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
// Create (index, value) pairs
|
||||
let mut indexed: Vec<(usize, f32)> =
|
||||
inputs.iter().enumerate().map(|(i, &v)| (i, v)).collect();
|
||||
|
||||
// Filter by threshold if set
|
||||
if let Some(threshold) = self.threshold {
|
||||
indexed.retain(|(_, v)| *v >= threshold);
|
||||
}
|
||||
|
||||
if indexed.is_empty() {
|
||||
return Ok(js_sys::Uint32Array::new_with_length(0));
|
||||
}
|
||||
|
||||
// Partial sort to get top-k
|
||||
let k_actual = self.k.min(indexed.len());
|
||||
indexed.select_nth_unstable_by(k_actual - 1, |a, b| {
|
||||
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
// Take top k and sort descending
|
||||
let mut winners: Vec<(usize, f32)> = indexed[..k_actual].to_vec();
|
||||
winners.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
// Return only indices as u32
|
||||
let indices: Vec<u32> = winners.into_iter().map(|(i, _)| i as u32).collect();
|
||||
Ok(js_sys::Uint32Array::from(indices.as_slice()))
|
||||
}
|
||||
|
||||
/// Select top-k neurons with their activation values
|
||||
///
|
||||
/// Returns array of [index, value] pairs.
|
||||
#[wasm_bindgen]
|
||||
pub fn select_with_values(&self, inputs: &[f32]) -> Result<JsValue, JsValue> {
|
||||
if inputs.len() != self.size {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Input size mismatch: expected {}, got {}",
|
||||
self.size,
|
||||
inputs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut indexed: Vec<(usize, f32)> =
|
||||
inputs.iter().enumerate().map(|(i, &v)| (i, v)).collect();
|
||||
|
||||
if let Some(threshold) = self.threshold {
|
||||
indexed.retain(|(_, v)| *v >= threshold);
|
||||
}
|
||||
|
||||
if indexed.is_empty() {
|
||||
return serde_wasm_bindgen::to_value(&Vec::<(usize, f32)>::new())
|
||||
.map_err(|e| JsValue::from_str(&e.to_string()));
|
||||
}
|
||||
|
||||
let k_actual = self.k.min(indexed.len());
|
||||
indexed.select_nth_unstable_by(k_actual - 1, |a, b| {
|
||||
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
let mut winners: Vec<(usize, f32)> = indexed[..k_actual].to_vec();
|
||||
winners.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||
|
||||
serde_wasm_bindgen::to_value(&winners).map_err(|e| JsValue::from_str(&e.to_string()))
|
||||
}
|
||||
|
||||
/// Create sparse activation vector (only top-k preserved)
|
||||
#[wasm_bindgen]
|
||||
pub fn sparse_activations(&self, inputs: &[f32]) -> Result<js_sys::Float32Array, JsValue> {
|
||||
if inputs.len() != self.size {
|
||||
return Err(JsValue::from_str(&format!(
|
||||
"Input size mismatch: expected {}, got {}",
|
||||
self.size,
|
||||
inputs.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut indexed: Vec<(usize, f32)> =
|
||||
inputs.iter().enumerate().map(|(i, &v)| (i, v)).collect();
|
||||
|
||||
if let Some(threshold) = self.threshold {
|
||||
indexed.retain(|(_, v)| *v >= threshold);
|
||||
}
|
||||
|
||||
let mut sparse = vec![0.0; self.size];
|
||||
|
||||
if !indexed.is_empty() {
|
||||
let k_actual = self.k.min(indexed.len());
|
||||
indexed.select_nth_unstable_by(k_actual - 1, |a, b| {
|
||||
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
for (idx, value) in &indexed[..k_actual] {
|
||||
sparse[*idx] = *value;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(js_sys::Float32Array::from(sparse.as_slice()))
|
||||
}
|
||||
|
||||
/// Get number of winners
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn k(&self) -> usize {
|
||||
self.k
|
||||
}
|
||||
|
||||
/// Get layer size
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
321
vendor/ruvector/crates/ruvector-nervous-system-wasm/tests/web.rs
vendored
Normal file
321
vendor/ruvector/crates/ruvector-nervous-system-wasm/tests/web.rs
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
//! Web tests for ruvector-nervous-system-wasm
|
||||
//!
|
||||
//! Run with: wasm-pack test --headless --chrome
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
use ruvector_nervous_system_wasm::*;
|
||||
|
||||
// ============================================================================
|
||||
// BTSP Tests
|
||||
// ============================================================================
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_btsp_synapse_creation() {
|
||||
let synapse = BTSPSynapse::new(0.5, 2000.0).expect("Should create synapse");
|
||||
assert!((synapse.weight() - 0.5).abs() < 0.001);
|
||||
assert!((synapse.eligibility_trace()).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_btsp_synapse_invalid_weight() {
|
||||
let result = BTSPSynapse::new(-0.1, 2000.0);
|
||||
assert!(result.is_err());
|
||||
|
||||
let result = BTSPSynapse::new(1.1, 2000.0);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_btsp_layer_forward() {
|
||||
let layer = BTSPLayer::new(10, 2000.0);
|
||||
let input = vec![0.1; 10];
|
||||
let output = layer.forward(&input).expect("Should compute forward");
|
||||
assert!(output >= 0.0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_btsp_one_shot_learning() {
|
||||
let mut layer = BTSPLayer::new(50, 2000.0);
|
||||
let pattern = vec![0.1; 50];
|
||||
let target = 0.8;
|
||||
|
||||
layer
|
||||
.one_shot_associate(&pattern, target)
|
||||
.expect("Should learn");
|
||||
|
||||
let output = layer.forward(&pattern).expect("Should compute forward");
|
||||
// One-shot learning should get close to target
|
||||
assert!(
|
||||
(output - target).abs() < 0.5,
|
||||
"Output: {}, Target: {}",
|
||||
output,
|
||||
target
|
||||
);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_btsp_associative_memory() {
|
||||
let mut memory = BTSPAssociativeMemory::new(10, 5);
|
||||
|
||||
let key = vec![0.5; 10];
|
||||
let value = vec![0.1, 0.2, 0.3, 0.4, 0.5];
|
||||
|
||||
memory.store_one_shot(&key, &value).expect("Should store");
|
||||
|
||||
let retrieved = memory.retrieve(&key).expect("Should retrieve");
|
||||
assert_eq!(retrieved.length(), 5);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HDC Tests
|
||||
// ============================================================================
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_random_vector() {
|
||||
let v = Hypervector::random();
|
||||
let count = v.popcount();
|
||||
|
||||
// Random vector should have ~50% bits set
|
||||
assert!(count > 4500 && count < 5500, "Popcount: {}", count);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_from_seed_deterministic() {
|
||||
let v1 = Hypervector::from_seed(42);
|
||||
let v2 = Hypervector::from_seed(42);
|
||||
|
||||
let sim = v1.similarity(&v2);
|
||||
assert!((sim - 1.0).abs() < 0.001, "Similarity should be 1.0");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_bind_commutative() {
|
||||
let a = Hypervector::random();
|
||||
let b = Hypervector::random();
|
||||
|
||||
let ab = a.bind(&b);
|
||||
let ba = b.bind(&a);
|
||||
|
||||
let sim = ab.similarity(&ba);
|
||||
assert!((sim - 1.0).abs() < 0.001, "Binding should be commutative");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_bind_self_inverse() {
|
||||
let a = Hypervector::random();
|
||||
let b = Hypervector::random();
|
||||
|
||||
let bound = a.bind(&b);
|
||||
let unbound = bound.bind(&b);
|
||||
|
||||
let sim = a.similarity(&unbound);
|
||||
assert!((sim - 1.0).abs() < 0.001, "Bind should be self-inverse");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_similarity_bounds() {
|
||||
let a = Hypervector::random();
|
||||
let b = Hypervector::random();
|
||||
|
||||
let sim = a.similarity(&b);
|
||||
assert!(
|
||||
sim >= -1.0 && sim <= 1.0,
|
||||
"Similarity out of bounds: {}",
|
||||
sim
|
||||
);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_memory_store_retrieve() {
|
||||
let mut memory = HdcMemory::new();
|
||||
|
||||
let apple = Hypervector::random();
|
||||
memory.store("apple", apple.clone());
|
||||
|
||||
assert!(memory.has("apple"));
|
||||
assert!(!memory.has("orange"));
|
||||
|
||||
let retrieved = memory.get("apple");
|
||||
assert!(retrieved.is_some());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_hdc_bundle_3() {
|
||||
let a = Hypervector::random();
|
||||
let b = Hypervector::random();
|
||||
let c = Hypervector::random();
|
||||
|
||||
let bundled = Hypervector::bundle_3(&a, &b, &c);
|
||||
|
||||
// Bundled should be similar to all inputs
|
||||
assert!(bundled.similarity(&a) > 0.3, "Should be similar to a");
|
||||
assert!(bundled.similarity(&b) > 0.3, "Should be similar to b");
|
||||
assert!(bundled.similarity(&c) > 0.3, "Should be similar to c");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WTA Tests
|
||||
// ============================================================================
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wta_basic_competition() {
|
||||
let mut wta = WTALayer::new(5, 0.5, 0.8).expect("Should create WTA");
|
||||
|
||||
let inputs = vec![0.1, 0.3, 0.9, 0.2, 0.4];
|
||||
let winner = wta.compete(&inputs).expect("Should compete");
|
||||
|
||||
assert_eq!(winner, 2, "Highest activation should win");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wta_threshold() {
|
||||
let mut wta = WTALayer::new(5, 0.95, 0.8).expect("Should create WTA");
|
||||
|
||||
let inputs = vec![0.1, 0.3, 0.9, 0.2, 0.4];
|
||||
let winner = wta.compete(&inputs).expect("Should compete");
|
||||
|
||||
assert_eq!(winner, -1, "No neuron should exceed threshold");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_wta_soft_competition() {
|
||||
let mut wta = WTALayer::new(5, 0.5, 0.8).expect("Should create WTA");
|
||||
|
||||
let inputs = vec![0.1, 0.3, 0.9, 0.2, 0.4];
|
||||
let activations = wta.compete_soft(&inputs).expect("Should compete soft");
|
||||
|
||||
// Sum should be ~1.0
|
||||
let mut sum = 0.0;
|
||||
for i in 0..activations.length() {
|
||||
sum += activations.get_index(i);
|
||||
}
|
||||
assert!((sum - 1.0).abs() < 0.01, "Activations should sum to 1.0");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_kwta_basic() {
|
||||
let kwta = KWTALayer::new(10, 3).expect("Should create K-WTA");
|
||||
|
||||
let inputs: Vec<f32> = (0..10).map(|i| i as f32).collect();
|
||||
let winners = kwta.select(&inputs).expect("Should select");
|
||||
|
||||
assert_eq!(winners.length(), 3);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_kwta_sparse_activations() {
|
||||
let kwta = KWTALayer::new(10, 3).expect("Should create K-WTA");
|
||||
|
||||
let inputs: Vec<f32> = (0..10).map(|i| i as f32).collect();
|
||||
let sparse = kwta
|
||||
.sparse_activations(&inputs)
|
||||
.expect("Should create sparse");
|
||||
|
||||
assert_eq!(sparse.length(), 10);
|
||||
|
||||
// Count non-zero elements
|
||||
let mut non_zero = 0;
|
||||
for i in 0..sparse.length() {
|
||||
if sparse.get_index(i) != 0.0 {
|
||||
non_zero += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(non_zero, 3, "Should have exactly k non-zero elements");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Global Workspace Tests
|
||||
// ============================================================================
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_workspace_creation() {
|
||||
let workspace = GlobalWorkspace::new(7);
|
||||
|
||||
assert_eq!(workspace.capacity(), 7);
|
||||
assert_eq!(workspace.len(), 0);
|
||||
assert!(workspace.is_empty());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_workspace_broadcast() {
|
||||
let mut workspace = GlobalWorkspace::new(3);
|
||||
|
||||
let content = vec![1.0, 2.0, 3.0];
|
||||
let item = WorkspaceItem::new(&content, 0.8, 1, 0);
|
||||
|
||||
let accepted = workspace.broadcast(item);
|
||||
assert!(accepted, "Should accept item");
|
||||
assert_eq!(workspace.len(), 1);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_workspace_capacity_limit() {
|
||||
let mut workspace = GlobalWorkspace::new(2);
|
||||
|
||||
// Fill workspace
|
||||
for i in 0..2 {
|
||||
let item = WorkspaceItem::new(&[1.0], 0.9, i as u16, 0);
|
||||
assert!(workspace.broadcast(item), "Should accept item {}", i);
|
||||
}
|
||||
|
||||
assert!(workspace.is_full());
|
||||
|
||||
// Try to add weak item - should fail
|
||||
let weak_item = WorkspaceItem::new(&[1.0], 0.5, 99, 0);
|
||||
let accepted = workspace.broadcast(weak_item);
|
||||
assert!(!accepted, "Should reject weak item");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_workspace_competition() {
|
||||
let mut workspace = GlobalWorkspace::with_threshold(3, 0.2);
|
||||
workspace.set_decay_rate(0.5);
|
||||
|
||||
let item = WorkspaceItem::new(&[1.0], 0.3, 0, 0);
|
||||
workspace.broadcast(item);
|
||||
|
||||
assert_eq!(workspace.len(), 1);
|
||||
|
||||
// After competition, salience = 0.3 * 0.5 = 0.15 < 0.2 threshold
|
||||
workspace.compete();
|
||||
|
||||
assert_eq!(workspace.len(), 0, "Item should be pruned");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_workspace_item_decay() {
|
||||
let mut item = WorkspaceItem::with_decay(&[1.0], 0.8, 1, 0, 0.9, 1000);
|
||||
|
||||
item.apply_decay(1.0);
|
||||
assert!(
|
||||
(item.salience() - 0.72).abs() < 0.01,
|
||||
"Salience should decay"
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_version_info() {
|
||||
let v = version();
|
||||
assert!(!v.is_empty());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_available_mechanisms() {
|
||||
let mechanisms = available_mechanisms();
|
||||
assert!(!mechanisms.is_null());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_performance_targets() {
|
||||
let targets = performance_targets();
|
||||
assert!(!targets.is_null());
|
||||
}
|
||||
Reference in New Issue
Block a user