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