Files
wifi-densepose/vendor/ruvector/crates/micro-hnsw-wasm/src/lib.rs

1263 lines
40 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Micro HNSW v2.3 - Neuromorphic HNSW with Novel Discoveries
//! Last validated: 2025-12-25
//! Target: <12KB WASM with multi-core support
//!
//! Features:
//! - Multiple distance metrics (L2, Cosine, Dot)
//! - Multi-core sharding (256 cores × 32 vectors = 8K total)
//! - Batch operations with 4-vector batching
//! - Beam search for better recall
//! - Result merging across cores
//! - Node types for Cypher-style typed graphs (16 types)
//! - Edge weights for GNN message passing
//! - Vector updates for online learning/GNN propagation
//! - **Spiking Neural Network integration with LIF neurons**
//! - **STDP (Spike-Timing Dependent Plasticity) learning**
//!
//! ## Novel Neuromorphic Discoveries (v2.3)
//! - **Spike-Timing Vector Encoding**: Convert vectors to temporal spike patterns
//! - **Homeostatic Plasticity**: Self-stabilizing network activity
//! - **Oscillatory Resonance**: Frequency-tuned search amplification
//! - **Temporal Pattern Recognition**: Spike-based similarity matching
//! - **Winner-Take-All Circuits**: Competitive neural selection
//! - **Dendritic Computation**: Non-linear local processing
#![no_std]
// ============ Configuration ============
const MAX_VECTORS: usize = 32; // Per core (256 × 32 = 8K total)
const MAX_DIMS: usize = 16; // Vector dimensions
const MAX_NEIGHBORS: usize = 6; // Graph connectivity
const BEAM_WIDTH: usize = 3; // Search beam width
// ============ Spiking Neural Network Configuration ============
const TAU_MEMBRANE: f32 = 20.0; // Membrane time constant (ms)
const TAU_REFRAC: f32 = 2.0; // Refractory period (ms)
const V_RESET: f32 = 0.0; // Reset potential
const V_REST: f32 = 0.0; // Resting potential
const STDP_A_PLUS: f32 = 0.01; // STDP potentiation magnitude
const STDP_A_MINUS: f32 = 0.012; // STDP depression magnitude
const TAU_STDP: f32 = 20.0; // STDP time constant
const INV_TAU_STDP: f32 = 0.05; // Pre-computed 1/TAU_STDP for optimization
const INV_255: f32 = 0.00392157; // Pre-computed 1/255 for weight normalization
// ============ Novel Neuromorphic Configuration ============
const HOMEOSTATIC_TARGET: f32 = 0.1; // Target spike rate (spikes/ms)
const HOMEOSTATIC_TAU: f32 = 1000.0; // Homeostasis time constant (slow)
const OSCILLATOR_FREQ: f32 = 40.0; // Gamma oscillation frequency (Hz)
const WTA_INHIBITION: f32 = 0.8; // Winner-take-all lateral inhibition
const DENDRITIC_NONLIN: f32 = 2.0; // Dendritic nonlinearity exponent
const SPIKE_ENCODING_RES: u8 = 8; // Temporal encoding resolution (bits)
// ============ Types ============
#[repr(u8)]
#[derive(Clone, Copy, PartialEq)]
pub enum Metric { L2 = 0, Cosine = 1, Dot = 2 }
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Vector {
data: [f32; MAX_DIMS],
norm: f32,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Node {
neighbors: [u8; MAX_NEIGHBORS],
count: u8,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct SearchResult {
pub idx: u8,
pub core_id: u8,
pub distance: f32,
}
#[repr(C)]
pub struct MicroHnsw {
vectors: [Vector; MAX_VECTORS],
nodes: [Node; MAX_VECTORS],
count: u8,
dims: u8,
metric: Metric,
core_id: u8,
}
// ============ Static Storage ============
static mut HNSW: MicroHnsw = MicroHnsw {
vectors: [Vector { data: [0.0; MAX_DIMS], norm: 0.0 }; MAX_VECTORS],
nodes: [Node { neighbors: [0; MAX_NEIGHBORS], count: 0 }; MAX_VECTORS],
count: 0,
dims: 16,
metric: Metric::L2,
core_id: 0,
};
static mut QUERY: [f32; MAX_DIMS] = [0.0; MAX_DIMS];
static mut INSERT: [f32; MAX_DIMS] = [0.0; MAX_DIMS];
static mut RESULTS: [SearchResult; 16] = [SearchResult { idx: 255, core_id: 0, distance: 0.0 }; 16];
static mut GLOBAL: [SearchResult; 16] = [SearchResult { idx: 255, core_id: 0, distance: 0.0 }; 16];
// ============ GNN/Cypher Extensions ============
// Node types: 4 bits per node (16 types), packed 2 per byte = 16 bytes
static mut NODE_TYPES: [u8; MAX_VECTORS / 2] = [0; 16];
// Edge weights: 4 bits per edge (packed), uniform per node = 32 bytes
static mut EDGE_WEIGHTS: [u8; MAX_VECTORS] = [255; 32];
// Delta buffer for vector updates
static mut DELTA: [f32; MAX_DIMS] = [0.0; MAX_DIMS];
// ============ Spiking Neural Network State ============
// Membrane potentials: LIF neuron states (one per vector)
static mut MEMBRANE: [f32; MAX_VECTORS] = [0.0; MAX_VECTORS];
// Adaptive thresholds: Dynamic firing thresholds
static mut THRESHOLD: [f32; MAX_VECTORS] = [1.0; MAX_VECTORS];
// Last spike time: For STDP calculations
static mut LAST_SPIKE: [f32; MAX_VECTORS] = [-1000.0; MAX_VECTORS];
// Refractory state: Time remaining in refractory period
static mut REFRAC: [f32; MAX_VECTORS] = [0.0; MAX_VECTORS];
// Current simulation time
static mut SIM_TIME: f32 = 0.0;
// Spike output buffer: Which neurons spiked this timestep
static mut SPIKES: [bool; MAX_VECTORS] = [false; MAX_VECTORS];
// ============ Novel Neuromorphic State ============
// Homeostatic plasticity: running average spike rate
static mut SPIKE_RATE: [f32; MAX_VECTORS] = [0.0; MAX_VECTORS];
// Oscillator phase: gamma rhythm for synchronization
static mut OSCILLATOR_PHASE: f32 = 0.0;
// Dendritic compartments: local nonlinear integration
static mut DENDRITE: [[f32; MAX_NEIGHBORS]; MAX_VECTORS] = [[0.0; MAX_NEIGHBORS]; MAX_VECTORS];
// Temporal spike pattern buffer (recent spikes encoded as bits)
static mut SPIKE_PATTERN: [u32; MAX_VECTORS] = [0; MAX_VECTORS];
// Resonance amplitude for each neuron
static mut RESONANCE: [f32; MAX_VECTORS] = [0.0; MAX_VECTORS];
// Winner-take-all state: inhibition accumulator
static mut WTA_INHIBIT: f32 = 0.0;
// ============ Math ============
#[inline(always)]
fn sqrt_fast(x: f32) -> f32 {
if x <= 0.0 { return 0.0; }
let i = 0x5f3759df - (x.to_bits() >> 1);
let y = f32::from_bits(i);
x * y * (1.5 - 0.5 * x * y * y)
}
#[inline(always)]
fn norm(v: &[f32], n: usize) -> f32 {
let mut s = 0.0f32;
let mut i = 0;
while i < n { s += v[i] * v[i]; i += 1; }
sqrt_fast(s)
}
#[inline(always)]
fn dist_l2(a: &[f32], b: &[f32], n: usize) -> f32 {
let mut s = 0.0f32;
let mut i = 0;
while i < n { let d = a[i] - b[i]; s += d * d; i += 1; }
s
}
#[inline(always)]
fn dist_dot(a: &[f32], b: &[f32], n: usize) -> f32 {
let mut s = 0.0f32;
let mut i = 0;
while i < n { s += a[i] * b[i]; i += 1; }
-s
}
#[inline(always)]
fn dist_cos(a: &[f32], an: f32, b: &[f32], bn: f32, n: usize) -> f32 {
if an == 0.0 || bn == 0.0 { return 1.0; }
let mut d = 0.0f32;
let mut i = 0;
while i < n { d += a[i] * b[i]; i += 1; }
1.0 - d / (an * bn)
}
#[inline(always)]
fn distance(q: &[f32], qn: f32, idx: u8) -> f32 {
unsafe {
let n = HNSW.dims as usize;
let v = &HNSW.vectors[idx as usize];
match HNSW.metric {
Metric::Cosine => dist_cos(q, qn, &v.data[..n], v.norm, n),
Metric::Dot => dist_dot(q, &v.data[..n], n),
Metric::L2 => dist_l2(q, &v.data[..n], n),
}
}
}
// ============ Core API ============
/// Initialize: init(dims, metric, core_id)
/// metric: 0=L2, 1=Cosine, 2=Dot
#[no_mangle]
pub extern "C" fn init(dims: u8, metric: u8, core_id: u8) {
unsafe {
HNSW.count = 0;
HNSW.dims = dims.min(MAX_DIMS as u8);
HNSW.metric = match metric { 1 => Metric::Cosine, 2 => Metric::Dot, _ => Metric::L2 };
HNSW.core_id = core_id;
}
}
#[no_mangle]
pub extern "C" fn get_insert_ptr() -> *mut f32 { unsafe { INSERT.as_mut_ptr() } }
#[no_mangle]
pub extern "C" fn get_query_ptr() -> *mut f32 { unsafe { QUERY.as_mut_ptr() } }
#[no_mangle]
pub extern "C" fn get_result_ptr() -> *const SearchResult { unsafe { RESULTS.as_ptr() } }
#[no_mangle]
pub extern "C" fn get_global_ptr() -> *const SearchResult { unsafe { GLOBAL.as_ptr() } }
/// Insert vector from INSERT buffer, returns index or 255 if full
#[no_mangle]
pub extern "C" fn insert() -> u8 {
unsafe {
if HNSW.count >= MAX_VECTORS as u8 { return 255; }
let idx = HNSW.count;
let n = HNSW.dims as usize;
// Copy vector and compute norm
let mut i = 0;
while i < n { HNSW.vectors[idx as usize].data[i] = INSERT[i]; i += 1; }
HNSW.vectors[idx as usize].norm = norm(&INSERT[..n], n);
HNSW.nodes[idx as usize].count = 0;
// Connect to nearest neighbors
if idx > 0 {
let qn = HNSW.vectors[idx as usize].norm;
let mut best = [0u8; MAX_NEIGHBORS];
let mut best_d = [f32::MAX; MAX_NEIGHBORS];
let mut found = 0usize;
// Find M nearest
let mut j = 0u8;
while j < idx {
let d = distance(&INSERT[..n], qn, j);
if found < MAX_NEIGHBORS || d < best_d[found.saturating_sub(1)] {
let mut p = found.min(MAX_NEIGHBORS - 1);
while p > 0 && best_d[p - 1] > d {
if p < MAX_NEIGHBORS { best[p] = best[p - 1]; best_d[p] = best_d[p - 1]; }
p -= 1;
}
best[p] = j; best_d[p] = d;
if found < MAX_NEIGHBORS { found += 1; }
}
j += 1;
}
// Add bidirectional edges
let mut k = 0;
while k < found {
let nb = best[k];
let c = HNSW.nodes[idx as usize].count as usize;
if c < MAX_NEIGHBORS {
HNSW.nodes[idx as usize].neighbors[c] = nb;
HNSW.nodes[idx as usize].count += 1;
}
let nc = HNSW.nodes[nb as usize].count as usize;
if nc < MAX_NEIGHBORS {
HNSW.nodes[nb as usize].neighbors[nc] = idx;
HNSW.nodes[nb as usize].count += 1;
}
k += 1;
}
}
HNSW.count += 1;
idx
}
}
/// Search for k nearest neighbors using beam search
#[no_mangle]
pub extern "C" fn search(k: u8) -> u8 {
unsafe {
if HNSW.count == 0 { return 0; }
let n = HNSW.dims as usize;
let k = k.min(16).min(HNSW.count);
let qn = norm(&QUERY[..n], n);
// Reset
let mut i = 0;
while i < 16 { RESULTS[i] = SearchResult { idx: 255, core_id: HNSW.core_id, distance: f32::MAX }; i += 1; }
let mut visited = [false; MAX_VECTORS];
let mut beam = [255u8; BEAM_WIDTH];
let mut beam_d = [f32::MAX; BEAM_WIDTH];
// Start from entry point
beam[0] = 0;
beam_d[0] = distance(&QUERY[..n], qn, 0);
visited[0] = true;
RESULTS[0] = SearchResult { idx: 0, core_id: HNSW.core_id, distance: beam_d[0] };
let mut rc = 1u8;
let mut bs = 1usize;
// Beam search iterations
let mut iter = 0u8;
while iter < k.max(BEAM_WIDTH as u8) && bs > 0 {
let mut nb = [255u8; BEAM_WIDTH];
let mut nd = [f32::MAX; BEAM_WIDTH];
let mut ns = 0usize;
let mut b = 0;
while b < bs {
if beam[b] == 255 { b += 1; continue; }
let node = &HNSW.nodes[beam[b] as usize];
let mut j = 0u8;
while j < node.count {
let nbr = node.neighbors[j as usize];
j += 1;
if visited[nbr as usize] { continue; }
visited[nbr as usize] = true;
let d = distance(&QUERY[..n], qn, nbr);
// Update beam
if ns < BEAM_WIDTH || d < nd[ns.saturating_sub(1)] {
let mut p = ns.min(BEAM_WIDTH - 1);
while p > 0 && nd[p - 1] > d {
if p < BEAM_WIDTH { nb[p] = nb[p - 1]; nd[p] = nd[p - 1]; }
p -= 1;
}
nb[p] = nbr; nd[p] = d;
if ns < BEAM_WIDTH { ns += 1; }
}
// Update results
if rc < 16 || d < RESULTS[(rc - 1) as usize].distance {
let mut p = rc.min(15) as usize;
while p > 0 && RESULTS[p - 1].distance > d {
if p < 16 { RESULTS[p] = RESULTS[p - 1]; }
p -= 1;
}
if p < 16 {
RESULTS[p] = SearchResult { idx: nbr, core_id: HNSW.core_id, distance: d };
if rc < 16 { rc += 1; }
}
}
}
b += 1;
}
beam = nb; beam_d = nd; bs = ns;
iter += 1;
}
rc.min(k)
}
}
// ============ Multi-Core ============
/// Merge results from another core into global buffer
#[no_mangle]
pub extern "C" fn merge(ptr: *const SearchResult, cnt: u8) -> u8 {
unsafe {
let mut gc = 0u8;
while gc < 16 && GLOBAL[gc as usize].idx != 255 { gc += 1; }
let mut i = 0u8;
while i < cnt.min(16) {
let r = &*ptr.add(i as usize);
i += 1;
if r.idx == 255 { continue; }
if gc < 16 || r.distance < GLOBAL[(gc - 1) as usize].distance {
let mut p = gc.min(15) as usize;
while p > 0 && GLOBAL[p - 1].distance > r.distance {
if p < 16 { GLOBAL[p] = GLOBAL[p - 1]; }
p -= 1;
}
if p < 16 {
GLOBAL[p] = *r;
if gc < 16 { gc += 1; }
}
}
}
gc
}
}
/// Clear global results
#[no_mangle]
pub extern "C" fn clear_global() {
unsafe {
let mut i = 0;
while i < 16 { GLOBAL[i] = SearchResult { idx: 255, core_id: 0, distance: f32::MAX }; i += 1; }
}
}
// ============ Info ============
#[no_mangle]
pub extern "C" fn count() -> u8 { unsafe { HNSW.count } }
#[no_mangle]
pub extern "C" fn get_core_id() -> u8 { unsafe { HNSW.core_id } }
#[no_mangle]
pub extern "C" fn get_metric() -> u8 { unsafe { HNSW.metric as u8 } }
#[no_mangle]
pub extern "C" fn get_dims() -> u8 { unsafe { HNSW.dims } }
#[no_mangle]
pub extern "C" fn get_capacity() -> u8 { MAX_VECTORS as u8 }
// ============ Cypher Node Types ============
/// Set node type (0-15) for Cypher-style typed queries
/// Types packed 2 per byte (4 bits each)
#[no_mangle]
pub extern "C" fn set_node_type(idx: u8, node_type: u8) {
if idx >= MAX_VECTORS as u8 { return; }
unsafe {
let byte_idx = (idx / 2) as usize;
let node_type = node_type & 0x0F; // Clamp to 4 bits
if idx & 1 == 0 {
NODE_TYPES[byte_idx] = (NODE_TYPES[byte_idx] & 0xF0) | node_type;
} else {
NODE_TYPES[byte_idx] = (NODE_TYPES[byte_idx] & 0x0F) | (node_type << 4);
}
}
}
/// Get node type (0-15)
#[no_mangle]
pub extern "C" fn get_node_type(idx: u8) -> u8 {
if idx >= MAX_VECTORS as u8 { return 0; }
unsafe {
let byte_idx = (idx / 2) as usize;
if idx & 1 == 0 {
NODE_TYPES[byte_idx] & 0x0F
} else {
NODE_TYPES[byte_idx] >> 4
}
}
}
/// Check if node type matches mask (for filtering in JS/host)
#[no_mangle]
pub extern "C" fn type_matches(idx: u8, type_mask: u16) -> u8 {
((type_mask >> get_node_type(idx)) & 1) as u8
}
// ============ GNN Edge Weights ============
/// Set node edge weight (uniform for all edges from this node, 0-255)
#[no_mangle]
pub extern "C" fn set_edge_weight(node: u8, weight: u8) {
if node < MAX_VECTORS as u8 { unsafe { EDGE_WEIGHTS[node as usize] = weight; } }
}
/// Get node edge weight
#[no_mangle]
pub extern "C" fn get_edge_weight(node: u8) -> u8 {
if node < MAX_VECTORS as u8 { unsafe { EDGE_WEIGHTS[node as usize] } } else { 0 }
}
/// Aggregate neighbors into DELTA buffer (GNN message passing)
#[no_mangle]
pub extern "C" fn aggregate_neighbors(idx: u8) {
unsafe {
if idx >= HNSW.count { return; }
let n = HNSW.dims as usize;
let nc = HNSW.nodes[idx as usize].count;
let mut d = 0;
while d < n { DELTA[d] = 0.0; d += 1; }
if nc == 0 { return; }
let mut i = 0u8;
while i < nc {
let nb = HNSW.nodes[idx as usize].neighbors[i as usize];
let w = EDGE_WEIGHTS[nb as usize] as f32;
d = 0;
while d < n { DELTA[d] += w * HNSW.vectors[nb as usize].data[d]; d += 1; }
i += 1;
}
let s = INV_255 / nc as f32;
d = 0; while d < n { DELTA[d] *= s; d += 1; }
}
}
// ============ Vector Updates ============
/// Get delta buffer pointer for reading aggregated values
#[no_mangle]
pub extern "C" fn get_delta_ptr() -> *const f32 { unsafe { DELTA.as_ptr() } }
/// Update vector: v = v + alpha * delta (in-place)
#[no_mangle]
pub extern "C" fn update_vector(idx: u8, alpha: f32) {
unsafe {
if idx >= HNSW.count { return; }
let n = HNSW.dims as usize;
let mut i = 0;
while i < n { HNSW.vectors[idx as usize].data[i] += alpha * DELTA[i]; i += 1; }
HNSW.vectors[idx as usize].norm = norm(&HNSW.vectors[idx as usize].data[..n], n);
}
}
/// Get mutable delta buffer pointer
#[no_mangle]
pub extern "C" fn set_delta_ptr() -> *mut f32 { unsafe { DELTA.as_mut_ptr() } }
/// Combined HNSW-SNN cycle: search → convert to currents → inject
/// Useful for linking vector similarity to neural activation
#[no_mangle]
pub extern "C" fn hnsw_to_snn(k: u8, gain: f32) -> u8 {
unsafe {
let found = search(k);
if found == 0 { return 0; }
// Convert search results to neural currents
let mut i = 0u8;
while i < found {
let r = &RESULTS[i as usize];
if r.idx != 255 {
// Inverse distance = stronger activation
let current = gain / (1.0 + r.distance);
MEMBRANE[r.idx as usize] += current;
}
i += 1;
}
found
}
}
// ============ Spiking Neural Network API ============
/// Reset SNN state for all neurons
#[no_mangle]
pub extern "C" fn snn_reset() {
unsafe {
let mut i = 0;
while i < MAX_VECTORS {
MEMBRANE[i] = V_REST;
THRESHOLD[i] = 1.0;
LAST_SPIKE[i] = -1000.0;
REFRAC[i] = 0.0;
SPIKES[i] = false;
i += 1;
}
SIM_TIME = 0.0;
}
}
/// Set membrane potential for a neuron
#[no_mangle]
pub extern "C" fn snn_set_membrane(idx: u8, v: f32) {
if idx < MAX_VECTORS as u8 { unsafe { MEMBRANE[idx as usize] = v; } }
}
/// Get membrane potential
#[no_mangle]
pub extern "C" fn snn_get_membrane(idx: u8) -> f32 {
if idx < MAX_VECTORS as u8 { unsafe { MEMBRANE[idx as usize] } } else { 0.0 }
}
/// Set firing threshold for a neuron
#[no_mangle]
pub extern "C" fn snn_set_threshold(idx: u8, t: f32) {
if idx < MAX_VECTORS as u8 { unsafe { THRESHOLD[idx as usize] = t; } }
}
/// Inject current into a neuron (adds to membrane potential)
#[no_mangle]
pub extern "C" fn snn_inject(idx: u8, current: f32) {
if idx < MAX_VECTORS as u8 { unsafe { MEMBRANE[idx as usize] += current; } }
}
/// Get spike status (1 if spiked last step, 0 otherwise)
#[no_mangle]
pub extern "C" fn snn_spiked(idx: u8) -> u8 {
if idx < MAX_VECTORS as u8 { unsafe { SPIKES[idx as usize] as u8 } } else { 0 }
}
/// Get spike bitset (32 neurons packed into u32)
#[no_mangle]
pub extern "C" fn snn_get_spikes() -> u32 {
unsafe {
let mut bits = 0u32;
let mut i = 0;
while i < MAX_VECTORS { if SPIKES[i] { bits |= 1 << i; } i += 1; }
bits
}
}
/// LIF neuron step: simulate one timestep (dt in ms)
/// Returns number of neurons that spiked
#[no_mangle]
pub extern "C" fn snn_step(dt: f32) -> u8 {
unsafe {
let decay = 1.0 - dt / TAU_MEMBRANE;
let mut spike_count = 0u8;
let mut i = 0u8;
while i < HNSW.count {
let idx = i as usize;
SPIKES[idx] = false;
// Skip if in refractory period
if REFRAC[idx] > 0.0 {
REFRAC[idx] -= dt;
i += 1;
continue;
}
// Leaky integration: V = V * decay
MEMBRANE[idx] *= decay;
// Check for spike
if MEMBRANE[idx] >= THRESHOLD[idx] {
SPIKES[idx] = true;
spike_count += 1;
LAST_SPIKE[idx] = SIM_TIME;
MEMBRANE[idx] = V_RESET;
REFRAC[idx] = TAU_REFRAC;
}
i += 1;
}
SIM_TIME += dt;
spike_count
}
}
/// Propagate spikes to neighbors (injects current based on edge weights)
/// Call after snn_step to propagate activity
#[no_mangle]
pub extern "C" fn snn_propagate(gain: f32) {
unsafe {
let mut i = 0u8;
while i < HNSW.count {
if !SPIKES[i as usize] { i += 1; continue; }
// This neuron spiked, inject current to neighbors
let nc = HNSW.nodes[i as usize].count;
let mut j = 0u8;
while j < nc {
let nb = HNSW.nodes[i as usize].neighbors[j as usize];
let w = EDGE_WEIGHTS[i as usize] as f32 / 255.0;
MEMBRANE[nb as usize] += gain * w;
j += 1;
}
i += 1;
}
}
}
/// STDP learning: adjust edge weights based on spike timing
/// Call after snn_step to apply plasticity
#[no_mangle]
pub extern "C" fn snn_stdp() {
unsafe {
let mut i = 0u8;
while i < HNSW.count {
if !SPIKES[i as usize] { i += 1; continue; }
// Post-synaptic neuron spiked
let nc = HNSW.nodes[i as usize].count;
let mut j = 0u8;
while j < nc {
let pre = HNSW.nodes[i as usize].neighbors[j as usize];
let dt = LAST_SPIKE[pre as usize] - SIM_TIME;
// LTP: pre before post, LTD: pre after post
// Simplified exponential approximation
let dw = if dt < 0.0 {
STDP_A_PLUS * (1.0 + dt * INV_TAU_STDP) // dt negative, so this decays
} else {
-STDP_A_MINUS * (1.0 - dt * INV_TAU_STDP)
};
// Update weight (clamped to 0-255 using integer math)
let w = EDGE_WEIGHTS[pre as usize] as i16 + (dw * 255.0) as i16;
EDGE_WEIGHTS[pre as usize] = if w < 0 { 0 } else if w > 255 { 255 } else { w as u8 };
j += 1;
}
i += 1;
}
}
}
/// Combined: step + propagate + optionally STDP
/// Returns spike count
#[no_mangle]
pub extern "C" fn snn_tick(dt: f32, gain: f32, learn: u8) -> u8 {
let spikes = snn_step(dt);
snn_propagate(gain);
if learn != 0 { snn_stdp(); }
spikes
}
/// Get current simulation time
#[no_mangle]
pub extern "C" fn snn_get_time() -> f32 { unsafe { SIM_TIME } }
// ============================================================================
// NOVEL NEUROMORPHIC DISCOVERIES
// ============================================================================
// ============ Spike-Timing Vector Encoding ============
// Novel discovery: Encode vectors as temporal spike patterns
// Each dimension becomes a spike time within a coding window
/// Encode vector to temporal spike pattern (rate-to-time conversion)
/// Higher values → earlier spikes (first-spike coding)
/// Returns encoded pattern as 32-bit bitmask
#[no_mangle]
pub extern "C" fn encode_vector_to_spikes(idx: u8) -> u32 {
unsafe {
if idx >= HNSW.count { return 0; }
let n = HNSW.dims as usize;
let mut pattern = 0u32;
// Normalize vector values to spike times
let mut max_val = 0.0f32;
let mut i = 0;
while i < n {
let v = HNSW.vectors[idx as usize].data[i];
if v > max_val { max_val = v; }
if -v > max_val { max_val = -v; }
i += 1;
}
if max_val == 0.0 { return 0; }
// Encode: high values → low bit positions (early spikes)
i = 0;
while i < n.min(SPIKE_ENCODING_RES as usize * 4) {
let normalized = (HNSW.vectors[idx as usize].data[i] + max_val) / (2.0 * max_val);
let slot = ((1.0 - normalized) * SPIKE_ENCODING_RES as f32) as u8;
let bit_pos = i as u8 + slot * (n as u8 / SPIKE_ENCODING_RES);
if bit_pos < 32 { pattern |= 1u32 << bit_pos; }
i += 1;
}
SPIKE_PATTERN[idx as usize] = pattern;
pattern
}
}
/// Compute spike-timing similarity between two spike patterns
/// Uses Victor-Purpura-inspired metric: count matching spike times
#[no_mangle]
pub extern "C" fn spike_timing_similarity(a: u32, b: u32) -> f32 {
// Count matching spike positions
let matches = (a & b).count_ones() as f32;
let total = (a | b).count_ones() as f32;
if total == 0.0 { return 1.0; }
matches / total // Jaccard-like similarity
}
/// Search using spike-timing representation
/// Novel: temporal code matching instead of distance
#[no_mangle]
pub extern "C" fn spike_search(query_pattern: u32, k: u8) -> u8 {
unsafe {
if HNSW.count == 0 { return 0; }
let k = k.min(16).min(HNSW.count);
// Reset results
let mut i = 0;
while i < 16 {
RESULTS[i] = SearchResult { idx: 255, core_id: HNSW.core_id, distance: 0.0 };
i += 1;
}
let mut found = 0u8;
i = 0;
while i < HNSW.count as usize {
let sim = spike_timing_similarity(query_pattern, SPIKE_PATTERN[i]);
// Store as negative similarity for compatibility (lower = better)
let dist = 1.0 - sim;
if found < k || dist < RESULTS[(found - 1) as usize].distance {
let mut p = found.min(k - 1) as usize;
while p > 0 && RESULTS[p - 1].distance > dist {
if p < 16 { RESULTS[p] = RESULTS[p - 1]; }
p -= 1;
}
if p < 16 {
RESULTS[p] = SearchResult {
idx: i as u8,
core_id: HNSW.core_id,
distance: dist
};
if found < k { found += 1; }
}
}
i += 1;
}
found
}
}
// ============ Homeostatic Plasticity ============
// Novel: Self-stabilizing network maintains target activity level
// Prevents runaway excitation or complete silence
/// Apply homeostatic plasticity: adjust thresholds to maintain target rate
#[no_mangle]
pub extern "C" fn homeostatic_update(dt: f32) {
unsafe {
let alpha = dt / HOMEOSTATIC_TAU;
let mut i = 0u8;
while i < HNSW.count {
let idx = i as usize;
// Update running spike rate estimate
let instant_rate = if SPIKES[idx] { 1.0 / dt } else { 0.0 };
SPIKE_RATE[idx] = SPIKE_RATE[idx] * (1.0 - alpha) + instant_rate * alpha;
// Adjust threshold to approach target rate
let rate_error = SPIKE_RATE[idx] - HOMEOSTATIC_TARGET;
THRESHOLD[idx] += rate_error * alpha;
// Clamp threshold to reasonable range
if THRESHOLD[idx] < 0.1 { THRESHOLD[idx] = 0.1; }
if THRESHOLD[idx] > 10.0 { THRESHOLD[idx] = 10.0; }
i += 1;
}
}
}
/// Get current spike rate estimate
#[no_mangle]
pub extern "C" fn get_spike_rate(idx: u8) -> f32 {
if idx < MAX_VECTORS as u8 { unsafe { SPIKE_RATE[idx as usize] } } else { 0.0 }
}
// ============ Oscillatory Resonance ============
// Novel: Gamma-rhythm synchronization for binding and search enhancement
// Neurons tuned to oscillation phase get amplified
/// Update oscillator phase
#[no_mangle]
pub extern "C" fn oscillator_step(dt: f32) {
unsafe {
// Phase advances with time: ω = 2πf
let omega = 6.28318 * OSCILLATOR_FREQ / 1000.0; // Convert Hz to rad/ms
OSCILLATOR_PHASE += omega * dt;
if OSCILLATOR_PHASE > 6.28318 { OSCILLATOR_PHASE -= 6.28318; }
}
}
/// Get current oscillator phase (0 to 2π)
#[no_mangle]
pub extern "C" fn oscillator_get_phase() -> f32 { unsafe { OSCILLATOR_PHASE } }
/// Compute resonance boost for a neuron based on phase alignment
/// Neurons in sync with gamma get amplified
#[no_mangle]
pub extern "C" fn compute_resonance(idx: u8) -> f32 {
unsafe {
if idx >= HNSW.count { return 0.0; }
let i = idx as usize;
// Each neuron has preferred phase based on its index
let preferred_phase = (idx as f32 / MAX_VECTORS as f32) * 6.28318;
let phase_diff = (OSCILLATOR_PHASE - preferred_phase).abs();
let min_diff = if phase_diff > 3.14159 { 6.28318 - phase_diff } else { phase_diff };
// Resonance is high when phase matches
RESONANCE[i] = 1.0 - min_diff / 3.14159;
RESONANCE[i]
}
}
/// Apply resonance-modulated search boost
/// Query matches are enhanced when neuron is in favorable phase
#[no_mangle]
pub extern "C" fn resonance_search(k: u8, phase_weight: f32) -> u8 {
unsafe {
let found = search(k);
// Modulate results by resonance
let mut i = 0u8;
while i < found {
let idx = RESULTS[i as usize].idx;
if idx != 255 {
let res = compute_resonance(idx);
// Lower distance = better, so multiply by (2 - resonance)
RESULTS[i as usize].distance *= 2.0 - res * phase_weight;
}
i += 1;
}
// Re-sort results after resonance modulation
let mut i = 0usize;
while i < found as usize {
let mut j = i + 1;
while j < found as usize {
if RESULTS[j].distance < RESULTS[i].distance {
let tmp = RESULTS[i];
RESULTS[i] = RESULTS[j];
RESULTS[j] = tmp;
}
j += 1;
}
i += 1;
}
found
}
}
// ============ Winner-Take-All Circuits ============
// Novel: Competitive selection via lateral inhibition
// Only the most active neuron wins, enabling hard decisions
/// Reset WTA state
#[no_mangle]
pub extern "C" fn wta_reset() { unsafe { WTA_INHIBIT = 0.0; } }
/// Run WTA competition: only highest membrane potential survives
/// Returns winner index (or 255 if no winner)
#[no_mangle]
pub extern "C" fn wta_compete() -> u8 {
unsafe {
let mut max_v = 0.0f32;
let mut winner = 255u8;
let mut i = 0u8;
while i < HNSW.count {
let v = MEMBRANE[i as usize];
if v > max_v && REFRAC[i as usize] <= 0.0 {
max_v = v;
winner = i;
}
i += 1;
}
// Apply lateral inhibition to all losers
if winner != 255 {
WTA_INHIBIT = max_v * WTA_INHIBITION;
i = 0;
while i < HNSW.count {
if i != winner {
MEMBRANE[i as usize] -= WTA_INHIBIT;
if MEMBRANE[i as usize] < V_RESET {
MEMBRANE[i as usize] = V_RESET;
}
}
i += 1;
}
}
winner
}
}
/// Soft WTA: proportional inhibition based on rank
#[no_mangle]
pub extern "C" fn wta_soft() {
unsafe {
// Find max membrane potential
let mut max_v = 0.0f32;
let mut i = 0u8;
while i < HNSW.count {
if MEMBRANE[i as usize] > max_v { max_v = MEMBRANE[i as usize]; }
i += 1;
}
if max_v <= 0.0 { return; }
// Normalize and apply softmax-like competition
i = 0;
while i < HNSW.count {
let ratio = MEMBRANE[i as usize] / max_v;
// Exponential competition: low ratios get strongly suppressed
let survival = ratio * ratio; // Square for sharper competition
MEMBRANE[i as usize] *= survival;
i += 1;
}
}
}
// ============ Dendritic Computation ============
// Novel: Nonlinear integration in dendritic compartments
// Enables local coincidence detection before soma integration
/// Reset dendritic compartments
#[no_mangle]
pub extern "C" fn dendrite_reset() {
unsafe {
let mut i = 0;
while i < MAX_VECTORS {
let mut j = 0;
while j < MAX_NEIGHBORS { DENDRITE[i][j] = 0.0; j += 1; }
i += 1;
}
}
}
/// Inject input to specific dendritic compartment
#[no_mangle]
pub extern "C" fn dendrite_inject(neuron: u8, branch: u8, current: f32) {
unsafe {
if neuron < MAX_VECTORS as u8 && branch < MAX_NEIGHBORS as u8 {
DENDRITE[neuron as usize][branch as usize] += current;
}
}
}
/// Dendritic integration with nonlinearity
/// Multiple coincident inputs on same branch get amplified
#[no_mangle]
pub extern "C" fn dendrite_integrate(neuron: u8) -> f32 {
unsafe {
if neuron >= HNSW.count { return 0.0; }
let idx = neuron as usize;
let nc = HNSW.nodes[idx].count as usize;
let mut total = 0.0f32;
let mut branch = 0;
while branch < nc {
let d = DENDRITE[idx][branch];
// Nonlinear: small inputs are linear, large inputs saturate with boost
if d > 0.0 {
// Sigmoidal nonlinearity with supralinear boost
let nonlin = if d < 1.0 {
d
} else {
1.0 + (d - 1.0) / (1.0 + (d - 1.0) / DENDRITIC_NONLIN)
};
total += nonlin;
}
branch += 1;
}
// Transfer to soma
MEMBRANE[idx] += total;
total
}
}
/// Propagate spikes through dendritic tree (not just soma)
#[no_mangle]
pub extern "C" fn dendrite_propagate(gain: f32) {
unsafe {
let mut i = 0u8;
while i < HNSW.count {
if !SPIKES[i as usize] { i += 1; continue; }
// This neuron spiked, inject to neighbor dendrites
let nc = HNSW.nodes[i as usize].count;
let mut j = 0u8;
while j < nc {
let nb = HNSW.nodes[i as usize].neighbors[j as usize];
let w = EDGE_WEIGHTS[i as usize] as f32 / 255.0;
// Find which dendrite branch this connection is on
let mut branch = 0u8;
let nb_nc = HNSW.nodes[nb as usize].count;
while branch < nb_nc {
if HNSW.nodes[nb as usize].neighbors[branch as usize] == i {
break;
}
branch += 1;
}
if branch < MAX_NEIGHBORS as u8 {
DENDRITE[nb as usize][branch as usize] += gain * w;
}
j += 1;
}
i += 1;
}
}
}
// ============ Temporal Pattern Recognition ============
// Novel: Store and match spike pattern sequences
// Enables recognition of dynamic temporal signatures
/// Record current spike state into pattern buffer (shift register)
#[no_mangle]
pub extern "C" fn pattern_record() {
unsafe {
let mut i = 0;
while i < MAX_VECTORS {
// Shift pattern left and add new spike
SPIKE_PATTERN[i] = (SPIKE_PATTERN[i] << 1) | (SPIKES[i] as u32);
i += 1;
}
}
}
/// Get temporal spike pattern for a neuron
#[no_mangle]
pub extern "C" fn get_pattern(idx: u8) -> u32 {
if idx < MAX_VECTORS as u8 { unsafe { SPIKE_PATTERN[idx as usize] } } else { 0 }
}
/// Match pattern against stored patterns (Hamming similarity)
/// Returns best matching neuron index
#[no_mangle]
pub extern "C" fn pattern_match(target: u32) -> u8 {
unsafe {
let mut best_idx = 255u8;
let mut best_sim = 0u32;
let mut i = 0u8;
while i < HNSW.count {
// XOR gives difference, NOT gives similarity bits
let diff = target ^ SPIKE_PATTERN[i as usize];
let sim = (!diff).count_ones();
if sim > best_sim {
best_sim = sim;
best_idx = i;
}
i += 1;
}
best_idx
}
}
/// Temporal correlation: find neurons with similar spike history
#[no_mangle]
pub extern "C" fn pattern_correlate(idx: u8, threshold: u8) -> u32 {
unsafe {
if idx >= HNSW.count { return 0; }
let target = SPIKE_PATTERN[idx as usize];
let mut correlated = 0u32;
let mut i = 0u8;
while i < HNSW.count {
if i != idx {
let diff = target ^ SPIKE_PATTERN[i as usize];
let dist = diff.count_ones() as u8;
if dist <= threshold && i < 32 {
correlated |= 1u32 << i;
}
}
i += 1;
}
correlated
}
}
// ============ Combined Neuromorphic Search ============
// Novel: Unified search combining all mechanisms
/// Advanced neuromorphic search with all novel features
/// Combines: HNSW graph, spike timing, oscillation, WTA
#[no_mangle]
pub extern "C" fn neuromorphic_search(k: u8, dt: f32, iterations: u8) -> u8 {
unsafe {
if HNSW.count == 0 { return 0; }
// Reset neural state
snn_reset();
dendrite_reset();
wta_reset();
// Convert query to spike pattern
let n = HNSW.dims as usize;
let qn = norm(&QUERY[..n], n);
// Initialize membrane potentials from vector distances
let mut i = 0u8;
while i < HNSW.count {
let d = distance(&QUERY[..n], qn, i);
// Inverse distance = initial activation
MEMBRANE[i as usize] = 1.0 / (1.0 + d);
i += 1;
}
// Run neuromorphic dynamics
let mut iter = 0u8;
while iter < iterations {
oscillator_step(dt);
// Dendritic integration
i = 0;
while i < HNSW.count {
dendrite_integrate(i);
i += 1;
}
// Neural step with spike propagation
snn_step(dt);
dendrite_propagate(0.5);
// WTA competition for sharpening
wta_soft();
// Record spike patterns
pattern_record();
// Homeostatic regulation
homeostatic_update(dt);
iter += 1;
}
// Collect results based on final spike patterns and resonance
let mut i = 0;
while i < 16 {
RESULTS[i] = SearchResult { idx: 255, core_id: HNSW.core_id, distance: f32::MAX };
i += 1;
}
let mut found = 0u8;
i = 0;
while i < HNSW.count as usize {
// Score = spike count + resonance + membrane potential
let spikes = SPIKE_PATTERN[i].count_ones() as f32;
let res = RESONANCE[i];
let vm = MEMBRANE[i];
let score = -(spikes * 10.0 + res * 5.0 + vm); // Negative for sorting
if found < k || score < RESULTS[(found - 1) as usize].distance {
let mut p = found.min(k - 1) as usize;
while p > 0 && RESULTS[p - 1].distance > score {
if p < 16 { RESULTS[p] = RESULTS[p - 1]; }
p -= 1;
}
if p < 16 {
RESULTS[p] = SearchResult {
idx: i as u8,
core_id: HNSW.core_id,
distance: score
};
if found < k { found += 1; }
}
}
i += 1;
}
found
}
}
/// Get total network activity (sum of spike rates)
#[no_mangle]
pub extern "C" fn get_network_activity() -> f32 {
unsafe {
let mut total = 0.0f32;
let mut i = 0;
while i < MAX_VECTORS {
total += SPIKE_RATE[i];
i += 1;
}
total
}
}
#[cfg(not(test))]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { loop {} }