#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum NeuronType {
Computational = 0b000,
Sensory = 0b001,
Motor = 0b010,
MemoryReader = 0b011,
MemoryMatcher = 0b100,
Gate = 0b101,
Relay = 0b110,
Oscillator = 0b111,
}
impl NeuronType {
#[inline]
pub fn from_flags(flags: u8) -> Self {
match (flags >> 3) & 0b111 {
0b000 => Self::Computational,
0b001 => Self::Sensory,
0b010 => Self::Motor,
0b011 => Self::MemoryReader,
0b100 => Self::MemoryMatcher,
0b101 => Self::Gate,
0b110 => Self::Relay,
0b111 => Self::Oscillator,
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum NeuronProfile {
RegularSpiking = 0b00,
FastSpiking = 0b01,
IntrinsicBursting = 0b10,
Reserved = 0b11,
}
impl NeuronProfile {
#[inline]
pub fn from_flags(flags: u8) -> Self {
match (flags >> 1) & 0b11 {
0b00 => Self::RegularSpiking,
0b01 => Self::FastSpiking,
0b10 => Self::IntrinsicBursting,
_ => Self::Reserved,
}
}
#[inline]
pub fn default_leak(self) -> u8 {
match self {
Self::RegularSpiking => 4, Self::FastSpiking => 8, Self::IntrinsicBursting => 2, Self::Reserved => 4,
}
}
#[inline]
pub fn default_refractory(self) -> u8 {
match self {
Self::RegularSpiking => 2,
Self::FastSpiking => 1, Self::IntrinsicBursting => 3, Self::Reserved => 2,
}
}
}
pub mod flags {
pub const INHIBITORY_BIT: u8 = 0x01;
#[inline]
pub fn is_inhibitory(f: u8) -> bool {
f & INHIBITORY_BIT != 0
}
#[inline]
pub fn is_excitatory(f: u8) -> bool {
!is_inhibitory(f)
}
#[inline]
pub fn neuron_type(f: u8) -> super::NeuronType {
super::NeuronType::from_flags(f)
}
#[inline]
pub fn encode(inhibitory: bool, profile: super::NeuronProfile) -> u8 {
encode_full(inhibitory, profile, super::NeuronType::Computational)
}
#[inline]
pub fn encode_full(inhibitory: bool, profile: super::NeuronProfile, ntype: super::NeuronType) -> u8 {
let mut f = 0u8;
if inhibitory {
f |= INHIBITORY_BIT;
}
f |= (profile as u8) << 1;
f |= (ntype as u8) << 3;
f
}
}
pub struct NeuronArrays {
pub membrane: Vec<i16>,
pub threshold: Vec<i16>,
pub leak: Vec<u8>,
pub refract_remaining: Vec<u8>,
pub flags: Vec<u8>,
pub trace: Vec<i8>,
pub spike_out: Vec<bool>,
pub binding_slot: Vec<u8>,
pub soma_position: Vec<[f32; 3]>,
pub axon_terminal: Vec<[f32; 3]>,
pub dendrite_radius: Vec<f32>,
pub axon_health: Vec<u8>,
}
impl NeuronArrays {
pub fn new(n: u32, n_excitatory: u32, resting: i16, threshold: i16) -> Self {
let n = n as usize;
let n_exc = n_excitatory as usize;
let mut flags_vec = vec![0u8; n];
for i in 0..n {
let inhibitory = i >= n_exc;
let profile = if inhibitory {
NeuronProfile::FastSpiking
} else {
NeuronProfile::RegularSpiking
};
flags_vec[i] = flags::encode(inhibitory, profile);
}
let mut leak_vec = vec![0u8; n];
for i in 0..n {
leak_vec[i] = NeuronProfile::from_flags(flags_vec[i]).default_leak();
}
Self {
membrane: vec![resting; n],
threshold: vec![threshold; n],
leak: leak_vec,
refract_remaining: vec![0; n],
flags: flags_vec,
trace: vec![0i8; n],
spike_out: vec![false; n],
binding_slot: vec![0u8; n],
soma_position: vec![[0.0, 0.0, 0.0]; n],
axon_terminal: vec![[0.0, 0.0, 0.0]; n],
dendrite_radius: vec![1.0; n], axon_health: vec![128; n], }
}
pub fn init_spatial_from_grid(&mut self, bounds: [f32; 3], dims: (u16, u16, u16)) {
let (w, h, d) = dims;
let n = self.len();
for i in 0..n {
let x = (i % w as usize) as f32;
let y = ((i / w as usize) % h as usize) as f32;
let z = (i / (w as usize * h as usize)) as f32;
let sx = if w > 1 { bounds[0] * x / (w as f32 - 1.0) } else { bounds[0] * 0.5 };
let sy = if h > 1 { bounds[1] * y / (h as f32 - 1.0) } else { bounds[1] * 0.5 };
let sz = if d > 1 { bounds[2] * z / (d as f32 - 1.0) } else { bounds[2] * 0.5 };
self.soma_position[i] = [sx, sy, sz];
self.axon_terminal[i] = [sx, sy, sz]; }
}
pub fn init_spatial_random(&mut self, bounds: [f32; 3], seed: u64) {
let n = self.len();
let mut rng = seed;
for i in 0..n {
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let rx = ((rng >> 32) as u32) as f32 / u32::MAX as f32;
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let ry = ((rng >> 32) as u32) as f32 / u32::MAX as f32;
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1);
let rz = ((rng >> 32) as u32) as f32 / u32::MAX as f32;
self.soma_position[i] = [bounds[0] * rx, bounds[1] * ry, bounds[2] * rz];
self.axon_terminal[i] = self.soma_position[i]; }
}
#[inline]
pub fn len(&self) -> usize {
self.membrane.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.membrane.is_empty()
}
pub fn extend(&mut self, additional: u32, n_new_excitatory: u32, resting: i16, threshold: i16) {
let n = additional as usize;
let n_exc = n_new_excitatory as usize;
for i in 0..n {
let inhibitory = i >= n_exc;
let profile = if inhibitory {
NeuronProfile::FastSpiking
} else {
NeuronProfile::RegularSpiking
};
let f = flags::encode(inhibitory, profile);
self.membrane.push(resting);
self.threshold.push(threshold);
self.leak.push(NeuronProfile::from_flags(f).default_leak());
self.refract_remaining.push(0);
self.flags.push(f);
self.trace.push(0);
self.spike_out.push(false);
self.binding_slot.push(0);
self.soma_position.push([0.0, 0.0, 0.0]);
self.axon_terminal.push([0.0, 0.0, 0.0]);
self.dendrite_radius.push(1.0);
self.axon_health.push(128);
}
}
pub fn remove_descending(&mut self, indices: &[usize]) -> usize {
let mut removed = 0;
for &idx in indices {
if idx >= self.membrane.len() { continue; }
self.membrane.swap_remove(idx);
self.threshold.swap_remove(idx);
self.leak.swap_remove(idx);
self.refract_remaining.swap_remove(idx);
self.flags.swap_remove(idx);
self.trace.swap_remove(idx);
self.spike_out.swap_remove(idx);
self.binding_slot.swap_remove(idx);
self.soma_position.swap_remove(idx);
self.axon_terminal.swap_remove(idx);
self.dendrite_radius.swap_remove(idx);
self.axon_health.swap_remove(idx);
removed += 1;
}
removed
}
pub fn neurons_in_radius(&self, pos: [f32; 3], radius: f32) -> Vec<usize> {
let r2 = radius * radius;
let mut result = Vec::new();
for i in 0..self.len() {
let dx = self.soma_position[i][0] - pos[0];
let dy = self.soma_position[i][1] - pos[1];
let dz = self.soma_position[i][2] - pos[2];
let d2 = dx * dx + dy * dy + dz * dz;
if d2 <= r2 {
result.push(i);
}
}
result
}
pub fn density_at(&self, pos: [f32; 3], sigma: f32) -> f32 {
let sigma2 = sigma * sigma;
let mut density = 0.0f32;
for i in 0..self.len() {
let dx = self.soma_position[i][0] - pos[0];
let dy = self.soma_position[i][1] - pos[1];
let dz = self.soma_position[i][2] - pos[2];
let d2 = dx * dx + dy * dy + dz * dz;
density += (-d2 / (2.0 * sigma2)).exp();
}
density
}
pub fn cluster_center(&self, pos: [f32; 3], radius: f32) -> Option<[f32; 3]> {
let nearby = self.neurons_in_radius(pos, radius);
if nearby.is_empty() {
return None;
}
let mut cx = 0.0f32;
let mut cy = 0.0f32;
let mut cz = 0.0f32;
for &i in &nearby {
cx += self.soma_position[i][0];
cy += self.soma_position[i][1];
cz += self.soma_position[i][2];
}
let n = nearby.len() as f32;
Some([cx / n, cy / n, cz / n])
}
pub fn grow_axon_step(&mut self, neuron_idx: usize, direction: [f32; 3], density_sigma: f32, resistance_factor: f32, seed: u64) -> bool {
if neuron_idx >= self.len() {
return false;
}
let terminal = self.axon_terminal[neuron_idx];
let new_pos = [
terminal[0] + direction[0],
terminal[1] + direction[1],
terminal[2] + direction[2],
];
let density = self.density_at(new_pos, density_sigma);
let resistance = (density * resistance_factor).min(1.0);
let rng = seed.wrapping_mul(6364136223846793005).wrapping_add(neuron_idx as u64);
let rand_val = ((rng >> 32) as u32) as f32 / u32::MAX as f32;
if rand_val > resistance {
self.axon_terminal[neuron_idx] = new_pos;
true
} else {
false
}
}
pub fn retract_axon_step(&mut self, neuron_idx: usize, step_size: f32) {
if neuron_idx >= self.len() {
return;
}
let soma = self.soma_position[neuron_idx];
let terminal = self.axon_terminal[neuron_idx];
let dx = soma[0] - terminal[0];
let dy = soma[1] - terminal[1];
let dz = soma[2] - terminal[2];
let dist = (dx * dx + dy * dy + dz * dz).sqrt();
if dist < step_size {
self.axon_terminal[neuron_idx] = soma;
} else {
let scale = step_size / dist;
self.axon_terminal[neuron_idx] = [
terminal[0] + dx * scale,
terminal[1] + dy * scale,
terminal[2] + dz * scale,
];
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neuron_arrays_dale_law() {
let arr = NeuronArrays::new(100, 80, -17920, -14080);
assert_eq!(arr.len(), 100);
for i in 0..80 {
assert!(flags::is_excitatory(arr.flags[i]), "neuron {i} should be excitatory");
}
for i in 80..100 {
assert!(flags::is_inhibitory(arr.flags[i]), "neuron {i} should be inhibitory");
}
}
#[test]
fn profile_encoding() {
let f = flags::encode(false, NeuronProfile::IntrinsicBursting);
assert!(flags::is_excitatory(f));
assert_eq!(NeuronProfile::from_flags(f), NeuronProfile::IntrinsicBursting);
let f2 = flags::encode(true, NeuronProfile::FastSpiking);
assert!(flags::is_inhibitory(f2));
assert_eq!(NeuronProfile::from_flags(f2), NeuronProfile::FastSpiking);
}
#[test]
fn resting_state() {
let arr = NeuronArrays::new(10, 8, -17920, -14080);
for &m in &arr.membrane {
assert_eq!(m, -17920);
}
for &t in &arr.threshold {
assert_eq!(t, -14080);
}
for &r in &arr.refract_remaining {
assert_eq!(r, 0);
}
for &tr in &arr.trace {
assert_eq!(tr, 0);
}
}
}