use super::{SpatialNeuron, SpatialSynapse, SpatialSynapseStore};
#[derive(Clone, Copy, Debug)]
pub struct WiringConfig {
pub max_distance: f32,
pub max_fanout: u16,
pub max_fanin: u16,
pub default_magnitude: u8,
}
impl Default for WiringConfig {
fn default() -> Self {
Self {
max_distance: 5.0,
max_fanout: 8,
max_fanin: 20,
default_magnitude: 100,
}
}
}
pub fn wire_by_proximity(
neurons: &[SpatialNeuron],
config: &WiringConfig,
) -> SpatialSynapseStore {
let n = neurons.len();
let mut store = SpatialSynapseStore::new(n);
let mut fanout = vec![0u16; n];
let mut fanin = vec![0u16; n];
for i in 0..n {
if neurons[i].nuclei.is_motor() {
continue;
}
if !neurons[i].axon.is_alive() {
continue;
}
let terminal = neurons[i].axon.terminal;
let mut candidates: Vec<(usize, f32)> = Vec::new();
for j in 0..n {
if i == j {
continue;
}
let soma = neurons[j].soma.position;
let dx = terminal[0] - soma[0];
let dy = terminal[1] - soma[1];
let dz = terminal[2] - soma[2];
let dist = (dx * dx + dy * dy + dz * dz).sqrt();
if dist <= config.max_distance {
candidates.push((j, dist));
}
}
candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
for (j, _dist) in candidates {
if fanout[i] >= config.max_fanout {
break;
}
if fanin[j] >= config.max_fanin {
continue;
}
let syn = if neurons[i].nuclei.is_inhibitory() {
SpatialSynapse::inhibitory(
i as u32,
j as u32,
config.default_magnitude,
0, )
} else {
SpatialSynapse::excitatory(
i as u32,
j as u32,
config.default_magnitude,
0,
)
};
store.add(syn);
fanout[i] += 1;
fanin[j] += 1;
}
}
store.rebuild_index(n);
store
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spatial::Axon;
#[test]
fn test_proximity_wiring_basic() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([5.0, 0.0, 0.0], 100);
let target = SpatialNeuron::pyramidal_at([5.0, 0.5, 0.0]);
let neurons = vec![source, target];
let config = WiringConfig {
max_distance: 2.0,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 1);
assert_eq!(store.outgoing(0)[0].target, 1);
}
#[test]
fn test_proximity_wiring_out_of_range() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([5.0, 0.0, 0.0], 100);
let target = SpatialNeuron::pyramidal_at([20.0, 0.0, 0.0]);
let neurons = vec![source, target];
let config = WiringConfig {
max_distance: 3.0,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 0);
}
#[test]
fn test_motor_neurons_dont_project() {
let motor = SpatialNeuron::motor_at([0.0, 0.0, 0.0], 0, 1);
let target = SpatialNeuron::pyramidal_at([0.5, 0.0, 0.0]);
let neurons = vec![motor, target];
let config = WiringConfig::default();
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 0);
}
#[test]
fn test_motor_neurons_receive_input() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([5.0, 0.0, 0.0], 100);
let motor = SpatialNeuron::motor_at([5.0, 0.0, 0.0], 0, 1);
let neurons = vec![source, motor];
let config = WiringConfig {
max_distance: 2.0,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 1);
assert_eq!(store.outgoing(0)[0].target, 1);
}
#[test]
fn test_fanout_limit() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([5.0, 0.0, 0.0], 100);
let mut neurons = vec![source];
for i in 0..20 {
neurons.push(SpatialNeuron::pyramidal_at([
5.0 + (i as f32) * 0.1,
0.0,
0.0,
]));
}
let config = WiringConfig {
max_distance: 5.0,
max_fanout: 4,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 4);
}
#[test]
fn test_fanin_limit() {
let target = SpatialNeuron::pyramidal_at([10.0, 0.0, 0.0]);
let mut neurons = vec![target];
for i in 0..10 {
let mut src = SpatialNeuron::pyramidal_at([i as f32, 0.0, 0.0]);
src.axon = Axon::myelinated([10.0, (i as f32) * 0.1, 0.0], 100);
neurons.push(src);
}
let config = WiringConfig {
max_distance: 3.0,
max_fanin: 3,
max_fanout: 8,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
let incoming: usize = (1..neurons.len())
.map(|i| {
store
.outgoing(i as u32)
.iter()
.filter(|s| s.target == 0)
.count()
})
.sum();
assert!(incoming <= 3, "fanin should be capped at 3, got {}", incoming);
}
#[test]
fn test_inhibitory_wiring() {
let mut inh = SpatialNeuron::interneuron_at([0.0, 0.0, 0.0]);
inh.axon = Axon::myelinated([2.0, 0.0, 0.0], 100);
let target = SpatialNeuron::pyramidal_at([2.0, 0.0, 0.0]);
let neurons = vec![inh, target];
let config = WiringConfig {
max_distance: 2.0,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 1);
assert!(store.outgoing(0)[0].is_inhibitory());
}
#[test]
fn test_nearest_first_preference() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([5.0, 0.0, 0.0], 100);
let near = SpatialNeuron::pyramidal_at([5.0, 0.0, 0.0]); let mid = SpatialNeuron::pyramidal_at([6.0, 0.0, 0.0]); let far = SpatialNeuron::pyramidal_at([7.0, 0.0, 0.0]);
let neurons = vec![source, near, mid, far];
let config = WiringConfig {
max_distance: 5.0,
max_fanout: 2,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
let out = store.outgoing(0);
assert_eq!(out.len(), 2);
assert_eq!(out[0].target, 1); assert_eq!(out[1].target, 2); }
#[test]
fn test_dead_axon_skipped() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([5.0, 0.0, 0.0], 100);
source.axon.health = 0;
let target = SpatialNeuron::pyramidal_at([5.0, 0.0, 0.0]);
let neurons = vec![source, target];
let config = WiringConfig::default();
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0).len(), 0);
}
#[test]
fn test_delay_is_zero() {
let mut source = SpatialNeuron::pyramidal_at([0.0, 0.0, 0.0]);
source.axon = Axon::myelinated([2.0, 0.0, 0.0], 100);
let target = SpatialNeuron::pyramidal_at([2.0, 0.0, 0.0]);
let neurons = vec![source, target];
let config = WiringConfig {
max_distance: 3.0,
..Default::default()
};
let store = wire_by_proximity(&neurons, &config);
assert_eq!(store.outgoing(0)[0].delay_us, 0);
}
}