1use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[derive(Clone, Debug)]
9pub struct Synapse {
10 pub pre_block: u32,
11 pub post_block: u32,
12 pub weight: f32, pub last_pre_spike_ms: u64,
14 pub last_post_spike_ms: u64,
15 pub spike_history: Vec<i64>, pub ltp_count: u32, pub ltd_count: u32, }
19
20pub struct SynapticPlasticity {
21 pub synapses: HashMap<(u32, u32), Synapse>,
22 pub ltp_threshold_ms: i64, pub ltd_threshold_ms: i64, pub learning_rate: f32, pub total_updates: u32,
26}
27
28impl Default for SynapticPlasticity {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl SynapticPlasticity {
35 pub fn new() -> Self {
36 Self {
37 synapses: HashMap::new(),
38 ltp_threshold_ms: 20, ltd_threshold_ms: -20, learning_rate: 0.1,
41 total_updates: 0,
42 }
43 }
44
45 pub fn ltp(&mut self, pre_block: u32, post_block: u32) -> f32 {
47 let key = (pre_block, post_block);
48 let now = Self::now_ms();
49
50 let synapse = self.synapses.entry(key).or_insert_with(|| Synapse {
51 pre_block,
52 post_block,
53 weight: 0.5,
54 last_pre_spike_ms: now,
55 last_post_spike_ms: now,
56 spike_history: Vec::new(),
57 ltp_count: 0,
58 ltd_count: 0,
59 });
60
61 let increment = self.learning_rate * (1.0 - synapse.weight);
63 synapse.weight = (synapse.weight + increment).min(1.0);
64 synapse.ltp_count += 1;
65 synapse.last_pre_spike_ms = now;
66 self.total_updates += 1;
67
68 synapse.weight
69 }
70
71 pub fn ltd(&mut self, pre_block: u32, post_block: u32) -> f32 {
73 let key = (pre_block, post_block);
74 let now = Self::now_ms();
75
76 let synapse = self.synapses.entry(key).or_insert_with(|| Synapse {
77 pre_block,
78 post_block,
79 weight: 0.5,
80 last_pre_spike_ms: now,
81 last_post_spike_ms: now,
82 spike_history: Vec::new(),
83 ltp_count: 0,
84 ltd_count: 0,
85 });
86
87 let decrement = self.learning_rate * synapse.weight;
89 synapse.weight = (synapse.weight - decrement).max(0.0);
90 synapse.ltd_count += 1;
91 synapse.last_post_spike_ms = now;
92 self.total_updates += 1;
93
94 synapse.weight
95 }
96
97 pub fn stdp(
101 &mut self,
102 pre_block: u32,
103 post_block: u32,
104 pre_time_ms: i64,
105 post_time_ms: i64,
106 ) -> f32 {
107 let key = (pre_block, post_block);
108 let timing_diff = post_time_ms - pre_time_ms; let synapse = self.synapses.entry(key).or_insert_with(|| Synapse {
111 pre_block,
112 post_block,
113 weight: 0.5,
114 last_pre_spike_ms: Self::now_ms(),
115 last_post_spike_ms: Self::now_ms(),
116 spike_history: Vec::new(),
117 ltp_count: 0,
118 ltd_count: 0,
119 });
120
121 synapse.spike_history.push(timing_diff);
122 if synapse.spike_history.len() > 50 {
123 synapse.spike_history.remove(0);
124 }
125
126 if timing_diff > 0 && timing_diff <= self.ltp_threshold_ms {
128 let strength = 1.0 - (timing_diff as f32 / self.ltp_threshold_ms as f32);
129 let increment = self.learning_rate * strength * (1.0 - synapse.weight);
130 synapse.weight = (synapse.weight + increment).min(1.0);
131 synapse.ltp_count += 1;
132 }
133 else if timing_diff < 0 && timing_diff >= self.ltd_threshold_ms {
135 let strength = 1.0 - (-timing_diff as f32 / -self.ltd_threshold_ms as f32);
136 let decrement = self.learning_rate * strength * synapse.weight;
137 synapse.weight = (synapse.weight - decrement).max(0.0);
138 synapse.ltd_count += 1;
139 }
140
141 self.total_updates += 1;
142 synapse.weight
143 }
144
145 pub fn get_weight(&self, pre_block: u32, post_block: u32) -> f32 {
147 self.synapses
148 .get(&(pre_block, post_block))
149 .map(|s| s.weight)
150 .unwrap_or(0.0)
151 }
152
153 pub fn strongest_synapses(&self, k: usize) -> Vec<(&(u32, u32), &Synapse)> {
155 let mut synapses: Vec<_> = self.synapses.iter().collect();
156 synapses.sort_by(|a, b| b.1.weight.total_cmp(&a.1.weight));
157 synapses.into_iter().take(k).collect()
158 }
159
160 pub fn ltp_dominant(&self) -> Vec<&Synapse> {
162 self.synapses
163 .values()
164 .filter(|s| s.ltp_count > s.ltd_count)
165 .collect()
166 }
167
168 pub fn ltd_dominant(&self) -> Vec<&Synapse> {
169 self.synapses
170 .values()
171 .filter(|s| s.ltd_count > s.ltp_count)
172 .collect()
173 }
174
175 pub fn stdp_curve(&self, timing_diff_ms: i64) -> f32 {
177 if timing_diff_ms > 0 && timing_diff_ms <= self.ltp_threshold_ms {
178 (1.0 - (timing_diff_ms as f32 / self.ltp_threshold_ms as f32).abs()).max(0.0)
180 } else if timing_diff_ms < 0 && timing_diff_ms >= self.ltd_threshold_ms {
181 -((timing_diff_ms as f32 / self.ltd_threshold_ms as f32).abs()).min(1.0)
183 } else {
184 0.0
185 }
186 }
187
188 pub fn stats(&self) -> (u32, u32, u32, f32, f32) {
190 let total_synapses = self.synapses.len() as u32;
191 let total_ltp: u32 = self.synapses.values().map(|s| s.ltp_count).sum();
192 let total_ltd: u32 = self.synapses.values().map(|s| s.ltd_count).sum();
193
194 let avg_weight = if !self.synapses.is_empty() {
195 self.synapses.values().map(|s| s.weight).sum::<f32>() / self.synapses.len() as f32
196 } else {
197 0.0
198 };
199
200 let ltp_ratio = if total_ltp + total_ltd > 0 {
201 total_ltp as f32 / (total_ltp + total_ltd) as f32
202 } else {
203 0.0
204 };
205
206 (total_synapses, total_ltp, total_ltd, avg_weight, ltp_ratio)
207 }
208
209 pub fn save(&self, dir: &Path) -> Result<(), String> {
210 let path = dir.join("synaptic_plasticity.bin");
211 let mut data = Vec::new();
212
213 data.extend_from_slice(b"SYPLS");
214 data.push(1);
215
216 let synapse_count = self.synapses.len() as u32;
217 data.extend_from_slice(&synapse_count.to_le_bytes());
218
219 for ((pre, post), synapse) in &self.synapses {
220 data.extend_from_slice(&pre.to_le_bytes());
221 data.extend_from_slice(&post.to_le_bytes());
222 data.extend_from_slice(&synapse.weight.to_le_bytes());
223 data.extend_from_slice(&synapse.last_pre_spike_ms.to_le_bytes());
224 data.extend_from_slice(&synapse.last_post_spike_ms.to_le_bytes());
225 data.extend_from_slice(&synapse.ltp_count.to_le_bytes());
226 data.extend_from_slice(&synapse.ltd_count.to_le_bytes());
227
228 let hist_count = synapse.spike_history.len() as u16;
229 data.extend_from_slice(&hist_count.to_le_bytes());
230 for &timing in &synapse.spike_history {
231 data.extend_from_slice(&timing.to_le_bytes());
232 }
233 }
234
235 let tmp_path = dir.join("synaptic_plasticity.bin.tmp");
236 fs::write(&tmp_path, data).map_err(|e| e.to_string())?;
237 fs::rename(&tmp_path, &path).map_err(|e| e.to_string())
238 }
239
240 pub fn load(dir: &Path) -> Result<Self, String> {
241 let path = dir.join("synaptic_plasticity.bin");
242 if !path.exists() {
243 return Ok(Self::new());
244 }
245
246 let data = fs::read(&path).map_err(|e| e.to_string())?;
247 if data.len() < 5 || &data[0..5] != b"SYPLS" {
248 return Ok(Self::new());
249 }
250
251 let mut idx = 6;
252 let mut synapses = HashMap::new();
253
254 if idx + 4 <= data.len() {
255 let synapse_count =
256 u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
257 as usize;
258 idx += 4;
259
260 for _ in 0..synapse_count {
261 if idx + 40 > data.len() {
262 break;
263 }
264
265 let pre =
266 u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
267 idx += 4;
268 let post =
269 u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
270 idx += 4;
271 let weight =
272 f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
273 idx += 4;
274 let last_pre = u64::from_le_bytes([
275 data[idx],
276 data[idx + 1],
277 data[idx + 2],
278 data[idx + 3],
279 data[idx + 4],
280 data[idx + 5],
281 data[idx + 6],
282 data[idx + 7],
283 ]);
284 idx += 8;
285 let last_post = u64::from_le_bytes([
286 data[idx],
287 data[idx + 1],
288 data[idx + 2],
289 data[idx + 3],
290 data[idx + 4],
291 data[idx + 5],
292 data[idx + 6],
293 data[idx + 7],
294 ]);
295 idx += 8;
296 let ltp_count =
297 u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
298 idx += 4;
299 let ltd_count =
300 u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
301 idx += 4;
302
303 let hist_count = u16::from_le_bytes([data[idx], data[idx + 1]]) as usize;
304 idx += 2;
305
306 let mut spike_history = Vec::new();
307 for _ in 0..hist_count {
308 if idx + 8 > data.len() {
309 break;
310 }
311 let timing = i64::from_le_bytes([
312 data[idx],
313 data[idx + 1],
314 data[idx + 2],
315 data[idx + 3],
316 data[idx + 4],
317 data[idx + 5],
318 data[idx + 6],
319 data[idx + 7],
320 ]);
321 spike_history.push(timing);
322 idx += 8;
323 }
324
325 synapses.insert(
326 (pre, post),
327 Synapse {
328 pre_block: pre,
329 post_block: post,
330 weight,
331 last_pre_spike_ms: last_pre,
332 last_post_spike_ms: last_post,
333 spike_history,
334 ltp_count,
335 ltd_count,
336 },
337 );
338 }
339 }
340
341 Ok(Self {
342 synapses,
343 ltp_threshold_ms: 20,
344 ltd_threshold_ms: -20,
345 learning_rate: 0.1,
346 total_updates: 0,
347 })
348 }
349
350 pub fn load_or_init(dir: &Path) -> Self {
351 Self::load(dir).unwrap_or_else(|_| Self::new())
352 }
353
354 pub fn heterosynaptic_depression(
356 &mut self,
357 active_synapse: (u32, u32),
358 neighborhood_radius: u32,
359 ) {
360 if let Some(active) = self.synapses.get(&active_synapse) {
361 let pre = active.pre_block;
362 let depression_strength = self.learning_rate * 0.5;
363
364 let post = active_synapse.1;
366 let keys_to_weaken: Vec<_> = self
367 .synapses
368 .keys()
369 .filter(|(p_pre, p_post)| {
370 *p_post == post && self.distance(pre, *p_pre) <= neighborhood_radius
371 })
372 .cloned()
373 .collect();
374
375 for key in keys_to_weaken {
376 if key != active_synapse {
377 if let Some(synapse) = self.synapses.get_mut(&key) {
378 synapse.weight = (synapse.weight - depression_strength).max(0.0);
379 synapse.ltd_count += 1;
380 }
381 }
382 }
383 }
384 }
385
386 fn distance(&self, a: u32, b: u32) -> u32 {
388 a.abs_diff(b)
389 }
390
391 pub fn time_dependent_plasticity(
393 &mut self,
394 synapse_key: (u32, u32),
395 practice_count: u32,
396 strategy_age_ms: u64,
397 ) -> f32 {
398 let now = Self::now_ms();
399
400 if let Some(synapse) = self.synapses.get_mut(&synapse_key) {
401 let early_phase_rate = if practice_count < 10 {
403 0.2 + (10 - practice_count) as f32 * 0.01 } else {
405 0.05
406 };
407
408 let consolidation_decay = if (10..50).contains(&practice_count) {
410 0.05 * (50 - practice_count) as f32 / 40.0 } else if practice_count >= 50 {
412 0.02 } else {
414 early_phase_rate
415 };
416
417 let strategy_boost = if strategy_age_ms < 300_000 {
419 0.1 * (1.0 - (strategy_age_ms as f32 / 300_000.0)) } else {
422 0.0
423 };
424
425 self.learning_rate = (consolidation_decay + strategy_boost).min(0.3);
426 synapse.last_pre_spike_ms = now;
427
428 self.learning_rate
429 } else {
430 0.2 }
432 }
433
434 fn now_ms() -> u64 {
435 std::time::SystemTime::now()
436 .duration_since(std::time::UNIX_EPOCH)
437 .unwrap_or_default()
438 .as_millis() as u64
439 }
440}