use super::{AdaptResult, SearchResult, SubstrateBackend};
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct NeuromorphicConfig {
pub hd_dim: usize,
pub n_neurons: usize,
pub k_wta: usize,
pub tau_m: f32,
pub btsp_threshold: f32,
pub kuramoto_k: f32,
pub oscillation_hz: f32,
}
impl Default for NeuromorphicConfig {
fn default() -> Self {
Self {
hd_dim: 10_000,
n_neurons: 1_000,
k_wta: 50, tau_m: 20.0, btsp_threshold: 0.7,
kuramoto_k: 0.3,
oscillation_hz: 40.0, }
}
}
struct NeuromorphicState {
hd_memory: Vec<Vec<u8>>, hd_dim: usize,
membrane: Vec<f32>,
#[allow(dead_code)]
weights: Vec<f32>,
n_neurons: usize,
phases: Vec<f32>,
order_parameter: f32,
eligibility: Vec<f32>,
pre_trace: Vec<f32>,
post_trace: Vec<f32>,
tick: u64,
}
impl NeuromorphicState {
fn new(cfg: &NeuromorphicConfig) -> Self {
use std::f32::consts::PI;
let n = cfg.n_neurons;
let phases: Vec<f32> = (0..n).map(|i| 2.0 * PI * i as f32 / n as f32).collect();
Self {
hd_memory: Vec::new(),
hd_dim: cfg.hd_dim,
membrane: vec![0.0f32; n],
weights: vec![0.0f32; n * n],
n_neurons: n,
phases,
order_parameter: 0.0,
eligibility: vec![0.0f32; n],
pre_trace: vec![0.0f32; n],
post_trace: vec![0.0f32; n],
tick: 0,
}
}
fn hd_encode(&self, vec: &[f32]) -> Vec<u8> {
let n_bytes = (self.hd_dim + 7) / 8;
let mut hv = vec![0u8; n_bytes];
let mut seed = 0x9e3779b97f4a7c15u64;
for (i, &v) in vec.iter().enumerate() {
seed = seed
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let proj_seed = seed ^ (i as u64).wrapping_mul(0x517cc1b727220a95);
let bit_idx = (proj_seed as usize) % self.hd_dim;
let threshold = ((proj_seed >> 32) as f32 / u32::MAX as f32) * 2.0 - 1.0;
if v > threshold {
hv[bit_idx / 8] |= 1 << (bit_idx % 8);
}
}
hv
}
fn hd_similarity(&self, a: &[u8], b: &[u8]) -> f32 {
let n_bits = self.hd_dim as f32;
let hamming: u32 = a
.iter()
.zip(b.iter())
.map(|(x, y)| (x ^ y).count_ones())
.sum();
1.0 - (hamming as f32 / n_bits)
}
#[allow(dead_code)]
#[inline]
fn k_wta(&mut self, k: usize) {
let n = self.membrane.len();
if k == 0 || k >= n {
return;
}
let mut indexed: Vec<(usize, f32)> = self.membrane.iter().copied().enumerate().collect();
indexed.select_nth_unstable_by(k - 1, |a, b| {
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
});
let threshold = indexed[k - 1].1;
for m in self.membrane.iter_mut() {
if *m < threshold {
*m = 0.0;
}
}
}
#[inline]
fn kuramoto_step(&mut self, dt: f32, omega: f32, k: f32) {
let n = self.phases.len();
if n == 0 {
return;
}
let (sum_sin, sum_cos) = self.phases.iter().fold((0.0f32, 0.0f32), |(ss, sc), &p| {
(ss + p.sin(), sc + p.cos())
});
let k_over_n = k / n as f32;
let mut new_sum_sin = 0.0f32;
let mut new_sum_cos = 0.0f32;
for phi in self.phases.iter_mut() {
let coupling = k_over_n * (phi.cos() * sum_sin - phi.sin() * sum_cos);
*phi += dt * (omega + coupling);
new_sum_sin += phi.sin();
new_sum_cos += phi.cos();
}
self.order_parameter =
(new_sum_sin * new_sum_sin + new_sum_cos * new_sum_cos).sqrt() / n as f32;
self.tick += 1;
}
}
pub struct NeuromorphicBackend {
config: NeuromorphicConfig,
state: NeuromorphicState,
pattern_ids: Vec<u64>,
next_id: u64,
}
impl NeuromorphicBackend {
pub fn new() -> Self {
let cfg = NeuromorphicConfig::default();
let state = NeuromorphicState::new(&cfg);
Self {
config: cfg,
state,
pattern_ids: Vec::new(),
next_id: 0,
}
}
pub fn with_config(cfg: NeuromorphicConfig) -> Self {
let state = NeuromorphicState::new(&cfg);
Self {
config: cfg,
state,
pattern_ids: Vec::new(),
next_id: 0,
}
}
pub fn store(&mut self, pattern: &[f32]) -> u64 {
let hv = self.state.hd_encode(pattern);
self.state.hd_memory.push(hv);
let id = self.next_id;
self.pattern_ids.push(id);
self.next_id += 1;
id
}
pub fn circadian_coherence(&mut self) -> f32 {
use std::f32::consts::TAU;
let omega = TAU * self.config.oscillation_hz / 1000.0; self.state.kuramoto_step(1.0, omega, self.config.kuramoto_k);
self.state.order_parameter
}
pub fn lif_tick(&mut self, input: &[f32]) -> Vec<bool> {
let tau = self.config.tau_m;
let n = self.state.n_neurons.min(input.len());
let mut spikes = vec![false; self.state.n_neurons];
for i in 0..n {
self.state.membrane[i] += (1.0 / tau) * (-self.state.membrane[i] + input[i]);
if self.state.membrane[i] >= 1.0 {
spikes[i] = true;
self.state.membrane[i] = 0.0; self.state.post_trace[i] = (self.state.post_trace[i] + 1.0) * 0.95;
self.state.eligibility[i] += 0.1;
}
self.state.pre_trace[i] *= 0.95;
self.state.eligibility[i] *= 0.99;
}
spikes
}
}
impl Default for NeuromorphicBackend {
fn default() -> Self {
Self::new()
}
}
impl SubstrateBackend for NeuromorphicBackend {
fn name(&self) -> &'static str {
"neuromorphic-hdc-lif"
}
fn similarity_search(&self, query: &[f32], k: usize) -> Vec<SearchResult> {
let t0 = Instant::now();
let query_hv = self.state.hd_encode(query);
let mut results: Vec<SearchResult> = self
.state
.hd_memory
.iter()
.zip(self.pattern_ids.iter())
.map(|(hv, &id)| {
let score = self.state.hd_similarity(&query_hv, hv);
SearchResult {
id,
score,
embedding: vec![],
}
})
.collect();
results.sort_unstable_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
});
results.truncate(k);
let _elapsed = t0.elapsed();
results
}
fn adapt(&mut self, pattern: &[f32], reward: f32) -> AdaptResult {
let t0 = Instant::now();
if reward.abs() > self.config.btsp_threshold {
self.store(pattern);
}
for e in self.state.eligibility.iter_mut() {
*e *= reward.abs();
}
let delta_norm = pattern.iter().map(|x| x * x).sum::<f32>().sqrt() * reward.abs();
let latency_us = t0.elapsed().as_micros() as u64;
AdaptResult {
delta_norm,
mode: "btsp-eprop",
latency_us,
}
}
fn coherence(&self) -> f32 {
self.state.order_parameter
}
fn reset(&mut self) {
self.state = NeuromorphicState::new(&self.config);
self.pattern_ids.clear();
self.next_id = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hdc_store_and_retrieve() {
let mut backend = NeuromorphicBackend::new();
let pattern = vec![0.5f32; 128];
let id = backend.store(&pattern);
let results = backend.similarity_search(&pattern, 1);
assert_eq!(results.len(), 1);
assert_eq!(results[0].id, id);
assert!(results[0].score > 0.6, "Self-similarity should be high");
}
#[test]
fn test_k_wta_sparsity() {
let mut backend = NeuromorphicBackend::new();
backend.state.membrane = (0..1000).map(|i| i as f32 / 1000.0).collect();
backend.state.k_wta(50);
let active = backend.state.membrane.iter().filter(|&&v| v > 0.0).count();
assert_eq!(active, 50, "K-WTA should leave exactly K active neurons");
}
#[test]
fn test_kuramoto_synchronization() {
let mut backend = NeuromorphicBackend::new();
backend.config.kuramoto_k = 2.0;
for _ in 0..500 {
backend.circadian_coherence();
}
assert!(
backend.state.order_parameter > 0.5,
"Strong Kuramoto coupling should achieve synchronization (R > 0.5)"
);
}
#[test]
fn test_lif_spikes() {
let mut backend = NeuromorphicBackend::new();
let strong_input = vec![10.0f32; 100]; let mut spiked = false;
for _ in 0..20 {
let spikes = backend.lif_tick(&strong_input);
if spikes.iter().any(|&s| s) {
spiked = true;
}
}
assert!(spiked, "Strong input should cause LIF spikes");
}
#[test]
fn test_btsp_one_shot_learning() {
let mut backend = NeuromorphicBackend::new();
let pattern = vec![1.0f32; 64];
let result = backend.adapt(&pattern, 0.9); assert!(result.delta_norm > 0.0);
let search = backend.similarity_search(&pattern, 1);
assert!(!search.is_empty());
}
}