1263 lines
40 KiB
Rust
1263 lines
40 KiB
Rust
//! 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 {} }
|