Skip to main content

feagi_npu_plasticity/
memory_neuron_array.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*
5 * Copyright 2025 Neuraville Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 */
10
11//! Memory Neuron Array - Structure of Arrays for memory neurons with lifecycle management
12//!
13//! High-performance SoA implementation optimized for:
14//! - SIMD-friendly vectorized operations
15//! - Rust/RTOS compatibility
16//! - Thread-safe operations
17//! - Efficient memory management with index reuse
18
19use crate::neuron_id_manager::{AllocationStats, NeuronIdManager};
20use serde::{Deserialize, Serialize};
21use std::collections::{HashMap, HashSet};
22
23/// Memory neuron lifecycle configuration
24#[derive(Debug, Clone, Copy)]
25pub struct MemoryNeuronLifecycleConfig {
26    /// Initial lifespan in bursts
27    pub initial_lifespan: u32,
28
29    /// Lifespan growth per reactivation
30    pub lifespan_growth_rate: f32,
31
32    /// Lifespan threshold for long-term memory conversion
33    pub longterm_threshold: u32,
34
35    /// Maximum reactivations before forced LTM
36    pub max_reactivations: u32,
37}
38
39impl Default for MemoryNeuronLifecycleConfig {
40    fn default() -> Self {
41        Self {
42            initial_lifespan: 20,
43            lifespan_growth_rate: 3.0,
44            longterm_threshold: 100,
45            max_reactivations: 1000,
46        }
47    }
48}
49
50/// Snapshot of a single memory neuron for API / inspection (plasticity array only).
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct MemoryNeuronDetail {
53    pub neuron_id: u32,
54    pub cortical_area_idx: u32,
55    pub pattern_hash: Option<u64>,
56    pub is_longterm_memory: bool,
57    pub is_active: bool,
58    pub lifespan_current: u32,
59    pub lifespan_initial: u32,
60    pub lifespan_growth_rate: f32,
61    pub creation_burst: u64,
62    pub last_activation_burst: u64,
63    pub activation_count: u32,
64}
65
66/// Memory neuron array statistics
67#[derive(Debug, Clone, Default)]
68pub struct MemoryNeuronStats {
69    pub total_capacity: usize,
70    pub active_neurons: usize,
71    pub longterm_neurons: usize,
72    pub dead_neurons: usize,
73    pub reusable_indices: usize,
74    pub memory_usage_bytes: usize,
75    pub avg_lifespan: f64,
76    pub avg_activation_count: f64,
77}
78
79/// High-performance Structure of Arrays for memory neurons
80pub struct MemoryNeuronArray {
81    capacity: usize,
82
83    // Core neuron properties (SoA layout)
84    neuron_ids: Vec<u32>,
85    cortical_area_ids: Vec<u32>,
86    is_active: Vec<bool>,
87
88    // Lifecycle management
89    lifespan_current: Vec<u32>,
90    lifespan_initial: Vec<u32>,
91    lifespan_growth_rate: Vec<f32>,
92    is_longterm_memory: Vec<bool>,
93
94    // Temporal tracking
95    creation_burst: Vec<u64>,
96    last_activation_burst: Vec<u64>,
97    activation_count: Vec<u32>,
98
99    // Pattern association (xxHash64 for fast, deterministic pattern identification)
100    pattern_hash_to_index: HashMap<u64, usize>,
101    index_to_pattern_hash: HashMap<usize, u64>,
102
103    // Index management
104    next_available_index: usize,
105    reusable_indices: HashSet<usize>,
106
107    // Area-specific tracking
108    area_neuron_indices: HashMap<u32, HashSet<usize>>,
109
110    // Neuron ID manager
111    id_manager: NeuronIdManager,
112}
113
114impl MemoryNeuronArray {
115    /// Create a new memory neuron array
116    pub fn new(capacity: usize) -> Self {
117        Self {
118            capacity,
119            neuron_ids: vec![0; capacity],
120            cortical_area_ids: vec![0; capacity],
121            is_active: vec![false; capacity],
122            lifespan_current: vec![0; capacity],
123            lifespan_initial: vec![0; capacity],
124            lifespan_growth_rate: vec![0.0; capacity],
125            is_longterm_memory: vec![false; capacity],
126            creation_burst: vec![0; capacity],
127            last_activation_burst: vec![0; capacity],
128            activation_count: vec![0; capacity],
129            pattern_hash_to_index: HashMap::new(),
130            index_to_pattern_hash: HashMap::new(),
131            next_available_index: 0,
132            reusable_indices: HashSet::new(),
133            area_neuron_indices: HashMap::new(),
134            id_manager: NeuronIdManager::new(),
135        }
136    }
137
138    /// Create a new memory neuron
139    pub fn create_memory_neuron(
140        &mut self,
141        pattern_hash: u64,
142        cortical_area_id: u32,
143        current_burst: u64,
144        config: &MemoryNeuronLifecycleConfig,
145    ) -> Option<usize> {
146        // Check if pattern already exists
147        if let Some(&existing_idx) = self.pattern_hash_to_index.get(&pattern_hash) {
148            if self.is_active[existing_idx] {
149                // Reactivate existing neuron instead
150                return self.reactivate_memory_neuron_internal(existing_idx, current_burst);
151            }
152        }
153
154        // Get neuron index (reuse or allocate new)
155        let neuron_idx = self.get_available_index_internal()?;
156
157        // Allocate global neuron ID
158        let neuron_id = self.id_manager.allocate_memory_neuron_id()?;
159
160        // Initialize neuron properties
161        self.neuron_ids[neuron_idx] = neuron_id;
162        self.cortical_area_ids[neuron_idx] = cortical_area_id;
163        self.is_active[neuron_idx] = true;
164
165        // Initialize lifecycle
166        self.lifespan_current[neuron_idx] = config.initial_lifespan;
167        self.lifespan_initial[neuron_idx] = config.initial_lifespan;
168        self.lifespan_growth_rate[neuron_idx] = config.lifespan_growth_rate;
169        self.is_longterm_memory[neuron_idx] = false;
170
171        // Initialize temporal tracking
172        self.creation_burst[neuron_idx] = current_burst;
173        self.last_activation_burst[neuron_idx] = current_burst;
174        self.activation_count[neuron_idx] = 1;
175
176        // Register pattern association
177        self.pattern_hash_to_index.insert(pattern_hash, neuron_idx);
178        self.index_to_pattern_hash.insert(neuron_idx, pattern_hash);
179
180        // Add to area tracking
181        self.area_neuron_indices
182            .entry(cortical_area_id)
183            .or_default()
184            .insert(neuron_idx);
185
186        Some(neuron_idx)
187    }
188
189    /// Reactivate an existing memory neuron
190    pub fn reactivate_memory_neuron(&mut self, neuron_idx: usize, current_burst: u64) -> bool {
191        self.reactivate_memory_neuron_internal(neuron_idx, current_burst)
192            .is_some()
193    }
194
195    /// Internal reactivate that returns Option<usize> for compatibility
196    fn reactivate_memory_neuron_internal(
197        &mut self,
198        neuron_idx: usize,
199        current_burst: u64,
200    ) -> Option<usize> {
201        if !self.is_valid_index(neuron_idx) || !self.is_active[neuron_idx] {
202            return None;
203        }
204
205        // Update activation tracking
206        self.last_activation_burst[neuron_idx] = current_burst;
207        self.activation_count[neuron_idx] += 1;
208
209        // Grow lifespan if not long-term memory
210        if !self.is_longterm_memory[neuron_idx] {
211            let current_lifespan = self.lifespan_current[neuron_idx];
212            let growth = self.lifespan_growth_rate[neuron_idx] as u32;
213            self.lifespan_current[neuron_idx] = current_lifespan.saturating_add(growth);
214        }
215
216        Some(neuron_idx)
217    }
218
219    /// Age all active memory neurons (vectorized operation)
220    pub fn age_memory_neurons(&mut self, _current_burst: u64) -> Vec<usize> {
221        let n = self.next_available_index;
222        if n == 0 {
223            return Vec::new();
224        }
225
226        let mut died_indices = Vec::new();
227
228        // Age eligible neurons
229        for i in 0..n {
230            if self.is_active[i] && !self.is_longterm_memory[i] && self.lifespan_current[i] > 0 {
231                self.lifespan_current[i] -= 1;
232
233                // Check if neuron died
234                if self.lifespan_current[i] == 0 {
235                    self.is_active[i] = false;
236                    died_indices.push(i);
237                }
238            }
239        }
240
241        // Clean up dead neurons after iteration
242        for &i in &died_indices {
243            self.cleanup_dead_neuron_internal(i);
244        }
245
246        died_indices
247    }
248
249    /// Check for neurons ready for long-term memory conversion
250    pub fn check_longterm_conversion(&mut self, longterm_threshold: u32) -> Vec<usize> {
251        let n = self.next_available_index;
252        if n == 0 {
253            return Vec::new();
254        }
255
256        let mut converted_indices = Vec::new();
257
258        for i in 0..n {
259            if self.is_active[i]
260                && !self.is_longterm_memory[i]
261                && self.lifespan_current[i] >= longterm_threshold
262            {
263                self.is_longterm_memory[i] = true;
264                converted_indices.push(i);
265            }
266        }
267
268        converted_indices
269    }
270
271    /// Check for neurons ready for long-term conversion using per-area thresholds.
272    ///
273    /// Areas without an explicit lifecycle config use `default_threshold`.
274    pub fn check_longterm_conversion_by_area(
275        &mut self,
276        lifecycle_configs: &HashMap<u32, MemoryNeuronLifecycleConfig>,
277        default_threshold: u32,
278    ) -> Vec<usize> {
279        let n = self.next_available_index;
280        if n == 0 {
281            return Vec::new();
282        }
283
284        let area_indices: Vec<(u32, Vec<usize>)> = self
285            .area_neuron_indices
286            .iter()
287            .map(|(&area, indices)| (area, indices.iter().copied().collect()))
288            .collect();
289
290        let mut converted_indices = Vec::new();
291        for (area_id, indices) in area_indices {
292            let threshold = lifecycle_configs
293                .get(&area_id)
294                .map(|config| config.longterm_threshold)
295                .unwrap_or(default_threshold);
296
297            for neuron_idx in indices {
298                if self.is_active[neuron_idx]
299                    && !self.is_longterm_memory[neuron_idx]
300                    && self.lifespan_current[neuron_idx] >= threshold
301                {
302                    self.is_longterm_memory[neuron_idx] = true;
303                    converted_indices.push(neuron_idx);
304                }
305            }
306        }
307
308        converted_indices
309    }
310
311    /// Get all active neuron IDs for a cortical area
312    pub fn get_active_neurons_by_area(&self, cortical_area_id: u32) -> Vec<u32> {
313        if let Some(indices) = self.area_neuron_indices.get(&cortical_area_id) {
314            indices
315                .iter()
316                .filter(|&&idx| self.is_valid_index(idx) && self.is_active[idx])
317                .map(|&idx| self.neuron_ids[idx])
318                .collect()
319        } else {
320            Vec::new()
321        }
322    }
323
324    /// Active short-term neurons (not marked LTM) in the area.
325    pub fn count_short_term_in_area(&self, cortical_area_id: u32) -> usize {
326        self.area_neuron_indices
327            .get(&cortical_area_id)
328            .map(|indices| {
329                indices
330                    .iter()
331                    .filter(|&&idx| {
332                        self.is_valid_index(idx)
333                            && self.is_active[idx]
334                            && !self.is_longterm_memory[idx]
335                    })
336                    .count()
337            })
338            .unwrap_or(0)
339    }
340
341    /// Active long-term memory neurons in the area.
342    pub fn count_long_term_in_area(&self, cortical_area_id: u32) -> usize {
343        self.area_neuron_indices
344            .get(&cortical_area_id)
345            .map(|indices| {
346                indices
347                    .iter()
348                    .filter(|&&idx| {
349                        self.is_valid_index(idx)
350                            && self.is_active[idx]
351                            && self.is_longterm_memory[idx]
352                    })
353                    .count()
354            })
355            .unwrap_or(0)
356    }
357
358    /// Stable-sorted active neuron IDs for the area, with pagination `(page_ids, total_count)`.
359    pub fn paginated_neuron_ids_in_area(
360        &self,
361        cortical_area_id: u32,
362        offset: usize,
363        limit: usize,
364    ) -> (Vec<u32>, usize) {
365        let mut ids = self.get_active_neurons_by_area(cortical_area_id);
366        ids.sort_unstable();
367        let total = ids.len();
368        let page = ids.into_iter().skip(offset).take(limit).collect();
369        (page, total)
370    }
371
372    fn find_neuron_index_by_global_id(&self, neuron_id: u32) -> Option<usize> {
373        (0..self.next_available_index).find(|&i| self.neuron_ids[i] == neuron_id)
374    }
375
376    /// Full detail for an active memory neuron by global neuron id, if present.
377    pub fn get_memory_neuron_detail(&self, neuron_id: u32) -> Option<MemoryNeuronDetail> {
378        let idx = self.find_neuron_index_by_global_id(neuron_id)?;
379        if !self.is_valid_index(idx) || !self.is_active[idx] {
380            return None;
381        }
382        Some(MemoryNeuronDetail {
383            neuron_id: self.neuron_ids[idx],
384            cortical_area_idx: self.cortical_area_ids[idx],
385            pattern_hash: self.index_to_pattern_hash.get(&idx).copied(),
386            is_longterm_memory: self.is_longterm_memory[idx],
387            is_active: self.is_active[idx],
388            lifespan_current: self.lifespan_current[idx],
389            lifespan_initial: self.lifespan_initial[idx],
390            lifespan_growth_rate: self.lifespan_growth_rate[idx],
391            creation_burst: self.creation_burst[idx],
392            last_activation_burst: self.last_activation_burst[idx],
393            activation_count: self.activation_count[idx],
394        })
395    }
396
397    /// Find neuron index by pattern hash
398    pub fn find_neuron_by_pattern(&self, pattern_hash: &u64) -> Option<usize> {
399        self.pattern_hash_to_index
400            .get(pattern_hash)
401            .copied()
402            .filter(|&idx| self.is_valid_index(idx) && self.is_active[idx])
403    }
404
405    /// Get neuron ID at index
406    pub fn get_neuron_id(&self, neuron_idx: usize) -> Option<u32> {
407        if self.is_valid_index(neuron_idx) {
408            Some(self.neuron_ids[neuron_idx])
409        } else {
410            None
411        }
412    }
413
414    /// Get cortical area ID at index
415    pub fn get_cortical_area_id(&self, neuron_idx: usize) -> Option<u32> {
416        if self.is_valid_index(neuron_idx) {
417            Some(self.cortical_area_ids[neuron_idx])
418        } else {
419            None
420        }
421    }
422
423    pub fn get_pattern_hash(&self, neuron_idx: usize) -> Option<u64> {
424        self.index_to_pattern_hash.get(&neuron_idx).copied()
425    }
426
427    /// Get comprehensive statistics
428    pub fn get_stats(&self) -> MemoryNeuronStats {
429        let n = self.next_available_index;
430
431        if n == 0 {
432            return MemoryNeuronStats {
433                total_capacity: self.capacity,
434                ..Default::default()
435            };
436        }
437
438        let active_count = self.is_active[..n].iter().filter(|&&x| x).count();
439        let longterm_count = (0..n)
440            .filter(|&i| self.is_active[i] && self.is_longterm_memory[i])
441            .count();
442        let dead_count = n - active_count;
443
444        // Calculate averages for active neurons
445        let (avg_lifespan, avg_activation_count) = if active_count > 0 {
446            let total_lifespan: u32 = (0..n)
447                .filter(|&i| self.is_active[i])
448                .map(|i| self.lifespan_current[i])
449                .sum();
450            let total_activations: u32 = (0..n)
451                .filter(|&i| self.is_active[i])
452                .map(|i| self.activation_count[i])
453                .sum();
454
455            (
456                total_lifespan as f64 / active_count as f64,
457                total_activations as f64 / active_count as f64,
458            )
459        } else {
460            (0.0, 0.0)
461        };
462
463        // Estimate memory usage
464        let memory_usage = self.capacity * (
465            std::mem::size_of::<u32>() * 4 +  // uint32 arrays
466            std::mem::size_of::<f32>() +       // float32 array
467            std::mem::size_of::<u64>() * 2 +   // uint64 arrays
468            std::mem::size_of::<bool>() * 2    // bool arrays
469        ) + self.pattern_hash_to_index.len() * (8 + 8)  // Pattern hash mappings (u64 + usize)
470          + self.area_neuron_indices.len() * 64; // Area tracking overhead
471
472        MemoryNeuronStats {
473            total_capacity: self.capacity,
474            active_neurons: active_count,
475            longterm_neurons: longterm_count,
476            dead_neurons: dead_count,
477            reusable_indices: self.reusable_indices.len(),
478            memory_usage_bytes: memory_usage,
479            avg_lifespan,
480            avg_activation_count,
481        }
482    }
483
484    /// Get ID manager allocation statistics
485    pub fn get_id_allocation_stats(&self) -> AllocationStats {
486        self.id_manager.get_allocation_stats()
487    }
488
489    /// Get available index (reuse or allocate new)
490    fn get_available_index_internal(&mut self) -> Option<usize> {
491        // Try to reuse a dead neuron index first
492        if let Some(&idx) = self.reusable_indices.iter().next() {
493            self.reusable_indices.remove(&idx);
494            return Some(idx);
495        }
496
497        // Allocate new index if capacity allows
498        if self.next_available_index < self.capacity {
499            let idx = self.next_available_index;
500            self.next_available_index += 1;
501            Some(idx)
502        } else {
503            None
504        }
505    }
506
507    /// Clean up associations for a dead neuron
508    fn cleanup_dead_neuron_internal(&mut self, neuron_idx: usize) {
509        // Deallocate global neuron ID
510        let neuron_id = self.neuron_ids[neuron_idx];
511        self.id_manager.deallocate_memory_neuron_id(neuron_id);
512
513        // Remove pattern association
514        if let Some(pattern_hash) = self.index_to_pattern_hash.remove(&neuron_idx) {
515            self.pattern_hash_to_index.remove(&pattern_hash);
516        }
517
518        // Remove from area tracking
519        let area_id = self.cortical_area_ids[neuron_idx];
520        if let Some(indices) = self.area_neuron_indices.get_mut(&area_id) {
521            indices.remove(&neuron_idx);
522        }
523
524        // Add to reusable indices
525        self.reusable_indices.insert(neuron_idx);
526    }
527
528    /// Check if neuron index is valid
529    fn is_valid_index(&self, neuron_idx: usize) -> bool {
530        neuron_idx < self.next_available_index
531    }
532
533    /// Reset array state (for testing)
534    /// Deactivate all memory neurons in a specific cortical area
535    pub fn reset_cortical_area(&mut self, cortical_area_id: u32) -> usize {
536        let Some(neuron_indices) = self.area_neuron_indices.get(&cortical_area_id) else {
537            return 0;
538        };
539
540        let indices_to_reset: Vec<usize> = neuron_indices.iter().copied().collect();
541        let mut reset_count = 0;
542
543        for neuron_idx in indices_to_reset {
544            if !self.is_valid_index(neuron_idx) {
545                continue;
546            }
547
548            // Deactivate neuron
549            self.is_active[neuron_idx] = false;
550
551            // Remove pattern mapping
552            if let Some(pattern_hash) = self.index_to_pattern_hash.remove(&neuron_idx) {
553                self.pattern_hash_to_index.remove(&pattern_hash);
554            }
555
556            // Clear properties (optional but clean)
557            self.lifespan_current[neuron_idx] = 0;
558            self.activation_count[neuron_idx] = 0;
559
560            // Mark as reusable
561            self.reusable_indices.insert(neuron_idx);
562
563            reset_count += 1;
564        }
565
566        // Remove area tracking
567        self.area_neuron_indices.remove(&cortical_area_id);
568
569        reset_count
570    }
571
572    pub fn reset(&mut self) {
573        self.neuron_ids.fill(0);
574        self.cortical_area_ids.fill(0);
575        self.is_active.fill(false);
576        self.lifespan_current.fill(0);
577        self.lifespan_initial.fill(0);
578        self.lifespan_growth_rate.fill(0.0);
579        self.is_longterm_memory.fill(false);
580        self.creation_burst.fill(0);
581        self.last_activation_burst.fill(0);
582        self.activation_count.fill(0);
583
584        self.pattern_hash_to_index.clear();
585        self.index_to_pattern_hash.clear();
586        self.area_neuron_indices.clear();
587
588        self.next_available_index = 0;
589        self.reusable_indices.clear();
590
591        self.id_manager.reset();
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    #[test]
600    fn test_lifecycle_config_default() {
601        let config = MemoryNeuronLifecycleConfig::default();
602        assert_eq!(config.initial_lifespan, 20);
603        assert_eq!(config.lifespan_growth_rate, 3.0);
604        assert_eq!(config.longterm_threshold, 100);
605        assert_eq!(config.max_reactivations, 1000);
606    }
607
608    #[test]
609    fn test_create_memory_neuron() {
610        let mut array = MemoryNeuronArray::new(1000);
611        let config = MemoryNeuronLifecycleConfig::default();
612
613        let pattern_hash = 0x0101010101010101u64;
614        let neuron_idx = array.create_memory_neuron(pattern_hash, 100, 0, &config);
615
616        assert!(neuron_idx.is_some());
617        let idx = neuron_idx.unwrap();
618        assert!(array.is_active[idx]);
619        assert_eq!(array.cortical_area_ids[idx], 100);
620        assert_eq!(array.lifespan_current[idx], config.initial_lifespan);
621        assert_eq!(array.activation_count[idx], 1);
622        assert_eq!(array.creation_burst[idx], 0);
623    }
624
625    #[test]
626    fn test_create_duplicate_pattern() {
627        let mut array = MemoryNeuronArray::new(1000);
628        let config = MemoryNeuronLifecycleConfig::default();
629
630        let pattern_hash = 0x0101010101010101u64;
631        let idx1 = array
632            .create_memory_neuron(pattern_hash, 100, 0, &config)
633            .unwrap();
634
635        // Creating with same pattern should reactivate existing neuron
636        let idx2 = array
637            .create_memory_neuron(pattern_hash, 100, 1, &config)
638            .unwrap();
639
640        assert_eq!(idx1, idx2);
641        assert_eq!(array.activation_count[idx1], 2); // Should have been reactivated
642    }
643
644    #[test]
645    fn test_multiple_neurons() {
646        let mut array = MemoryNeuronArray::new(1000);
647        let config = MemoryNeuronLifecycleConfig::default();
648
649        let mut neurons = Vec::new();
650        for i in 0..10 {
651            let pattern_hash = i as u64;
652            let idx = array
653                .create_memory_neuron(pattern_hash, 100, 0, &config)
654                .unwrap();
655            neurons.push(idx);
656        }
657
658        assert_eq!(neurons.len(), 10);
659        assert_eq!(array.next_available_index, 10);
660
661        let stats = array.get_stats();
662        assert_eq!(stats.active_neurons, 10);
663    }
664
665    #[test]
666    fn test_reactivate_memory_neuron() {
667        let mut array = MemoryNeuronArray::new(1000);
668        let config = MemoryNeuronLifecycleConfig::default();
669
670        let pattern_hash = 0x0101010101010101u64;
671        let idx = array
672            .create_memory_neuron(pattern_hash, 100, 0, &config)
673            .unwrap();
674
675        let initial_count = array.activation_count[idx];
676        let initial_lifespan = array.lifespan_current[idx];
677
678        assert!(array.reactivate_memory_neuron(idx, 1));
679
680        assert_eq!(array.activation_count[idx], initial_count + 1);
681        assert_eq!(array.last_activation_burst[idx], 1);
682
683        // Lifespan should have grown
684        let expected_lifespan = initial_lifespan + config.lifespan_growth_rate as u32;
685        assert_eq!(array.lifespan_current[idx], expected_lifespan);
686    }
687
688    #[test]
689    fn test_reactivate_invalid_neuron() {
690        let mut array = MemoryNeuronArray::new(1000);
691
692        // Try to reactivate non-existent neuron
693        assert!(!array.reactivate_memory_neuron(0, 1));
694        assert!(!array.reactivate_memory_neuron(999, 1));
695    }
696
697    #[test]
698    fn test_age_memory_neurons() {
699        let mut array = MemoryNeuronArray::new(1000);
700        let config = MemoryNeuronLifecycleConfig {
701            initial_lifespan: 2,
702            ..Default::default()
703        };
704
705        let pattern_hash = 0x0101010101010101u64;
706        let idx = array
707            .create_memory_neuron(pattern_hash, 100, 0, &config)
708            .unwrap();
709
710        // Age once
711        let died = array.age_memory_neurons(1);
712        assert!(died.is_empty());
713        assert_eq!(array.lifespan_current[idx], 1);
714
715        // Age again - should die
716        let died = array.age_memory_neurons(2);
717        assert_eq!(died.len(), 1);
718        assert_eq!(died[0], idx);
719        assert!(!array.is_active[idx]);
720
721        // Pattern should no longer be findable
722        let found = array.find_neuron_by_pattern(&pattern_hash);
723        assert!(found.is_none());
724    }
725
726    #[test]
727    fn test_age_multiple_neurons() {
728        let mut array = MemoryNeuronArray::new(1000);
729        let config = MemoryNeuronLifecycleConfig {
730            initial_lifespan: 5,
731            ..Default::default()
732        };
733
734        let mut neurons = Vec::new();
735        for i in 0..10 {
736            let pattern_hash = i as u64;
737            let idx = array
738                .create_memory_neuron(pattern_hash, 100, 0, &config)
739                .unwrap();
740            neurons.push(idx);
741        }
742
743        // Age 5 times - all should die
744        for burst in 1..=5 {
745            let died = array.age_memory_neurons(burst);
746            if burst < 5 {
747                assert_eq!(died.len(), 0);
748            } else {
749                assert_eq!(died.len(), 10);
750            }
751        }
752
753        let stats = array.get_stats();
754        assert_eq!(stats.active_neurons, 0);
755        assert_eq!(stats.dead_neurons, 10);
756    }
757
758    #[test]
759    fn test_longterm_memory_no_aging() {
760        let mut array = MemoryNeuronArray::new(1000);
761        let config = MemoryNeuronLifecycleConfig {
762            initial_lifespan: 100,
763            ..Default::default()
764        };
765
766        let pattern_hash = 0x0101010101010101u64;
767        let idx = array
768            .create_memory_neuron(pattern_hash, 100, 0, &config)
769            .unwrap();
770
771        // Convert to long-term memory
772        let converted = array.check_longterm_conversion(100);
773        assert_eq!(converted.len(), 1);
774        assert!(array.is_longterm_memory[idx]);
775
776        let initial_lifespan = array.lifespan_current[idx];
777
778        // Age many times - should not affect long-term memory
779        for burst in 1..=50 {
780            array.age_memory_neurons(burst);
781        }
782
783        assert!(array.is_active[idx]);
784        assert_eq!(array.lifespan_current[idx], initial_lifespan); // Should not change
785    }
786
787    #[test]
788    fn test_longterm_conversion() {
789        let mut array = MemoryNeuronArray::new(1000);
790        let config = MemoryNeuronLifecycleConfig {
791            initial_lifespan: 100,
792            ..Default::default()
793        };
794
795        let pattern_hash = 0x0101010101010101u64;
796        let idx = array
797            .create_memory_neuron(pattern_hash, 100, 0, &config)
798            .unwrap();
799
800        let converted = array.check_longterm_conversion(100);
801        assert_eq!(converted.len(), 1);
802        assert_eq!(converted[0], idx);
803        assert!(array.is_longterm_memory[idx]);
804
805        // Second check should not convert again
806        let converted2 = array.check_longterm_conversion(100);
807        assert_eq!(converted2.len(), 0);
808    }
809
810    #[test]
811    fn test_longterm_conversion_threshold() {
812        let mut array = MemoryNeuronArray::new(1000);
813        let config = MemoryNeuronLifecycleConfig {
814            initial_lifespan: 50,
815            ..Default::default()
816        };
817
818        let pattern_hash = 0x0101010101010101u64;
819        let idx = array
820            .create_memory_neuron(pattern_hash, 100, 0, &config)
821            .unwrap();
822
823        // Should not convert below threshold
824        let converted = array.check_longterm_conversion(100);
825        assert_eq!(converted.len(), 0);
826        assert!(!array.is_longterm_memory[idx]);
827
828        // Grow lifespan through reactivations
829        for burst in 1..=20 {
830            array.reactivate_memory_neuron(idx, burst);
831        }
832
833        // Now should convert
834        let converted = array.check_longterm_conversion(100);
835        assert_eq!(converted.len(), 1);
836        assert!(array.is_longterm_memory[idx]);
837    }
838
839    #[test]
840    fn test_find_neuron_by_pattern() {
841        let mut array = MemoryNeuronArray::new(1000);
842        let config = MemoryNeuronLifecycleConfig::default();
843
844        let pattern_hash = 0x0101010101010101u64;
845        let idx = array
846            .create_memory_neuron(pattern_hash, 100, 0, &config)
847            .unwrap();
848
849        let found = array.find_neuron_by_pattern(&pattern_hash);
850        assert_eq!(found, Some(idx));
851
852        // Different pattern should not be found
853        let pattern_hash2 = 0x0202020202020202u64;
854        let found2 = array.find_neuron_by_pattern(&pattern_hash2);
855        assert_eq!(found2, None);
856    }
857
858    #[test]
859    fn test_get_active_neurons_by_area() {
860        let mut array = MemoryNeuronArray::new(1000);
861        let config = MemoryNeuronLifecycleConfig::default();
862
863        // Create neurons in different areas
864        for area in [100, 200] {
865            for i in 0..5 {
866                let pattern_hash = ((area as u64) << 32) | (i as u64);
867                array.create_memory_neuron(pattern_hash, area, 0, &config);
868            }
869        }
870
871        let area100_neurons = array.get_active_neurons_by_area(100);
872        let area200_neurons = array.get_active_neurons_by_area(200);
873
874        assert_eq!(area100_neurons.len(), 5);
875        assert_eq!(area200_neurons.len(), 5);
876
877        // Non-existent area
878        let area999_neurons = array.get_active_neurons_by_area(999);
879        assert_eq!(area999_neurons.len(), 0);
880    }
881
882    #[test]
883    fn test_get_neuron_id() {
884        let mut array = MemoryNeuronArray::new(1000);
885        let config = MemoryNeuronLifecycleConfig::default();
886
887        let pattern_hash = 0x0101010101010101u64;
888        let idx = array
889            .create_memory_neuron(pattern_hash, 100, 0, &config)
890            .unwrap();
891
892        let neuron_id = array.get_neuron_id(idx);
893        assert!(neuron_id.is_some());
894
895        // Invalid index
896        let invalid_id = array.get_neuron_id(999);
897        assert!(invalid_id.is_none());
898    }
899
900    #[test]
901    fn test_index_reuse() {
902        let mut array = MemoryNeuronArray::new(1000);
903        let config = MemoryNeuronLifecycleConfig {
904            initial_lifespan: 1,
905            ..Default::default()
906        };
907
908        let pattern_hash = 0x0101010101010101u64;
909        let idx1 = array
910            .create_memory_neuron(pattern_hash, 100, 0, &config)
911            .unwrap();
912        assert_eq!(idx1, 0);
913
914        // Let it die
915        array.age_memory_neurons(1);
916        assert!(!array.is_active[idx1]);
917
918        // Create new neuron - should reuse index
919        let pattern_hash2 = 0x0202020202020202u64;
920        let idx2 = array
921            .create_memory_neuron(pattern_hash2, 100, 2, &config)
922            .unwrap();
923        assert_eq!(idx2, 0); // Should reuse index 0
924        assert_eq!(array.next_available_index, 1); // Should not have advanced
925    }
926
927    #[test]
928    fn test_get_stats() {
929        let mut array = MemoryNeuronArray::new(1000);
930        let config = MemoryNeuronLifecycleConfig::default();
931
932        // Create some neurons
933        for i in 0..10 {
934            let pattern_hash = i as u64;
935            array.create_memory_neuron(pattern_hash, 100, 0, &config);
936        }
937
938        let stats = array.get_stats();
939        assert_eq!(stats.total_capacity, 1000);
940        assert_eq!(stats.active_neurons, 10);
941        assert_eq!(stats.longterm_neurons, 0);
942        assert_eq!(stats.dead_neurons, 0);
943        assert!(stats.avg_lifespan > 0.0);
944        assert!(stats.avg_activation_count >= 1.0);
945        assert!(stats.memory_usage_bytes > 0);
946    }
947
948    #[test]
949    fn test_capacity_exhaustion() {
950        let mut array = MemoryNeuronArray::new(5);
951        let config = MemoryNeuronLifecycleConfig::default();
952
953        // Create neurons up to capacity
954        for i in 0..5 {
955            let pattern_hash = i as u64;
956            let idx = array.create_memory_neuron(pattern_hash, 100, 0, &config);
957            assert!(idx.is_some());
958        }
959
960        // Try to create beyond capacity
961        let pattern_hash = 0x6363636363636363u64;
962        let idx = array.create_memory_neuron(pattern_hash, 100, 0, &config);
963        assert!(idx.is_none());
964    }
965
966    #[test]
967    fn test_reset_cortical_area() {
968        let mut array = MemoryNeuronArray::new(1000);
969        let config = MemoryNeuronLifecycleConfig::default();
970
971        // Create neurons in area 5
972        let pattern1 = 0x0101010101010101u64;
973        let pattern2 = 0x0202020202020202u64;
974        array.create_memory_neuron(pattern1, 5, 0, &config);
975        array.create_memory_neuron(pattern2, 5, 0, &config);
976
977        // Create neurons in area 6
978        let pattern3 = 0x0303030303030303u64;
979        array.create_memory_neuron(pattern3, 6, 0, &config);
980
981        // Verify initial state
982        assert_eq!(array.get_active_neurons_by_area(5).len(), 2);
983        assert_eq!(array.get_active_neurons_by_area(6).len(), 1);
984
985        // Reset area 5
986        let reset_count = array.reset_cortical_area(5);
987        assert_eq!(reset_count, 2);
988
989        // Verify area 5 is empty
990        assert_eq!(array.get_active_neurons_by_area(5).len(), 0);
991
992        // Verify area 6 is unchanged
993        assert_eq!(array.get_active_neurons_by_area(6).len(), 1);
994
995        // Verify pattern hashes are cleared for area 5
996        assert!(!array.pattern_hash_to_index.contains_key(&pattern1));
997        assert!(!array.pattern_hash_to_index.contains_key(&pattern2));
998
999        // Verify area 6 pattern still exists
1000        assert!(array.pattern_hash_to_index.contains_key(&pattern3));
1001    }
1002
1003    #[test]
1004    fn test_reset() {
1005        let mut array = MemoryNeuronArray::new(1000);
1006        let config = MemoryNeuronLifecycleConfig::default();
1007
1008        // Create some neurons
1009        for i in 0..5 {
1010            let pattern_hash = i as u64;
1011            array.create_memory_neuron(pattern_hash, 100, 0, &config);
1012        }
1013
1014        assert_eq!(array.next_available_index, 5);
1015
1016        array.reset();
1017
1018        assert_eq!(array.next_available_index, 0);
1019        let stats = array.get_stats();
1020        assert_eq!(stats.active_neurons, 0);
1021    }
1022
1023    #[test]
1024    fn test_lifespan_growth_on_reactivation() {
1025        let mut array = MemoryNeuronArray::new(1000);
1026        let config = MemoryNeuronLifecycleConfig {
1027            initial_lifespan: 10,
1028            lifespan_growth_rate: 5.0,
1029            longterm_threshold: 100,
1030            max_reactivations: 1000,
1031        };
1032
1033        let pattern_hash = 0x0101010101010101u64;
1034        let idx = array
1035            .create_memory_neuron(pattern_hash, 100, 0, &config)
1036            .unwrap();
1037
1038        assert_eq!(array.lifespan_current[idx], 10);
1039
1040        array.reactivate_memory_neuron(idx, 1);
1041        assert_eq!(array.lifespan_current[idx], 15);
1042
1043        array.reactivate_memory_neuron(idx, 2);
1044        assert_eq!(array.lifespan_current[idx], 20);
1045    }
1046
1047    #[test]
1048    fn test_st_ltm_counts_and_pagination_and_detail() {
1049        let mut array = MemoryNeuronArray::new(1000);
1050        let config = MemoryNeuronLifecycleConfig::default();
1051
1052        for i in 0..5 {
1053            array
1054                .create_memory_neuron(i as u64, 7, 0, &config)
1055                .expect("create");
1056        }
1057        assert_eq!(array.count_short_term_in_area(7), 5);
1058        assert_eq!(array.count_long_term_in_area(7), 0);
1059
1060        array.is_longterm_memory[0] = true;
1061        assert_eq!(array.count_short_term_in_area(7), 4);
1062        assert_eq!(array.count_long_term_in_area(7), 1);
1063
1064        let (page0, total) = array.paginated_neuron_ids_in_area(7, 0, 2);
1065        assert_eq!(total, 5);
1066        assert_eq!(page0.len(), 2);
1067
1068        let nid = page0[0];
1069        let detail = array.get_memory_neuron_detail(nid).expect("detail");
1070        assert_eq!(detail.neuron_id, nid);
1071        assert_eq!(detail.cortical_area_idx, 7);
1072        assert!(detail.pattern_hash.is_some());
1073    }
1074}