Skip to main content

microscope_memory/
neuroplasticity.rs

1//! Neuroplasticity — adaptive reorganization and learning through experience
2//! Implements Hebbian learning, structural reorganization, and adaptive pathway strengthening
3
4use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[derive(Clone, Debug)]
9pub struct SynapticConnection {
10    pub from_block: u32,
11    pub to_block: u32,
12    pub weight: f32,    // 0.0-1.0, synaptic strength
13    pub frequency: u32, // co-activation count
14    pub last_activated_ms: u64,
15    pub plasticity_score: f32, // capacity to change
16}
17
18#[derive(Clone, Debug)]
19pub struct NeuralPathway {
20    pub id: u64,
21    pub nodes: Vec<u32>,         // blocks in sequence
22    pub strength: f32,           // pathway activation strength
23    pub efficiency: f32,         // speed of activation (inverse latency)
24    pub usage_count: u32,        // how often used
25    pub specialized_for: String, // domain/context
26}
27
28pub struct Neuroplasticity {
29    pub synapses: HashMap<(u32, u32), SynapticConnection>,
30    pub pathways: HashMap<u64, NeuralPathway>,
31    pub reorganization_threshold: f32,
32    pub plasticity_window_ms: u64,
33}
34
35impl Default for Neuroplasticity {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl Neuroplasticity {
42    pub fn new() -> Self {
43        Self {
44            synapses: HashMap::new(),
45            pathways: HashMap::new(),
46            reorganization_threshold: 0.5,
47            plasticity_window_ms: 86_400_000, // 24 hours
48        }
49    }
50
51    /// Strengthen or create synaptic connection (Hebbian learning)
52    pub fn strengthen_synapse(&mut self, from: u32, to: u32, success: bool) {
53        let key = (from, to);
54        let now = Self::now_ms();
55
56        self.synapses
57            .entry(key)
58            .and_modify(|s| {
59                s.frequency += 1;
60                let delta = if success { 0.05 } else { -0.02 };
61                s.weight = (s.weight + delta).clamp(0.0, 1.0);
62                s.last_activated_ms = now;
63            })
64            .or_insert_with(|| SynapticConnection {
65                from_block: from,
66                to_block: to,
67                weight: if success { 0.6 } else { 0.3 },
68                frequency: 1,
69                last_activated_ms: now,
70                plasticity_score: 0.8,
71            });
72    }
73
74    /// Create or strengthen neural pathway
75    pub fn strengthen_pathway(&mut self, nodes: Vec<u32>, domain: &str) -> u64 {
76        let pathway_id = Self::pathway_hash(&nodes);
77
78        self.pathways
79            .entry(pathway_id)
80            .and_modify(|p| {
81                p.usage_count += 1;
82                p.strength = (p.strength + 0.1).min(1.0);
83                p.efficiency = (p.efficiency + 0.05).min(1.0);
84            })
85            .or_insert_with(|| NeuralPathway {
86                id: pathway_id,
87                nodes: nodes.clone(),
88                strength: 0.5,
89                efficiency: 0.4,
90                usage_count: 1,
91                specialized_for: domain.to_string(),
92            });
93
94        pathway_id
95    }
96
97    /// Prune weak connections (use it or lose it)
98    pub fn prune_weak_synapses(&mut self, strength_threshold: f32) -> usize {
99        let before = self.synapses.len();
100        self.synapses.retain(|_, s| s.weight > strength_threshold);
101        before - self.synapses.len()
102    }
103
104    /// Reorganize pathways (merge/split based on usage)
105    pub fn reorganize_pathways(&mut self) -> usize {
106        let mut reorganized = 0;
107        let now = Self::now_ms();
108
109        // Strengthen frequently used pathways
110        for pathway in self.pathways.values_mut() {
111            if pathway.usage_count >= 10 {
112                pathway.strength = (pathway.strength + 0.02).min(1.0);
113                reorganized += 1;
114            }
115        }
116
117        // Prune unused pathways
118        let unused_before = self.pathways.len();
119        self.pathways.retain(|_, p| {
120            let age = now.saturating_sub(p.id);
121            p.usage_count > 0 || age < self.plasticity_window_ms
122        });
123
124        reorganized + (unused_before - self.pathways.len())
125    }
126
127    /// Calculate network plasticity (capacity to change)
128    pub fn calculate_plasticity(&self) -> f32 {
129        if self.synapses.is_empty() {
130            return 0.0;
131        }
132
133        let weak_synapses = self.synapses.values().filter(|s| s.weight < 0.3).count() as f32;
134        let total = self.synapses.len() as f32;
135
136        (weak_synapses / total).min(1.0)
137    }
138
139    /// Find alternative pathways (network rewiring)
140    pub fn find_alternative_pathways(&self, source: u32, target: u32) -> Vec<Vec<u32>> {
141        let mut alternatives = Vec::new();
142
143        // Simple: find two-hop paths
144        for (key, synapse) in &self.synapses {
145            if key.0 == source {
146                for (key2, synapse2) in &self.synapses {
147                    if key2.0 == key.1 && key2.1 == target {
148                        let combined_weight = synapse.weight * synapse2.weight;
149                        if combined_weight > 0.2 {
150                            alternatives.push(vec![source, key.1, target]);
151                        }
152                    }
153                }
154            }
155        }
156
157        alternatives
158    }
159
160    /// Get network statistics
161    pub fn stats(&self) -> (usize, usize, f32, f32, usize) {
162        let synapse_count = self.synapses.len();
163        let pathway_count = self.pathways.len();
164        let avg_weight = if !self.synapses.is_empty() {
165            self.synapses.values().map(|s| s.weight).sum::<f32>() / synapse_count as f32
166        } else {
167            0.0
168        };
169        let plasticity = self.calculate_plasticity();
170        let strong_pathways = self.pathways.values().filter(|p| p.strength > 0.7).count();
171
172        (
173            synapse_count,
174            pathway_count,
175            avg_weight,
176            plasticity,
177            strong_pathways,
178        )
179    }
180
181    /// Get strongest pathways
182    pub fn strongest_pathways(&self, k: usize) -> Vec<&NeuralPathway> {
183        let mut sorted: Vec<_> = self.pathways.values().collect();
184        sorted.sort_by(|a, b| b.strength.total_cmp(&a.strength));
185        sorted.into_iter().take(k).collect()
186    }
187
188    pub fn save(&self, dir: &Path) -> Result<(), String> {
189        let path = dir.join("neuroplasticity.bin");
190        let mut data = Vec::new();
191
192        data.extend_from_slice(b"NPLS");
193        data.push(1);
194
195        // Synapses
196        let synapse_count = self.synapses.len() as u32;
197        data.extend_from_slice(&synapse_count.to_le_bytes());
198        for ((from, to), synapse) in &self.synapses {
199            data.extend_from_slice(&from.to_le_bytes());
200            data.extend_from_slice(&to.to_le_bytes());
201            data.extend_from_slice(&synapse.weight.to_le_bytes());
202            data.extend_from_slice(&synapse.frequency.to_le_bytes());
203            data.extend_from_slice(&synapse.last_activated_ms.to_le_bytes());
204            data.extend_from_slice(&synapse.plasticity_score.to_le_bytes());
205        }
206
207        // Pathways
208        let pathway_count = self.pathways.len() as u32;
209        data.extend_from_slice(&pathway_count.to_le_bytes());
210        for pathway in self.pathways.values() {
211            data.extend_from_slice(&pathway.id.to_le_bytes());
212            data.extend_from_slice(&pathway.strength.to_le_bytes());
213            data.extend_from_slice(&pathway.efficiency.to_le_bytes());
214            data.extend_from_slice(&pathway.usage_count.to_le_bytes());
215
216            let spec_bytes = pathway.specialized_for.as_bytes();
217            data.push(spec_bytes.len() as u8);
218            data.extend_from_slice(spec_bytes);
219
220            let node_count = pathway.nodes.len() as u16;
221            data.extend_from_slice(&node_count.to_le_bytes());
222            for &node in &pathway.nodes {
223                data.extend_from_slice(&node.to_le_bytes());
224            }
225        }
226
227        let tmp_path = dir.join("neuroplasticity.bin.tmp");
228        fs::write(&tmp_path, data).map_err(|e| e.to_string())?;
229        fs::rename(&tmp_path, &path).map_err(|e| e.to_string())
230    }
231
232    pub fn load(dir: &Path) -> Result<Self, String> {
233        let path = dir.join("neuroplasticity.bin");
234        if !path.exists() {
235            return Ok(Self::new());
236        }
237
238        let data = fs::read(&path).map_err(|e| e.to_string())?;
239        if data.len() < 5 || &data[0..4] != b"NPLS" {
240            return Ok(Self::new());
241        }
242
243        let mut idx = 5;
244        let mut synapses = HashMap::new();
245        let mut pathways = HashMap::new();
246
247        // Read synapses
248        if idx + 4 <= data.len() {
249            let synapse_count =
250                u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
251                    as usize;
252            idx += 4;
253
254            for _ in 0..synapse_count {
255                if idx + 36 > data.len() {
256                    break;
257                }
258
259                let from =
260                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
261                idx += 4;
262                let to =
263                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
264                idx += 4;
265                let weight =
266                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
267                idx += 4;
268                let frequency =
269                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
270                idx += 4;
271                let last_activated_ms = u64::from_le_bytes([
272                    data[idx],
273                    data[idx + 1],
274                    data[idx + 2],
275                    data[idx + 3],
276                    data[idx + 4],
277                    data[idx + 5],
278                    data[idx + 6],
279                    data[idx + 7],
280                ]);
281                idx += 8;
282                let plasticity_score =
283                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
284                idx += 4;
285
286                synapses.insert(
287                    (from, to),
288                    SynapticConnection {
289                        from_block: from,
290                        to_block: to,
291                        weight,
292                        frequency,
293                        last_activated_ms,
294                        plasticity_score,
295                    },
296                );
297            }
298        }
299
300        // Read pathways
301        if idx + 4 <= data.len() {
302            let pathway_count =
303                u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
304                    as usize;
305            idx += 4;
306
307            for _ in 0..pathway_count {
308                if idx + 30 > data.len() {
309                    break;
310                }
311
312                let id = u64::from_le_bytes([
313                    data[idx],
314                    data[idx + 1],
315                    data[idx + 2],
316                    data[idx + 3],
317                    data[idx + 4],
318                    data[idx + 5],
319                    data[idx + 6],
320                    data[idx + 7],
321                ]);
322                idx += 8;
323
324                let strength =
325                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
326                idx += 4;
327                let efficiency =
328                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
329                idx += 4;
330                let usage_count =
331                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
332                idx += 4;
333
334                if idx >= data.len() {
335                    break;
336                }
337                let spec_len = data[idx] as usize;
338                idx += 1;
339
340                if idx + spec_len > data.len() {
341                    break;
342                }
343                let specialized_for =
344                    String::from_utf8_lossy(&data[idx..idx + spec_len]).to_string();
345                idx += spec_len;
346
347                if idx + 2 > data.len() {
348                    break;
349                }
350                let node_count = u16::from_le_bytes([data[idx], data[idx + 1]]) as usize;
351                idx += 2;
352
353                let mut nodes = Vec::new();
354                for _ in 0..node_count {
355                    if idx + 4 > data.len() {
356                        break;
357                    }
358                    let node = u32::from_le_bytes([
359                        data[idx],
360                        data[idx + 1],
361                        data[idx + 2],
362                        data[idx + 3],
363                    ]);
364                    nodes.push(node);
365                    idx += 4;
366                }
367
368                pathways.insert(
369                    id,
370                    NeuralPathway {
371                        id,
372                        nodes,
373                        strength,
374                        efficiency,
375                        usage_count,
376                        specialized_for,
377                    },
378                );
379            }
380        }
381
382        Ok(Self {
383            synapses,
384            pathways,
385            reorganization_threshold: 0.5,
386            plasticity_window_ms: 86_400_000,
387        })
388    }
389
390    pub fn load_or_init(dir: &Path) -> Self {
391        Self::load(dir).unwrap_or_else(|_| Self::new())
392    }
393
394    fn pathway_hash(nodes: &[u32]) -> u64 {
395        let mut hash: u64 = 0xcbf29ce484222325;
396        for &node in nodes.iter().take(10) {
397            hash = hash.wrapping_mul(0x100000001b3) ^ (node as u64);
398        }
399        hash
400    }
401
402    fn now_ms() -> u64 {
403        std::time::SystemTime::now()
404            .duration_since(std::time::UNIX_EPOCH)
405            .unwrap_or_default()
406            .as_millis() as u64
407    }
408}