Skip to main content

microscope_memory/
functional_plasticity.rs

1//! Functional Neuroplasticity — adaptive reorganization of functional areas
2//! Sensorimotor remapping, cross-modal plasticity, functional compensation
3
4use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[derive(Clone, Debug)]
9pub struct FunctionalArea {
10    pub id: u64,
11    pub name: String,     // "vision", "language", "motor", etc.
12    pub blocks: Vec<u32>, // blocks assigned to this area
13    pub primary_domain: String,
14    pub cross_modal_connections: Vec<u64>, // connected to other areas
15    pub plasticity_index: f32,             // 0.0-1.0, how adaptive this area is
16    pub compensation_level: f32,           // how much it compensates for damage
17}
18
19#[derive(Clone, Debug)]
20pub struct SensoriMotorMap {
21    pub input_block: u32,
22    pub output_blocks: Vec<u32>,
23    pub strength: f32,
24    pub remapping_count: u32,
25    pub efficiency: f32,
26}
27
28pub struct FunctionalPlasticity {
29    pub areas: HashMap<u64, FunctionalArea>,
30    pub sensorimotor_maps: Vec<SensoriMotorMap>,
31    pub crossmodal_connections: HashMap<(u64, u64), f32>, // (area1, area2) -> strength
32    pub damage_compensation: HashMap<u64, f32>,           // area_id -> compensation strength
33}
34
35impl Default for FunctionalPlasticity {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl FunctionalPlasticity {
42    pub fn new() -> Self {
43        Self {
44            areas: HashMap::new(),
45            sensorimotor_maps: Vec::new(),
46            crossmodal_connections: HashMap::new(),
47            damage_compensation: HashMap::new(),
48        }
49    }
50
51    /// Create functional area (e.g., visual cortex)
52    pub fn create_area(&mut self, name: &str, domain: &str, seed_blocks: Vec<u32>) -> u64 {
53        let area_id = Self::area_hash(name, domain);
54
55        let area = FunctionalArea {
56            id: area_id,
57            name: name.to_string(),
58            blocks: seed_blocks,
59            primary_domain: domain.to_string(),
60            cross_modal_connections: Vec::new(),
61            plasticity_index: 0.5,
62            compensation_level: 0.0,
63        };
64
65        self.areas.insert(area_id, area);
66        area_id
67    }
68
69    /// Create sensorimotor mapping (input -> output)
70    pub fn map_sensorimotor(&mut self, input: u32, outputs: Vec<u32>) -> f32 {
71        let strength = (outputs.len() as f32 / 10.0).min(1.0);
72
73        self.sensorimotor_maps.push(SensoriMotorMap {
74            input_block: input,
75            output_blocks: outputs,
76            strength,
77            remapping_count: 0,
78            efficiency: 0.5,
79        });
80
81        strength
82    }
83
84    /// Remap sensorimotor pathway (plasticity in action)
85    pub fn remap_pathway(&mut self, old_input: u32, new_input: u32) -> bool {
86        if let Some(map) = self
87            .sensorimotor_maps
88            .iter_mut()
89            .find(|m| m.input_block == old_input)
90        {
91            map.input_block = new_input;
92            map.remapping_count += 1;
93            map.efficiency = (map.efficiency + 0.05).min(1.0);
94            return true;
95        }
96        false
97    }
98
99    /// Create cross-modal connection (area1 <-> area2)
100    pub fn connect_areas(&mut self, area1_id: u64, area2_id: u64) -> bool {
101        if self.areas.contains_key(&area1_id) && self.areas.contains_key(&area2_id) {
102            let strength = 0.3;
103            self.crossmodal_connections
104                .insert((area1_id, area2_id), strength);
105            self.crossmodal_connections
106                .insert((area2_id, area1_id), strength);
107
108            if let Some(a1) = self.areas.get_mut(&area1_id) {
109                a1.cross_modal_connections.push(area2_id);
110            }
111            if let Some(a2) = self.areas.get_mut(&area2_id) {
112                a2.cross_modal_connections.push(area1_id);
113            }
114
115            return true;
116        }
117        false
118    }
119
120    /// Strengthen cross-modal connection
121    pub fn strengthen_crossmodal(&mut self, area1_id: u64, area2_id: u64, use_success: bool) {
122        let key = (area1_id, area2_id);
123        if let Some(strength) = self.crossmodal_connections.get_mut(&key) {
124            let delta = if use_success { 0.05 } else { -0.02 };
125            *strength = (*strength + delta).clamp(0.0, 1.0);
126        }
127    }
128
129    /// Simulate area damage and trigger compensation
130    pub fn damage_area(&mut self, area_id: u64, severity: f32) {
131        let connected: Vec<u64> = if let Some(area) = self.areas.get_mut(&area_id) {
132            area.plasticity_index = (area.plasticity_index - severity).max(0.0);
133            area.cross_modal_connections.clone()
134        } else {
135            Vec::new()
136        };
137
138        // Trigger compensation from connected areas
139        for connected_id in connected {
140            let comp_strength = (severity * 0.7).min(1.0);
141            self.damage_compensation.insert(connected_id, comp_strength);
142
143            if let Some(conn_area) = self.areas.get_mut(&connected_id) {
144                conn_area.compensation_level =
145                    (conn_area.compensation_level + comp_strength).min(1.0);
146                conn_area.plasticity_index = (conn_area.plasticity_index + 0.1).min(1.0);
147            }
148        }
149    }
150
151    /// Recover compensation (gradual adaptation)
152    pub fn recover_compensation(&mut self, area_id: u64, recovery_rate: f32) -> f32 {
153        if let Some(comp) = self.damage_compensation.get_mut(&area_id) {
154            *comp = (*comp - recovery_rate).max(0.0);
155            return *comp;
156        }
157        0.0
158    }
159
160    /// Get functional reorganization stats
161    pub fn stats(&self) -> (u32, u32, usize, f32) {
162        let total_areas = self.areas.len() as u32;
163        let total_blocks: u32 = self.areas.values().map(|a| a.blocks.len() as u32).sum();
164        let total_maps = self.sensorimotor_maps.len();
165        let avg_plasticity = if !self.areas.is_empty() {
166            self.areas.values().map(|a| a.plasticity_index).sum::<f32>() / self.areas.len() as f32
167        } else {
168            0.0
169        };
170
171        (total_areas, total_blocks, total_maps, avg_plasticity)
172    }
173
174    /// Get areas by domain
175    pub fn areas_by_domain(&self, domain: &str) -> Vec<&FunctionalArea> {
176        self.areas
177            .values()
178            .filter(|a| a.primary_domain == domain)
179            .collect()
180    }
181
182    /// Get most plastic areas
183    pub fn most_plastic(&self, k: usize) -> Vec<(&str, f32)> {
184        let mut areas: Vec<_> = self
185            .areas
186            .values()
187            .map(|a| (a.name.as_str(), a.plasticity_index))
188            .collect();
189        areas.sort_by(|a, b| b.1.total_cmp(&a.1));
190        areas.into_iter().take(k).collect()
191    }
192
193    pub fn save(&self, dir: &Path) -> Result<(), String> {
194        let path = dir.join("functional_plasticity.bin");
195        let mut data = Vec::new();
196
197        data.extend_from_slice(b"FNPL");
198        data.push(1);
199
200        // Areas
201        let area_count = self.areas.len() as u32;
202        data.extend_from_slice(&area_count.to_le_bytes());
203        for area in self.areas.values() {
204            data.extend_from_slice(&area.id.to_le_bytes());
205
206            let name_bytes = area.name.as_bytes();
207            data.push(name_bytes.len() as u8);
208            data.extend_from_slice(name_bytes);
209
210            let domain_bytes = area.primary_domain.as_bytes();
211            data.push(domain_bytes.len() as u8);
212            data.extend_from_slice(domain_bytes);
213
214            data.extend_from_slice(&area.plasticity_index.to_le_bytes());
215            data.extend_from_slice(&area.compensation_level.to_le_bytes());
216
217            let block_count = area.blocks.len() as u16;
218            data.extend_from_slice(&block_count.to_le_bytes());
219            for &block in &area.blocks {
220                data.extend_from_slice(&block.to_le_bytes());
221            }
222        }
223
224        // Sensorimotor maps
225        let map_count = self.sensorimotor_maps.len() as u32;
226        data.extend_from_slice(&map_count.to_le_bytes());
227        for map in &self.sensorimotor_maps {
228            data.extend_from_slice(&map.input_block.to_le_bytes());
229            data.extend_from_slice(&map.strength.to_le_bytes());
230            data.extend_from_slice(&map.efficiency.to_le_bytes());
231            data.extend_from_slice(&map.remapping_count.to_le_bytes());
232
233            let out_count = map.output_blocks.len() as u16;
234            data.extend_from_slice(&out_count.to_le_bytes());
235            for &out in &map.output_blocks {
236                data.extend_from_slice(&out.to_le_bytes());
237            }
238        }
239
240        let tmp_path = dir.join("functional_plasticity.bin.tmp");
241        fs::write(&tmp_path, data).map_err(|e| e.to_string())?;
242        fs::rename(&tmp_path, &path).map_err(|e| e.to_string())
243    }
244
245    pub fn load(dir: &Path) -> Result<Self, String> {
246        let path = dir.join("functional_plasticity.bin");
247        if !path.exists() {
248            return Ok(Self::new());
249        }
250
251        let data = fs::read(&path).map_err(|e| e.to_string())?;
252        if data.len() < 5 || &data[0..4] != b"FNPL" {
253            return Ok(Self::new());
254        }
255
256        let mut idx = 5;
257        let mut areas = HashMap::new();
258        let mut sensorimotor_maps = Vec::new();
259
260        // Read areas
261        if idx + 4 <= data.len() {
262            let area_count =
263                u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
264                    as usize;
265            idx += 4;
266
267            for _ in 0..area_count {
268                if idx + 18 > data.len() {
269                    break;
270                }
271
272                let id = u64::from_le_bytes([
273                    data[idx],
274                    data[idx + 1],
275                    data[idx + 2],
276                    data[idx + 3],
277                    data[idx + 4],
278                    data[idx + 5],
279                    data[idx + 6],
280                    data[idx + 7],
281                ]);
282                idx += 8;
283
284                if idx >= data.len() {
285                    break;
286                }
287                let name_len = data[idx] as usize;
288                idx += 1;
289
290                if idx + name_len > data.len() {
291                    break;
292                }
293                let name = String::from_utf8_lossy(&data[idx..idx + name_len]).to_string();
294                idx += name_len;
295
296                if idx >= data.len() {
297                    break;
298                }
299                let domain_len = data[idx] as usize;
300                idx += 1;
301
302                if idx + domain_len > data.len() {
303                    break;
304                }
305                let primary_domain =
306                    String::from_utf8_lossy(&data[idx..idx + domain_len]).to_string();
307                idx += domain_len;
308
309                if idx + 12 > data.len() {
310                    break;
311                }
312                let plasticity_index =
313                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
314                idx += 4;
315                let compensation_level =
316                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
317                idx += 4;
318
319                let block_count = u16::from_le_bytes([data[idx], data[idx + 1]]) as usize;
320                idx += 2;
321
322                let mut blocks = Vec::new();
323                for _ in 0..block_count {
324                    if idx + 4 > data.len() {
325                        break;
326                    }
327                    let block = u32::from_le_bytes([
328                        data[idx],
329                        data[idx + 1],
330                        data[idx + 2],
331                        data[idx + 3],
332                    ]);
333                    blocks.push(block);
334                    idx += 4;
335                }
336
337                areas.insert(
338                    id,
339                    FunctionalArea {
340                        id,
341                        name,
342                        blocks,
343                        primary_domain,
344                        cross_modal_connections: Vec::new(),
345                        plasticity_index,
346                        compensation_level,
347                    },
348                );
349            }
350        }
351
352        // Read sensorimotor maps
353        if idx + 4 <= data.len() {
354            let map_count =
355                u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
356                    as usize;
357            idx += 4;
358
359            for _ in 0..map_count {
360                if idx + 20 > data.len() {
361                    break;
362                }
363
364                let input_block =
365                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
366                idx += 4;
367
368                let strength =
369                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
370                idx += 4;
371
372                let efficiency =
373                    f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
374                idx += 4;
375
376                let remapping_count =
377                    u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
378                idx += 4;
379
380                let out_count = u16::from_le_bytes([data[idx], data[idx + 1]]) as usize;
381                idx += 2;
382
383                let mut output_blocks = Vec::new();
384                for _ in 0..out_count {
385                    if idx + 4 > data.len() {
386                        break;
387                    }
388                    let out = u32::from_le_bytes([
389                        data[idx],
390                        data[idx + 1],
391                        data[idx + 2],
392                        data[idx + 3],
393                    ]);
394                    output_blocks.push(out);
395                    idx += 4;
396                }
397
398                sensorimotor_maps.push(SensoriMotorMap {
399                    input_block,
400                    output_blocks,
401                    strength,
402                    remapping_count,
403                    efficiency,
404                });
405            }
406        }
407
408        Ok(Self {
409            areas,
410            sensorimotor_maps,
411            crossmodal_connections: HashMap::new(),
412            damage_compensation: HashMap::new(),
413        })
414    }
415
416    pub fn load_or_init(dir: &Path) -> Self {
417        Self::load(dir).unwrap_or_else(|_| Self::new())
418    }
419
420    fn area_hash(name: &str, domain: &str) -> u64 {
421        let mut hash: u64 = 0xcbf29ce484222325;
422        for &b in (name.to_string() + domain).as_bytes().iter().take(16) {
423            hash = hash.wrapping_mul(0x100000001b3) ^ (b as u64);
424        }
425        hash
426    }
427}