1use once_cell::sync::Lazy;
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use std::collections::{HashMap, HashSet};
35use std::hash::Hasher;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::sync::{Arc, Mutex};
38use tracing::{debug, error, info, trace, warn};
39use xxhash_rust::xxh64::Xxh64;
40
41pub type BrainRegionIoRegistry = HashMap<String, (Vec<String>, Vec<String>)>;
43
44use crate::models::{BrainRegion, BrainRegionHierarchy, CorticalArea, CorticalAreaDimensions};
45use crate::types::{BduError, BduResult};
46use feagi_npu_neural::synapse::SYNAPSE_EDGE_ASSOCIATIVE_MEMORY;
47use feagi_npu_neural::types::NeuronId;
48use feagi_structures::genomic::cortical_area::{
49 CoreCorticalType, CorticalAreaType, CorticalID, CustomCorticalType,
50};
51use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
52
53use feagi_state_manager::StateManager;
56
57const DATA_HASH_SEED: u64 = 0;
58const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
60
61static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
66 Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ConnectomeConfig {
71 pub max_neurons: usize,
73
74 pub max_synapses: usize,
76
77 pub backend: String,
79}
80
81impl Default for ConnectomeConfig {
82 fn default() -> Self {
83 Self {
84 max_neurons: 10_000_000,
85 max_synapses: 100_000_000,
86 backend: "cpu".to_string(),
87 }
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct MemoryTwinDiagnostic {
94 pub src_cortical_area: String,
95 pub dst_cortical_area: String,
96 pub src_cortical_type: String,
97 pub dst_cortical_type: String,
98 pub mapping_exists: bool,
99 pub episodic_rule_count: usize,
100 pub twin_expected: bool,
101 pub twin_present: bool,
102 pub twin_cortical_area: Option<String>,
103 pub reason: Option<String>,
104 pub src_parent_region_id: Option<String>,
105 pub dst_parent_region_id: Option<String>,
106 pub twin_parent_region_id: Option<String>,
107 pub twin_parent_matches_src_parent: Option<bool>,
108}
109
110pub struct ConnectomeManager {
132 cortical_areas: HashMap<CorticalID, CorticalArea>,
134
135 cortical_id_to_idx: HashMap<CorticalID, u32>,
137
138 cortical_idx_to_id: HashMap<u32, CorticalID>,
140
141 next_cortical_idx: u32,
143
144 brain_regions: BrainRegionHierarchy,
146
147 classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
149
150 morphology_registry: feagi_evolutionary::MorphologyRegistry,
152
153 config: ConnectomeConfig,
155
156 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
162
163 #[cfg(feature = "plasticity")]
165 plasticity_executor:
166 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
167
168 cached_neuron_count: Arc<AtomicUsize>,
171
172 cached_synapse_count: Arc<AtomicUsize>,
175
176 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
179
180 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
183
184 initialized: bool,
186
187 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
189}
190
191type NeuronData = (
193 u32,
194 u32,
195 u32,
196 f32,
197 f32,
198 f32,
199 f32,
200 i32,
201 u16,
202 f32,
203 u16,
204 u16,
205 bool,
206);
207
208impl ConnectomeManager {
209 fn get_mapping_rules_for_destination<'a>(
210 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
211 dst_area_id: &CorticalID,
212 ) -> Option<&'a Vec<serde_json::Value>> {
213 if let Some(rules) = mapping_dst
214 .get(&dst_area_id.as_base_64())
215 .and_then(|value| value.as_array())
216 {
217 return Some(rules);
218 }
219
220 for (raw_dst_key, rules_value) in mapping_dst {
223 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
224 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
225 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
226 continue;
227 }
228 if let Some(rules) = rules_value.as_array() {
229 return Some(rules);
230 }
231 }
232
233 None
234 }
235
236 fn new() -> Self {
238 Self {
239 cortical_areas: HashMap::new(),
240 cortical_id_to_idx: HashMap::new(),
241 cortical_idx_to_id: HashMap::new(),
242 next_cortical_idx: 7, brain_regions: BrainRegionHierarchy::new(),
245 classifiers: HashMap::new(),
246 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
247 config: ConnectomeConfig::default(),
248 npu: None,
249 #[cfg(feature = "plasticity")]
250 plasticity_executor: None,
251 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
252 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
253 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
254 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
255 initialized: false,
256 last_fatigue_calculation: Arc::new(Mutex::new(
257 std::time::Instant::now() - std::time::Duration::from_secs(10),
258 )), }
260 }
261
262 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
279 Arc::clone(&*INSTANCE)
282 }
283
284 pub fn new_for_testing() -> Self {
312 Self {
313 cortical_areas: HashMap::new(),
314 cortical_id_to_idx: HashMap::new(),
315 cortical_idx_to_id: HashMap::new(),
316 next_cortical_idx: 0,
317 brain_regions: BrainRegionHierarchy::new(),
318 classifiers: HashMap::new(),
319 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
320 config: ConnectomeConfig::default(),
321 npu: None,
322 #[cfg(feature = "plasticity")]
323 plasticity_executor: None,
324 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
325 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
326 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
327 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
328 initialized: false,
329 last_fatigue_calculation: Arc::new(Mutex::new(
330 std::time::Instant::now() - std::time::Duration::from_secs(10),
331 )),
332 }
333 }
334
335 pub fn new_for_testing_with_npu(
351 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
352 ) -> Self {
353 Self {
354 cortical_areas: HashMap::new(),
355 cortical_id_to_idx: HashMap::new(),
356 cortical_idx_to_id: HashMap::new(),
357 next_cortical_idx: 7,
358 brain_regions: BrainRegionHierarchy::new(),
359 classifiers: HashMap::new(),
360 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
361 config: ConnectomeConfig::default(),
362 npu: Some(npu),
363 #[cfg(feature = "plasticity")]
364 plasticity_executor: None,
365 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
366 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
367 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
368 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
369 initialized: false,
370 last_fatigue_calculation: Arc::new(Mutex::new(
371 std::time::Instant::now() - std::time::Duration::from_secs(10),
372 )),
373 }
374 }
375
376 pub fn setup_core_morphologies_for_testing(&mut self) {
386 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
387 }
388
389 #[cfg(test)]
398 pub fn reset_for_testing() {
399 let mut instance = INSTANCE.write();
400 *instance = Self::new();
401 }
402
403 fn update_state_hashes(
409 &self,
410 brain_regions: Option<u64>,
411 cortical_areas: Option<u64>,
412 brain_geometry: Option<u64>,
413 morphologies: Option<u64>,
414 cortical_mappings: Option<u64>,
415 ) {
416 let state_manager = StateManager::instance();
417 let state_manager = state_manager.read();
418 if let Some(value) = brain_regions {
419 state_manager.set_brain_regions_hash(value);
420 }
421 if let Some(value) = cortical_areas {
422 state_manager.set_cortical_areas_hash(value);
423 }
424 if let Some(value) = brain_geometry {
425 state_manager.set_brain_geometry_hash(value);
426 }
427 if let Some(value) = morphologies {
428 state_manager.set_morphologies_hash(value);
429 }
430 if let Some(value) = cortical_mappings {
431 state_manager.set_cortical_mappings_hash(value);
432 }
433 }
434
435 fn refresh_brain_regions_hash(&self) {
437 let hash = self.compute_brain_regions_hash();
438 self.update_state_hashes(Some(hash), None, None, None, None);
439 }
440
441 #[allow(dead_code)]
442 fn refresh_cortical_areas_hash(&self) {
444 let hash = self.compute_cortical_areas_hash();
445 self.update_state_hashes(None, Some(hash), None, None, None);
446 }
447
448 #[allow(dead_code)]
449 fn refresh_brain_geometry_hash(&self) {
451 let hash = self.compute_brain_geometry_hash();
452 self.update_state_hashes(None, None, Some(hash), None, None);
453 }
454
455 fn refresh_morphologies_hash(&self) {
457 let hash = self.compute_morphologies_hash();
458 self.update_state_hashes(None, None, None, Some(hash), None);
459 }
460
461 pub fn refresh_cortical_mappings_hash(&self) {
463 let hash = self.compute_cortical_mappings_hash();
464 self.update_state_hashes(None, None, None, None, Some(hash));
465 }
466
467 pub fn refresh_classifiers_hash(&self) {
469 let hash = self.compute_classifiers_hash();
470 let state_manager = StateManager::instance();
471 let state_manager = state_manager.read();
472 state_manager.set_classifiers_hash(hash);
473 }
474
475 pub fn refresh_all_connectome_hashes(&self) {
480 self.update_state_hashes(
481 Some(self.compute_brain_regions_hash()),
482 Some(self.compute_cortical_areas_hash()),
483 Some(self.compute_brain_geometry_hash()),
484 Some(self.compute_morphologies_hash()),
485 Some(self.compute_cortical_mappings_hash()),
486 );
487 self.refresh_classifiers_hash();
488 }
489
490 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
492 let cortical_hash = if properties_changed {
493 Some(self.compute_cortical_areas_hash())
494 } else {
495 None
496 };
497 let geometry_hash = if geometry_changed {
498 Some(self.compute_brain_geometry_hash())
499 } else {
500 None
501 };
502 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
503 }
504
505 fn compute_brain_regions_hash(&self) -> u64 {
507 let mut hasher = Xxh64::new(DATA_HASH_SEED);
508 let mut region_ids: Vec<String> = self
509 .brain_regions
510 .get_all_region_ids()
511 .into_iter()
512 .cloned()
513 .collect();
514 region_ids.sort();
515
516 for region_id in region_ids {
517 let Some(region) = self.brain_regions.get_region(®ion_id) else {
518 continue;
519 };
520 Self::hash_str(&mut hasher, ®ion_id);
521 Self::hash_str(&mut hasher, ®ion.name);
522 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
523 let parent_id = self.brain_regions.get_parent(®ion_id);
524 match parent_id {
525 Some(parent) => Self::hash_str(&mut hasher, parent),
526 None => Self::hash_str(&mut hasher, "null"),
527 }
528
529 let mut cortical_ids: Vec<String> = region
530 .cortical_areas
531 .iter()
532 .map(|id| id.as_base_64())
533 .collect();
534 cortical_ids.sort();
535 for cortical_id in cortical_ids {
536 Self::hash_str(&mut hasher, &cortical_id);
537 }
538
539 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
540 }
541
542 hasher.finish() & HASH_SAFE_MASK
543 }
544
545 fn compute_cortical_areas_hash(&self) -> u64 {
547 let mut hasher = Xxh64::new(DATA_HASH_SEED);
548 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
549 areas.sort_by_key(|area| area.cortical_id.as_base_64());
550
551 for area in areas {
552 let cortical_id = area.cortical_id.as_base_64();
553 Self::hash_str(&mut hasher, &cortical_id);
554 hasher.write_u32(area.cortical_idx);
555 Self::hash_str(&mut hasher, &area.name);
556 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
557
558 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
559 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
560 }
561
562 hasher.finish() & HASH_SAFE_MASK
563 }
564
565 fn compute_brain_geometry_hash(&self) -> u64 {
567 let mut hasher = Xxh64::new(DATA_HASH_SEED);
568 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
569 areas.sort_by_key(|area| area.cortical_id.as_base_64());
570
571 for area in areas {
572 let cortical_id = area.cortical_id.as_base_64();
573 Self::hash_str(&mut hasher, &cortical_id);
574
575 Self::hash_i32(&mut hasher, area.position.x);
576 Self::hash_i32(&mut hasher, area.position.y);
577 Self::hash_i32(&mut hasher, area.position.z);
578
579 Self::hash_u32(&mut hasher, area.dimensions.width);
580 Self::hash_u32(&mut hasher, area.dimensions.height);
581 Self::hash_u32(&mut hasher, area.dimensions.depth);
582
583 let coord_2d = area
584 .properties
585 .get("coordinate_2d")
586 .or_else(|| area.properties.get("coordinates_2d"));
587 match coord_2d {
588 Some(value) => Self::hash_json_value(&mut hasher, value),
589 None => Self::hash_str(&mut hasher, "null"),
590 }
591 }
592
593 hasher.finish() & HASH_SAFE_MASK
594 }
595
596 fn compute_morphologies_hash(&self) -> u64 {
598 let mut hasher = Xxh64::new(DATA_HASH_SEED);
599 let mut morphology_ids = self.morphology_registry.morphology_ids();
600 morphology_ids.sort();
601
602 for morphology_id in morphology_ids {
603 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
604 Self::hash_str(&mut hasher, &morphology_id);
605 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
606 Self::hash_str(&mut hasher, &morphology.class);
607 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
608 Self::hash_json_value(&mut hasher, &value);
609 }
610 }
611 }
612
613 hasher.finish() & HASH_SAFE_MASK
614 }
615
616 fn compute_cortical_mappings_hash(&self) -> u64 {
618 let mut hasher = Xxh64::new(DATA_HASH_SEED);
619 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
620 areas.sort_by_key(|area| area.cortical_id.as_base_64());
621
622 for area in areas {
623 let cortical_id = area.cortical_id.as_base_64();
624 Self::hash_str(&mut hasher, &cortical_id);
625 if let Some(serde_json::Value::Object(map)) =
626 area.properties.get("cortical_mapping_dst")
627 {
628 let mut dest_ids: Vec<&String> = map.keys().collect();
629 dest_ids.sort();
630 for dest_id in dest_ids {
631 Self::hash_str(&mut hasher, dest_id);
632 if let Some(value) = map.get(dest_id) {
633 Self::hash_json_value(&mut hasher, value);
634 }
635 }
636 } else {
637 Self::hash_str(&mut hasher, "null");
638 }
639 }
640
641 hasher.finish() & HASH_SAFE_MASK
642 }
643
644 fn compute_classifiers_hash(&self) -> u64 {
646 let mut hasher = Xxh64::new(DATA_HASH_SEED);
647 let mut classifier_ids: Vec<String> = self.classifiers.keys().cloned().collect();
648 classifier_ids.sort();
649 for classifier_id in classifier_ids {
650 let Some(classifier) = self.classifiers.get(&classifier_id) else {
651 continue;
652 };
653 Self::hash_str(&mut hasher, &classifier.classifier_id);
654 Self::hash_str(&mut hasher, &classifier.name);
655 Self::hash_str(&mut hasher, &classifier.parent_region_id);
656 Self::hash_i32(&mut hasher, classifier.coordinates_3d[0]);
657 Self::hash_i32(&mut hasher, classifier.coordinates_3d[1]);
658 Self::hash_i32(&mut hasher, classifier.coordinates_3d[2]);
659 Self::hash_str(&mut hasher, classifier.training_mode.as_str());
660 match &classifier.kernel_area_id {
661 Some(area_id) => Self::hash_str(&mut hasher, area_id),
662 None => Self::hash_str(&mut hasher, "null"),
663 }
664 match &classifier.class_area_id {
665 Some(area_id) => Self::hash_str(&mut hasher, area_id),
666 None => Self::hash_str(&mut hasher, "null"),
667 }
668 match &classifier.mask_area_id {
669 Some(area_id) => Self::hash_str(&mut hasher, area_id),
670 None => Self::hash_str(&mut hasher, "null"),
671 }
672 match classifier.kernel_size {
673 Some([x, y, z]) => {
674 Self::hash_u32(&mut hasher, x);
675 Self::hash_u32(&mut hasher, y);
676 Self::hash_u32(&mut hasher, z);
677 }
678 None => Self::hash_str(&mut hasher, "null"),
679 }
680 for field in &classifier.fields {
681 Self::hash_str(&mut hasher, &field.field_area_id);
682 Self::hash_str(&mut hasher, &field.scan_twin_id);
683 }
684 Self::hash_str(&mut hasher, &classifier.kernel_memory_id);
685 Self::hash_str(&mut hasher, &classifier.class_memory_id);
686 Self::hash_properties_filtered(&mut hasher, &classifier.properties, &[]);
687 }
688 hasher.finish() & HASH_SAFE_MASK
689 }
690
691 fn hash_str(hasher: &mut Xxh64, value: &str) {
693 hasher.write(value.as_bytes());
694 hasher.write_u8(0);
695 }
696
697 fn hash_i32(hasher: &mut Xxh64, value: i32) {
699 hasher.write(&value.to_le_bytes());
700 }
701
702 fn hash_u32(hasher: &mut Xxh64, value: u32) {
704 hasher.write(&value.to_le_bytes());
705 }
706
707 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
709 match value {
710 serde_json::Value::Null => {
711 hasher.write_u8(0);
712 }
713 serde_json::Value::Bool(val) => {
714 hasher.write_u8(1);
715 hasher.write_u8(*val as u8);
716 }
717 serde_json::Value::Number(num) => {
718 hasher.write_u8(2);
719 Self::hash_str(hasher, &num.to_string());
720 }
721 serde_json::Value::String(val) => {
722 hasher.write_u8(3);
723 Self::hash_str(hasher, val);
724 }
725 serde_json::Value::Array(items) => {
726 hasher.write_u8(4);
727 for item in items {
728 Self::hash_json_value(hasher, item);
729 }
730 }
731 serde_json::Value::Object(map) => {
732 hasher.write_u8(5);
733 let mut keys: Vec<&String> = map.keys().collect();
734 keys.sort();
735 for key in keys {
736 Self::hash_str(hasher, key);
737 if let Some(val) = map.get(key) {
738 Self::hash_json_value(hasher, val);
739 }
740 }
741 }
742 }
743 }
744
745 fn hash_properties_filtered(
747 hasher: &mut Xxh64,
748 properties: &HashMap<String, serde_json::Value>,
749 excluded_keys: &[&str],
750 ) {
751 let mut keys: Vec<&String> = properties.keys().collect();
752 keys.sort();
753 for key in keys {
754 if excluded_keys.contains(&key.as_str()) {
755 continue;
756 }
757 Self::hash_str(hasher, key);
758 if let Some(value) = properties.get(key) {
759 Self::hash_json_value(hasher, value);
760 }
761 }
762 }
763
764 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
785 if self.cortical_areas.contains_key(&area.cortical_id) {
787 return Err(BduError::InvalidArea(format!(
788 "Cortical area {} already exists",
789 area.cortical_id
790 )));
791 }
792
793 use feagi_structures::genomic::cortical_area::CoreCorticalType;
796
797 let death_id = CoreCorticalType::Death.to_cortical_id();
798 let power_id = CoreCorticalType::Power.to_cortical_id();
799 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
800 let pain_id = CoreCorticalType::Pain.to_cortical_id();
801 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
802 let fear_id = CoreCorticalType::Fear.to_cortical_id();
803 let hope_id = CoreCorticalType::Hope.to_cortical_id();
804
805 let is_death_area = area.cortical_id == death_id;
806 let is_power_area = area.cortical_id == power_id;
807 let is_fatigue_area = area.cortical_id == fatigue_id;
808 let is_pain_area = area.cortical_id == pain_id;
809 let is_pleasure_area = area.cortical_id == pleasure_id;
810 let is_fear_area = area.cortical_id == fear_id;
811 let is_hope_area = area.cortical_id == hope_id;
812
813 if is_death_area {
814 trace!(
815 target: "feagi-bdu",
816 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
817 area.cortical_id
818 );
819 area.cortical_idx = 0;
820 } else if is_power_area {
821 trace!(
822 target: "feagi-bdu",
823 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
824 area.cortical_id
825 );
826 area.cortical_idx = 1;
827 } else if is_fatigue_area {
828 trace!(
829 target: "feagi-bdu",
830 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
831 area.cortical_id
832 );
833 area.cortical_idx = 2;
834 } else if is_pain_area {
835 trace!(
836 target: "feagi-bdu",
837 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
838 area.cortical_id
839 );
840 area.cortical_idx = 3;
841 } else if is_pleasure_area {
842 trace!(
843 target: "feagi-bdu",
844 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
845 area.cortical_id
846 );
847 area.cortical_idx = 4;
848 } else if is_fear_area {
849 trace!(
850 target: "feagi-bdu",
851 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
852 area.cortical_id
853 );
854 area.cortical_idx = 5;
855 } else if is_hope_area {
856 trace!(
857 target: "feagi-bdu",
858 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
859 area.cortical_id
860 );
861 area.cortical_idx = 6;
862 } else {
863 if area.cortical_idx == 0 {
865 area.cortical_idx = self.next_cortical_idx;
866 self.next_cortical_idx += 1;
867 trace!(
868 target: "feagi-bdu",
869 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
870 area.cortical_idx,
871 area.cortical_id.as_base_64()
872 );
873 } else {
874 if area.cortical_idx <= 6 {
876 warn!(
877 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
878 area.cortical_id, area.cortical_idx);
879 area.cortical_idx = self.next_cortical_idx;
880 self.next_cortical_idx += 1;
881 info!(
882 " Reassigned '{}' to cortical_idx={}",
883 area.cortical_id, area.cortical_idx
884 );
885 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
886 return Err(BduError::InvalidArea(format!(
887 "Cortical index {} is already in use",
888 area.cortical_idx
889 )));
890 }
891
892 if area.cortical_idx >= self.next_cortical_idx {
894 self.next_cortical_idx = area.cortical_idx + 1;
895 }
896 }
897 }
898
899 let cortical_id = area.cortical_id;
900 let cortical_idx = area.cortical_idx;
901
902 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
904 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
905
906 area.properties
908 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
909
910 let parent_region_id = area
919 .properties
920 .get("parent_region_id")
921 .and_then(|v| v.as_str())
922 .map(|s| s.to_string());
923
924 self.cortical_areas.insert(cortical_id, area);
926
927 if let Some(region_id) = parent_region_id {
929 let region = self
930 .brain_regions
931 .get_region_mut(®ion_id)
932 .ok_or_else(|| {
933 BduError::InvalidArea(format!(
934 "Unknown parent_region_id '{}' for cortical area {}",
935 region_id,
936 cortical_id.as_base_64()
937 ))
938 })?;
939 region.add_area(cortical_id);
940 }
941
942 {
945 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
946 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
947 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
948 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
949 }
950 let state_manager = StateManager::instance();
952 let state_manager = state_manager.read();
953 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
954
955 if let Some(ref npu) = self.npu {
959 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
960 if let Ok(mut npu_lock) = npu.lock() {
961 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
962 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
963 trace!(
964 target: "feagi-bdu",
965 "Registered cortical area idx={} -> '{}' in NPU",
966 cortical_idx,
967 cortical_id.as_base_64()
968 );
969 }
970 }
971
972 self.sync_cortical_area_flags_to_npu()?;
974
975 self.initialized = true;
976
977 self.refresh_cortical_area_hashes(true, true);
978 self.refresh_brain_regions_hash();
979
980 Ok(cortical_idx)
981 }
982
983 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
998 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
999 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
1000 })?;
1001
1002 #[cfg(feature = "plasticity")]
1003 if let Some(executor) = self.plasticity_executor.as_ref() {
1004 let exec = match executor.lock() {
1005 Ok(exec) => exec,
1006 Err(_) => {
1007 self.cortical_areas.insert(*cortical_id, area);
1008 return Err(BduError::Internal(format!(
1009 "Failed to lock PlasticityExecutor while removing cortical area {}",
1010 cortical_id
1011 )));
1012 }
1013 };
1014 exec.unregister_memory_area(area.cortical_idx);
1015 }
1016
1017 self.cortical_id_to_idx.remove(cortical_id);
1019 self.cortical_idx_to_id.remove(&area.cortical_idx);
1020
1021 self.refresh_cortical_area_hashes(true, true);
1022 Ok(())
1023 }
1024
1025 pub fn rename_cortical_area_id(
1029 &mut self,
1030 old_id: &CorticalID,
1031 new_id: CorticalID,
1032 new_cortical_type: CorticalAreaType,
1033 ) -> BduResult<()> {
1034 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
1035 }
1036
1037 pub fn rename_cortical_area_id_with_options(
1039 &mut self,
1040 old_id: &CorticalID,
1041 new_id: CorticalID,
1042 new_cortical_type: CorticalAreaType,
1043 update_npu_registry: bool,
1044 ) -> BduResult<()> {
1045 if !self.cortical_areas.contains_key(old_id) {
1046 return Err(BduError::InvalidArea(format!(
1047 "Cortical area {} does not exist",
1048 old_id
1049 )));
1050 }
1051 if self.cortical_areas.contains_key(&new_id) {
1052 return Err(BduError::InvalidArea(format!(
1053 "Cortical area {} already exists",
1054 new_id
1055 )));
1056 }
1057
1058 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
1059 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
1060 })?;
1061 let cortical_idx = area.cortical_idx;
1062 area.cortical_id = new_id;
1063 area.cortical_type = new_cortical_type;
1064
1065 self.cortical_areas.insert(new_id, area);
1066 self.cortical_id_to_idx.remove(old_id);
1067 self.cortical_id_to_idx.insert(new_id, cortical_idx);
1068 self.cortical_idx_to_id.insert(cortical_idx, new_id);
1069
1070 {
1072 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
1073 if let Some(value) = neuron_cache.remove(old_id) {
1074 neuron_cache.insert(new_id, value);
1075 }
1076 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
1077 if let Some(value) = synapse_cache.remove(old_id) {
1078 synapse_cache.insert(new_id, value);
1079 }
1080 }
1081
1082 self.brain_regions.rename_cortical_area_id(old_id, new_id);
1084
1085 let old_id_str = old_id.as_base_64();
1087 let new_id_str = new_id.as_base_64();
1088 for area in self.cortical_areas.values_mut() {
1089 if let Some(mapping) = area
1090 .properties
1091 .get_mut("cortical_mapping_dst")
1092 .and_then(|v| v.as_object_mut())
1093 {
1094 if let Some(value) = mapping.remove(&old_id_str) {
1095 mapping.insert(new_id_str.clone(), value);
1096 }
1097 }
1098 }
1099
1100 if update_npu_registry {
1102 if let Some(ref npu) = self.npu {
1103 if let Ok(mut npu_lock) = npu.lock() {
1104 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
1105 }
1106 }
1107 }
1108
1109 self.refresh_cortical_area_hashes(true, true);
1110 self.refresh_brain_regions_hash();
1111 self.refresh_cortical_mappings_hash();
1112
1113 Ok(())
1114 }
1115
1116 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1118 self.cortical_areas.get(cortical_id)
1119 }
1120
1121 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1123 self.cortical_areas.get_mut(cortical_id)
1124 }
1125
1126 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1128 self.cortical_id_to_idx.get(cortical_id).copied()
1129 }
1130
1131 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1143 self.brain_regions.find_region_containing_area(cortical_id)
1144 }
1145
1146 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1149 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1150 return false;
1151 };
1152 let Some(src_area) = self.cortical_areas.get(area) else {
1153 return false;
1154 };
1155 let Some(dst_obj) = src_area
1156 .properties
1157 .get("cortical_mapping_dst")
1158 .and_then(|v| v.as_object())
1159 else {
1160 return false;
1161 };
1162 for dst_key in dst_obj.keys() {
1163 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1164 continue;
1165 };
1166 match self.brain_regions.find_region_containing_area(&dst_id) {
1167 None => return true,
1168 Some(rid) if rid != my_region => return true,
1169 _ => {}
1170 }
1171 }
1172 false
1173 }
1174
1175 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1177 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1178 return false;
1179 };
1180 let my_b64 = area.as_base_64();
1181 for (src_id, src_area) in &self.cortical_areas {
1182 if src_id == area {
1183 continue;
1184 }
1185 let Some(dst_map) = src_area
1186 .properties
1187 .get("cortical_mapping_dst")
1188 .and_then(|v| v.as_object())
1189 else {
1190 continue;
1191 };
1192 if !dst_map.contains_key(&my_b64) {
1193 continue;
1194 }
1195 match self.brain_regions.find_region_containing_area(src_id) {
1196 None => return true,
1197 Some(rid) if rid != my_region => return true,
1198 _ => {}
1199 }
1200 }
1201 false
1202 }
1203
1204 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1215 use std::collections::HashSet;
1216
1217 let region_ids: Vec<String> = self
1218 .brain_regions
1219 .get_all_region_ids()
1220 .into_iter()
1221 .cloned()
1222 .collect();
1223
1224 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1225 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1226
1227 for rid in ®ion_ids {
1229 inputs_by_region.insert(rid.clone(), HashSet::new());
1230 outputs_by_region.insert(rid.clone(), HashSet::new());
1231 }
1232
1233 for (src_id, src_area) in &self.cortical_areas {
1234 let Some(dstmap) = src_area
1235 .properties
1236 .get("cortical_mapping_dst")
1237 .and_then(|v| v.as_object())
1238 else {
1239 continue;
1240 };
1241
1242 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1243 debug!(
1244 target: "feagi-bdu",
1245 "Skipping region IO for source area {} (not in any region)",
1246 src_id.as_base_64()
1247 );
1248 continue;
1249 };
1250
1251 for dst_id_str in dstmap.keys() {
1252 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1253 BduError::InvalidArea(format!(
1254 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1255 dst_id_str,
1256 src_id.as_base_64(),
1257 e
1258 ))
1259 })?;
1260
1261 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1262 else {
1263 warn!(
1264 target: "feagi-bdu",
1265 "Skipping region IO for destination area {} (not in any region)",
1266 dst_id.as_base_64()
1267 );
1268 continue;
1269 };
1270
1271 if src_region_id == dst_region_id {
1272 continue;
1273 }
1274 if self.mapping_is_classifier_assembly_edge(src_id, &dst_id) {
1275 continue;
1276 }
1277
1278 outputs_by_region
1279 .entry(src_region_id.clone())
1280 .or_default()
1281 .insert(src_id.as_base_64());
1282 inputs_by_region
1283 .entry(dst_region_id.clone())
1284 .or_default()
1285 .insert(dst_id.as_base_64());
1286 }
1287 }
1288
1289 for rid in ®ion_ids {
1291 let Some(region) = self.brain_regions.get_region(rid) else {
1292 continue;
1293 };
1294 let in_ids = crate::region_io_designation::parse_designated_id_list(
1295 region
1296 .properties
1297 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1298 )?;
1299 let out_ids = crate::region_io_designation::parse_designated_id_list(
1300 region
1301 .properties
1302 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1303 )?;
1304 for id in in_ids {
1305 inputs_by_region
1306 .entry(rid.clone())
1307 .or_default()
1308 .insert(id.as_base_64());
1309 }
1310 for id in out_ids {
1311 outputs_by_region
1312 .entry(rid.clone())
1313 .or_default()
1314 .insert(id.as_base_64());
1315 }
1316 }
1317
1318 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1319 for rid in region_ids {
1320 let mut inputs: Vec<String> = inputs_by_region
1321 .remove(&rid)
1322 .unwrap_or_default()
1323 .into_iter()
1324 .collect();
1325 let mut outputs: Vec<String> = outputs_by_region
1326 .remove(&rid)
1327 .unwrap_or_default()
1328 .into_iter()
1329 .collect();
1330
1331 inputs.sort();
1332 outputs.sort();
1333
1334 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1335 BduError::InvalidArea(format!(
1336 "Unable to recompute region IO: region '{}' not found in hierarchy",
1337 rid
1338 ))
1339 })?;
1340
1341 if inputs.is_empty() {
1342 region.properties.remove("inputs");
1343 } else {
1344 region
1345 .properties
1346 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1347 }
1348
1349 if outputs.is_empty() {
1350 region.properties.remove("outputs");
1351 } else {
1352 region
1353 .properties
1354 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1355 }
1356
1357 computed.insert(rid, (inputs, outputs));
1358 }
1359
1360 self.refresh_brain_regions_hash();
1361
1362 Ok(computed)
1363 }
1364
1365 pub fn get_root_region_id(&self) -> Option<String> {
1371 self.brain_regions.get_root_region_id()
1372 }
1373
1374 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1376 self.cortical_idx_to_id.get(&cortical_idx)
1377 }
1378
1379 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1382 self.cortical_idx_to_id
1383 .iter()
1384 .map(|(idx, id)| (*idx, id.as_base_64()))
1385 .collect()
1386 }
1387
1388 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1393 let mut granularities = ahash::AHashMap::new();
1394 for (cortical_id, area) in &self.cortical_areas {
1395 let cortical_idx = self
1396 .cortical_id_to_idx
1397 .get(cortical_id)
1398 .copied()
1399 .unwrap_or(0);
1400
1401 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1404 if let Some(arr) = granularity_json.as_array() {
1405 if arr.len() == 3 {
1406 let x_opt = arr[0]
1407 .as_u64()
1408 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1409 let y_opt = arr[1]
1410 .as_u64()
1411 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1412 let z_opt = arr[2]
1413 .as_u64()
1414 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1415
1416 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1417 let granularity = (x as u32, y as u32, z as u32);
1418 if granularity != (1, 1, 1) {
1420 granularities.insert(cortical_idx, granularity);
1421 }
1422 }
1423 }
1424 }
1425 }
1426 }
1427 granularities
1428 }
1429
1430 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1432 self.cortical_areas.keys().collect()
1433 }
1434
1435 pub fn get_cortical_area_count(&self) -> usize {
1437 self.cortical_areas.len()
1438 }
1439
1440 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1454 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1455 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1456 if let Some(upstream_array) = upstream_prop.as_array() {
1457 return upstream_array
1458 .iter()
1459 .filter_map(|v| v.as_u64().map(|n| n as u32))
1460 .collect();
1461 }
1462 }
1463
1464 warn!(target: "feagi-bdu",
1466 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1467 target_cortical_id.as_base_64()
1468 );
1469 }
1470
1471 Vec::new()
1472 }
1473
1474 pub fn get_episodic_memory_upstream_cortical_areas(
1480 &self,
1481 target_cortical_id: &CorticalID,
1482 ) -> Vec<u32> {
1483 let mut episodic_upstream = Vec::new();
1484 for (src_id, src_area) in &self.cortical_areas {
1485 if src_id == target_cortical_id {
1486 continue;
1487 }
1488 let Some(mapping_dst) = src_area
1489 .properties
1490 .get("cortical_mapping_dst")
1491 .and_then(|v| v.as_object())
1492 else {
1493 continue;
1494 };
1495 let Some(rules) =
1496 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1497 else {
1498 continue;
1499 };
1500 if !Self::mapping_has_morphology_rule(rules, "episodic_memory") {
1501 continue;
1502 }
1503 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1504 continue;
1505 };
1506 episodic_upstream.push(src_idx);
1507 }
1508
1509 episodic_upstream.sort_unstable();
1510 episodic_upstream.dedup();
1511 episodic_upstream
1512 }
1513
1514 fn mapping_has_morphology_rule(rules: &[serde_json::Value], morphology: &str) -> bool {
1515 rules.iter().any(|rule| {
1516 let morphology_id = rule
1517 .as_object()
1518 .and_then(|obj| obj.get("morphology_id"))
1519 .and_then(|v| v.as_str())
1520 .or_else(|| {
1521 rule.as_array()
1522 .and_then(|arr| arr.first())
1523 .and_then(|v| v.as_str())
1524 });
1525 morphology_id == Some(morphology)
1526 })
1527 }
1528
1529 fn area_belongs_to_classifier_assembly(area: &CorticalArea) -> bool {
1531 if area
1532 .properties
1533 .get("classifier_assembly")
1534 .and_then(|value| value.as_bool())
1535 == Some(true)
1536 {
1537 return true;
1538 }
1539 matches!(
1540 area.properties
1541 .get("classifier_role")
1542 .and_then(|value| value.as_str()),
1543 Some("kernel_memory") | Some("class_memory") | Some("scan_twin")
1544 )
1545 }
1546
1547 fn mapping_is_classifier_assembly_edge(
1549 &self,
1550 src_id: &CorticalID,
1551 dst_id: &CorticalID,
1552 ) -> bool {
1553 let src_is_classifier = self
1554 .cortical_areas
1555 .get(src_id)
1556 .is_some_and(Self::area_belongs_to_classifier_assembly);
1557 let dst_is_classifier = self
1558 .cortical_areas
1559 .get(dst_id)
1560 .is_some_and(Self::area_belongs_to_classifier_assembly);
1561 if src_is_classifier || dst_is_classifier {
1562 return true;
1563 }
1564 let src_key = src_id.as_base_64();
1565 let dst_key = dst_id.as_base_64();
1566 self.classifiers.values().any(|classifier| {
1567 classifier.owns_area(&src_key)
1568 || classifier.owns_area(&dst_key)
1569 || (classifier.references_input(&src_key) && classifier.owns_area(&dst_key))
1570 })
1571 }
1572
1573 pub fn get_episodic_scan_upstream_cortical_areas(
1575 &self,
1576 target_cortical_id: &CorticalID,
1577 ) -> Vec<u32> {
1578 let mut scan_upstream = Vec::new();
1579 for (src_id, src_area) in &self.cortical_areas {
1580 if src_id == target_cortical_id {
1581 continue;
1582 }
1583 let Some(mapping_dst) = src_area
1584 .properties
1585 .get("cortical_mapping_dst")
1586 .and_then(|v| v.as_object())
1587 else {
1588 continue;
1589 };
1590 let Some(rules) =
1591 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1592 else {
1593 continue;
1594 };
1595 if !Self::mapping_has_morphology_rule(rules, "episodic_scan") {
1596 continue;
1597 }
1598 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1599 continue;
1600 };
1601 scan_upstream.push(src_idx);
1602 }
1603 scan_upstream.sort_unstable();
1604 scan_upstream.dedup();
1605 scan_upstream
1606 }
1607
1608 fn mapping_from_src_to_dst_has_scan(
1609 &self,
1610 src_area_id: &CorticalID,
1611 dst_area_id: &CorticalID,
1612 ) -> bool {
1613 let Some(src_area) = self.cortical_areas.get(src_area_id) else {
1614 return false;
1615 };
1616 let Some(mapping_dst) = src_area
1617 .properties
1618 .get("cortical_mapping_dst")
1619 .and_then(|v| v.as_object())
1620 else {
1621 return false;
1622 };
1623 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
1624 return false;
1625 };
1626 Self::mapping_has_morphology_rule(rules, "episodic_scan")
1627 }
1628
1629 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1631 upstream
1632 .iter()
1633 .filter_map(|idx| {
1634 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1635 let area = self.cortical_areas.get(cortical_id)?;
1636 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1637 None
1638 } else {
1639 Some(*idx)
1640 }
1641 })
1642 .collect()
1643 }
1644
1645 pub fn refresh_upstream_cortical_areas_from_mappings(
1649 &mut self,
1650 target_cortical_id: &CorticalID,
1651 ) -> Vec<u32> {
1652 use std::collections::HashSet;
1653 let target_id_str = target_cortical_id.as_base_64();
1654 let mut upstream_idxs = HashSet::new();
1655 for (src_id, src_area) in &self.cortical_areas {
1656 if src_id == target_cortical_id {
1657 continue;
1658 }
1659 if let Some(mapping) = src_area
1660 .properties
1661 .get("cortical_mapping_dst")
1662 .and_then(|v| v.as_object())
1663 {
1664 if mapping.contains_key(&target_id_str) {
1665 if let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) {
1666 upstream_idxs.insert(src_idx);
1667 }
1668 }
1669 }
1670 }
1671
1672 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1673 upstream_list.sort_unstable();
1674
1675 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1676 target_area.properties.insert(
1677 "upstream_cortical_areas".to_string(),
1678 serde_json::json!(upstream_list),
1679 );
1680 }
1681
1682 self.get_upstream_cortical_areas(target_cortical_id)
1683 }
1684
1685 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1695 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1696 let upstream_array = area
1697 .properties
1698 .entry("upstream_cortical_areas".to_string())
1699 .or_insert_with(|| serde_json::json!([]));
1700
1701 if let Some(arr) = upstream_array.as_array_mut() {
1702 let src_value = serde_json::json!(src_cortical_idx);
1703 if !arr.contains(&src_value) {
1704 arr.push(src_value);
1705 debug!(target: "feagi-bdu",
1706 "Added upstream area idx={} to cortical area '{}'",
1707 src_cortical_idx, target_cortical_id.as_base_64()
1708 );
1709 }
1710 }
1711 }
1712 }
1713
1714 pub fn get_memory_twin_for_upstream_idx(
1716 &self,
1717 memory_area_idx: u32,
1718 upstream_idx: u32,
1719 ) -> Option<CorticalID> {
1720 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1721 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1722 let area = self.cortical_areas.get(memory_id)?;
1723 let mapping = area
1724 .properties
1725 .get("memory_twin_areas")
1726 .and_then(|v| v.as_object())?;
1727 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1728 CorticalID::try_from_base_64(twin_b64).ok()
1729 }
1730
1731 pub fn diagnose_memory_twin_for_mapping(
1738 &self,
1739 src_area_id: &CorticalID,
1740 dst_area_id: &CorticalID,
1741 ) -> BduResult<MemoryTwinDiagnostic> {
1742 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
1743 BduError::InvalidArea(format!(
1744 "Source area not found: {}",
1745 src_area_id.as_base_64()
1746 ))
1747 })?;
1748 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
1749 BduError::InvalidArea(format!(
1750 "Destination area not found: {}",
1751 dst_area_id.as_base_64()
1752 ))
1753 })?;
1754
1755 let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
1756 let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
1757
1758 let rules_opt = src_area
1759 .properties
1760 .get("cortical_mapping_dst")
1761 .and_then(|v| v.as_object())
1762 .and_then(|mapping_dst| {
1763 Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
1764 });
1765 let mapping_exists = rules_opt.is_some();
1766 let episodic_rule_count = rules_opt
1767 .map(|rules| {
1768 rules
1769 .iter()
1770 .filter(|rule| {
1771 let morphology_id = rule
1772 .as_object()
1773 .and_then(|obj| obj.get("morphology_id"))
1774 .and_then(|v| v.as_str())
1775 .or_else(|| {
1776 rule.as_array()
1777 .and_then(|arr| arr.first())
1778 .and_then(|v| v.as_str())
1779 });
1780 morphology_id == Some("episodic_memory")
1781 })
1782 .count()
1783 })
1784 .unwrap_or(0);
1785
1786 let twin_expected =
1787 mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
1788 let twin_cortical_area = dst_area
1789 .properties
1790 .get("memory_twin_areas")
1791 .and_then(|v| v.as_object())
1792 .and_then(|map| map.get(&src_area_id.as_base_64()))
1793 .and_then(|v| v.as_str())
1794 .map(ToString::to_string);
1795 let twin_present = twin_cortical_area.is_some();
1796
1797 let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
1798 CorticalID::try_from_base_64(twin_b64)
1799 .ok()
1800 .and_then(|twin_id| {
1801 self.cortical_areas.get(&twin_id).and_then(|area| {
1802 area.properties
1803 .get("parent_region_id")
1804 .and_then(|v| v.as_str())
1805 .map(ToString::to_string)
1806 })
1807 })
1808 });
1809 let src_parent_region_id = src_area
1810 .properties
1811 .get("parent_region_id")
1812 .and_then(|v| v.as_str())
1813 .map(ToString::to_string);
1814 let dst_parent_region_id = dst_area
1815 .properties
1816 .get("parent_region_id")
1817 .and_then(|v| v.as_str())
1818 .map(ToString::to_string);
1819 let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
1820 (Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
1821 (None, None) if twin_present => Some(true),
1822 _ if twin_present => Some(false),
1823 _ => None,
1824 };
1825
1826 let reason = if !mapping_exists {
1827 Some("no_mapping_rule_between_src_and_dst".to_string())
1828 } else if episodic_rule_count == 0 {
1829 Some("mapping_exists_but_not_episodic_memory".to_string())
1830 } else if !dst_is_memory {
1831 Some("destination_is_not_memory_area".to_string())
1832 } else if src_is_memory {
1833 Some("upstream_is_memory_area_twin_not_supported".to_string())
1834 } else if !twin_present {
1835 Some("twin_expected_but_missing".to_string())
1836 } else if twin_parent_matches_src_parent == Some(false) {
1837 Some("twin_parent_region_mismatch_with_source".to_string())
1838 } else {
1839 None
1840 };
1841
1842 Ok(MemoryTwinDiagnostic {
1843 src_cortical_area: src_area_id.as_base_64(),
1844 dst_cortical_area: dst_area_id.as_base_64(),
1845 src_cortical_type: src_area.cortical_type.to_string(),
1846 dst_cortical_type: dst_area.cortical_type.to_string(),
1847 mapping_exists,
1848 episodic_rule_count,
1849 twin_expected,
1850 twin_present,
1851 twin_cortical_area,
1852 reason,
1853 src_parent_region_id,
1854 dst_parent_region_id,
1855 twin_parent_region_id,
1856 twin_parent_matches_src_parent,
1857 })
1858 }
1859
1860 pub fn ensure_memory_twin_area(
1862 &mut self,
1863 memory_area_id: &CorticalID,
1864 upstream_area_id: &CorticalID,
1865 ) -> BduResult<CorticalID> {
1866 use crate::models::CorticalAreaExt;
1867
1868 let register_replay_mapping = |manager: &mut ConnectomeManager,
1869 twin_id: &CorticalID|
1870 -> BduResult<()> {
1871 let Some(npu) = manager.npu.as_ref() else {
1872 return Ok(());
1873 };
1874 let memory_area_idx =
1875 *manager
1876 .cortical_id_to_idx
1877 .get(memory_area_id)
1878 .ok_or_else(|| {
1879 BduError::InvalidArea(format!(
1880 "Memory area idx missing for {}",
1881 memory_area_id.as_base_64()
1882 ))
1883 })?;
1884 let upstream_area_idx = *manager
1885 .cortical_id_to_idx
1886 .get(upstream_area_id)
1887 .ok_or_else(|| {
1888 BduError::InvalidArea(format!(
1889 "Upstream area idx missing for {}",
1890 upstream_area_id.as_base_64()
1891 ))
1892 })?;
1893 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1894 BduError::InvalidArea(format!(
1895 "Twin area idx missing for {}",
1896 twin_id.as_base_64()
1897 ))
1898 })?;
1899 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1900 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1901 })?;
1902 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1903 if let Ok(mut npu_lock) = npu.lock() {
1904 npu_lock.register_memory_twin_mapping(
1905 memory_area_idx,
1906 upstream_area_idx,
1907 twin_area_idx,
1908 potential,
1909 );
1910 }
1911 Ok(())
1912 };
1913
1914 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1915 BduError::InvalidArea(format!(
1916 "Memory area {} not found",
1917 memory_area_id.as_base_64()
1918 ))
1919 })?;
1920 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1921 BduError::InvalidArea(format!(
1922 "Upstream area {} not found",
1923 upstream_area_id.as_base_64()
1924 ))
1925 })?;
1926
1927 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1928 return Err(BduError::InvalidArea(format!(
1929 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1930 upstream_area_id.as_base_64()
1931 )));
1932 }
1933
1934 if let Some(existing) = memory_area
1935 .properties
1936 .get("memory_twin_areas")
1937 .and_then(|v| v.as_object())
1938 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1939 .and_then(|v| v.as_str())
1940 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1941 {
1942 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1943 register_replay_mapping(self, &existing)?;
1944 self.refresh_cortical_mappings_hash();
1945 return Ok(existing);
1946 }
1947
1948 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1949 if self.cortical_areas.contains_key(&twin_id) {
1950 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1951 let expected_source = upstream_area_id.as_base_64();
1952 let expected_target = memory_area_id.as_base_64();
1953 let existing_source = existing
1954 .properties
1955 .get("memory_twin_of")
1956 .and_then(|v| v.as_str());
1957 let existing_target = existing
1958 .properties
1959 .get("memory_twin_for")
1960 .and_then(|v| v.as_str());
1961 if existing_source != Some(expected_source.as_str())
1962 || existing_target != Some(expected_target.as_str())
1963 {
1964 warn!(
1965 target: "feagi-bdu",
1966 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1967 upstream_area_id.as_base_64(),
1968 memory_area_id.as_base_64()
1969 );
1970 existing.properties.insert(
1971 "memory_twin_of".to_string(),
1972 serde_json::json!(expected_source),
1973 );
1974 existing.properties.insert(
1975 "memory_twin_for".to_string(),
1976 serde_json::json!(expected_target),
1977 );
1978 }
1979 }
1980 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1981 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1982 register_replay_mapping(self, &twin_id)?;
1983 self.refresh_cortical_mappings_hash();
1984 return Ok(twin_id);
1985 }
1986
1987 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1988 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1989 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1990 let mut twin_area = CorticalArea::new(
1991 twin_id,
1992 0,
1993 twin_name,
1994 upstream_area.dimensions,
1995 twin_position,
1996 twin_type,
1997 )?;
1998 twin_area.properties = self.build_memory_twin_properties(
1999 memory_area,
2000 upstream_area,
2001 memory_area_id,
2002 upstream_area_id,
2003 );
2004
2005 let _twin_idx = self.add_cortical_area(twin_area)?;
2006 let _ = self.create_neurons_for_area(&twin_id);
2007
2008 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
2009 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
2010 register_replay_mapping(self, &twin_id)?;
2011 self.refresh_cortical_mappings_hash();
2012 Ok(twin_id)
2013 }
2014
2015 pub fn ensure_scan_twin_area(
2017 &mut self,
2018 memory_area_id: &CorticalID,
2019 field_area_id: &CorticalID,
2020 ) -> BduResult<CorticalID> {
2021 let class_channel_count = self.classifier_class_channel_count(memory_area_id)?;
2022 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
2023 BduError::InvalidArea(format!(
2024 "Memory area {} not found",
2025 memory_area_id.as_base_64()
2026 ))
2027 })?;
2028 let field_area = self.cortical_areas.get(field_area_id).ok_or_else(|| {
2029 BduError::InvalidArea(format!(
2030 "Scan field area {} not found",
2031 field_area_id.as_base_64()
2032 ))
2033 })?;
2034 if matches!(field_area.cortical_type, CorticalAreaType::Memory(_)) {
2035 return Err(BduError::InvalidArea(format!(
2036 "Scan field {} is memory type; scan twins are only for non-memory fields",
2037 field_area_id.as_base_64()
2038 )));
2039 }
2040
2041 if let Some(existing) = memory_area
2042 .properties
2043 .get("memory_twin_areas")
2044 .and_then(|v| v.as_object())
2045 .and_then(|map| map.get(&field_area_id.as_base_64()))
2046 .and_then(|v| v.as_str())
2047 .and_then(|s| CorticalID::try_from_base_64(s).ok())
2048 {
2049 self.refresh_cortical_mappings_hash();
2050 return Ok(existing);
2051 }
2052
2053 let twin_id = self.build_scan_twin_id(memory_area_id, field_area_id)?;
2054 if self.cortical_areas.contains_key(&twin_id) {
2055 self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
2056 self.refresh_cortical_mappings_hash();
2057 return Ok(twin_id);
2058 }
2059
2060 let twin_name = format!("{}_scan_twin", field_area.name.replace(' ', "_"));
2061 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
2062 let twin_position = self.build_memory_twin_position(memory_area, field_area);
2063 let twin_dims = CorticalAreaDimensions::new(
2064 field_area.dimensions.width,
2065 field_area.dimensions.height,
2066 class_channel_count,
2067 )
2068 .map_err(|e| BduError::Internal(format!("Invalid scan twin dimensions: {}", e)))?;
2069 let mut twin_area =
2070 CorticalArea::new(twin_id, 0, twin_name, twin_dims, twin_position, twin_type)?;
2071 twin_area.properties = self.build_memory_twin_properties(
2072 memory_area,
2073 field_area,
2074 memory_area_id,
2075 field_area_id,
2076 );
2077 twin_area
2078 .properties
2079 .insert("scan_twin".to_string(), serde_json::json!(true));
2080
2081 let _twin_idx = self.add_cortical_area(twin_area)?;
2082 let _ = self.create_neurons_for_area(&twin_id);
2083 self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
2084 self.refresh_cortical_mappings_hash();
2085 Ok(twin_id)
2086 }
2087
2088 fn classifier_class_channel_count(&self, memory_area_id: &CorticalID) -> BduResult<u32> {
2089 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
2090 BduError::InvalidArea(format!(
2091 "Memory area {} not found",
2092 memory_area_id.as_base_64()
2093 ))
2094 })?;
2095 let class_id = memory_area
2096 .properties
2097 .get("classifier_class_area_id")
2098 .and_then(|v| v.as_str())
2099 .ok_or_else(|| {
2100 BduError::InvalidArea(format!(
2101 "Memory area {} has no classifier_class_area_id; scan twin cannot be sized",
2102 memory_area_id.as_base_64()
2103 ))
2104 })?;
2105 let class_cortical_id = CorticalID::try_from_base_64(class_id).map_err(|e| {
2106 BduError::InvalidArea(format!(
2107 "Invalid classifier_class_area_id '{}': {}",
2108 class_id, e
2109 ))
2110 })?;
2111 let class_area = self.cortical_areas.get(&class_cortical_id).ok_or_else(|| {
2112 BduError::InvalidArea(format!(
2113 "Classifier class area {} not found",
2114 class_cortical_id.as_base_64()
2115 ))
2116 })?;
2117 let count = class_area
2118 .dimensions
2119 .width
2120 .saturating_mul(class_area.dimensions.height)
2121 .saturating_mul(class_area.dimensions.depth);
2122 if count == 0 {
2123 return Err(BduError::InvalidArea(format!(
2124 "Classifier class area {} has zero volume",
2125 class_cortical_id.as_base_64()
2126 )));
2127 }
2128 Ok(count)
2129 }
2130
2131 fn build_scan_twin_id(
2132 &self,
2133 memory_area_id: &CorticalID,
2134 field_area_id: &CorticalID,
2135 ) -> BduResult<CorticalID> {
2136 let mut hasher = Xxh64::new(DATA_HASH_SEED);
2137 hasher.write(memory_area_id.as_base_64().as_bytes());
2138 hasher.write(field_area_id.as_base_64().as_bytes());
2139 hasher.write(b"scan_twin");
2140 let hash = hasher.finish();
2141 let mut bytes = hash.to_be_bytes();
2142 bytes[0] = b'c';
2143 CorticalID::try_from_bytes(&bytes).map_err(|e| {
2144 BduError::Internal(format!("Failed to build scan twin cortical ID: {}", e))
2145 })
2146 }
2147
2148 fn build_memory_twin_position(
2149 &self,
2150 _memory_area: &CorticalArea,
2151 upstream_area: &CorticalArea,
2152 ) -> GenomeCoordinate3D {
2153 let width = upstream_area.dimensions.width as f32;
2154 let margin = (width * 0.25).ceil() as i32;
2155 let offset = upstream_area.dimensions.width as i32 + margin;
2156 GenomeCoordinate3D::new(
2157 upstream_area.position.x + offset,
2158 upstream_area.position.y,
2159 upstream_area.position.z,
2160 )
2161 }
2162
2163 fn build_memory_twin_id(
2164 &self,
2165 memory_area_id: &CorticalID,
2166 upstream_area_id: &CorticalID,
2167 ) -> BduResult<CorticalID> {
2168 let mut hasher = Xxh64::new(DATA_HASH_SEED);
2169 hasher.write(memory_area_id.as_base_64().as_bytes());
2170 hasher.write(upstream_area_id.as_base_64().as_bytes());
2171 hasher.write(b"memory_twin");
2172 let hash = hasher.finish();
2173 let mut bytes = hash.to_be_bytes();
2174 bytes[0] = b'c';
2175 CorticalID::try_from_bytes(&bytes)
2176 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
2177 }
2178
2179 fn build_memory_twin_properties(
2180 &self,
2181 _memory_area: &CorticalArea,
2182 upstream_area: &CorticalArea,
2183 memory_area_id: &CorticalID,
2184 upstream_area_id: &CorticalID,
2185 ) -> HashMap<String, serde_json::Value> {
2186 let mut props = upstream_area.properties.clone();
2187 props.remove("cortical_mapping_dst");
2188 props.remove("upstream_cortical_areas");
2189 props.remove("parent_region_id");
2190 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
2191 props.insert("is_mem_type".to_string(), serde_json::json!(false));
2192 props.insert(
2193 "memory_twin_of".to_string(),
2194 serde_json::json!(upstream_area_id.as_base_64()),
2195 );
2196 props.insert(
2197 "memory_twin_for".to_string(),
2198 serde_json::json!(memory_area_id.as_base_64()),
2199 );
2200 if let Some(parent_region_id) = upstream_area
2201 .properties
2202 .get("parent_region_id")
2203 .and_then(|v| v.as_str())
2204 {
2205 props.insert(
2206 "parent_region_id".to_string(),
2207 serde_json::json!(parent_region_id),
2208 );
2209 }
2210 props
2211 }
2212
2213 pub fn remove_memory_twin_mapping(
2214 &mut self,
2215 memory_area_id: &str,
2216 field_area_id: &str,
2217 ) -> BduResult<()> {
2218 let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
2219 BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
2220 })?;
2221 if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
2222 if let Some(twins) = memory_area
2223 .properties
2224 .get_mut("memory_twin_areas")
2225 .and_then(|value| value.as_object_mut())
2226 {
2227 twins.remove(field_area_id);
2228 }
2229 }
2230 Ok(())
2231 }
2232
2233 fn set_memory_twin_mapping(
2234 &mut self,
2235 memory_area_id: &CorticalID,
2236 upstream_area_id: &CorticalID,
2237 twin_id: &CorticalID,
2238 ) {
2239 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
2240 let mapping = memory_area
2241 .properties
2242 .entry("memory_twin_areas".to_string())
2243 .or_insert_with(|| serde_json::json!({}));
2244 if let Some(map) = mapping.as_object_mut() {
2245 map.insert(
2246 upstream_area_id.as_base_64(),
2247 serde_json::json!(twin_id.as_base_64()),
2248 );
2249 }
2250 }
2251 }
2252
2253 fn twin_is_classifier_stamp(&self, twin_id: &CorticalID) -> bool {
2255 let twin_b64 = twin_id.as_base_64();
2256 if self
2257 .classifiers
2258 .values()
2259 .any(|classifier| classifier.field_for_twin(&twin_b64).is_some())
2260 {
2261 return true;
2262 }
2263 self.cortical_areas
2264 .get(twin_id)
2265 .is_some_and(Self::area_belongs_to_classifier_assembly)
2266 }
2267
2268 pub fn rekey_memory_twin_source(
2271 &mut self,
2272 memory_area_id: &str,
2273 old_src_area_id: &str,
2274 new_src_area_id: &str,
2275 ) -> BduResult<()> {
2276 if old_src_area_id == new_src_area_id {
2277 return Ok(());
2278 }
2279 let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
2280 BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
2281 })?;
2282 let twin_entries: Vec<(String, serde_json::Value)> = {
2283 let memory_area = self.cortical_areas.get(&memory_id).ok_or_else(|| {
2284 BduError::InvalidArea(format!("Memory area {} not found", memory_area_id))
2285 })?;
2286 memory_area
2287 .properties
2288 .get("memory_twin_areas")
2289 .and_then(|value| value.as_object())
2290 .map(|twins| {
2291 twins
2292 .iter()
2293 .map(|(key, value)| (key.clone(), value.clone()))
2294 .collect()
2295 })
2296 .unwrap_or_default()
2297 };
2298 if twin_entries.is_empty() {
2299 return Ok(());
2300 }
2301 let mut used_key: Option<String> = None;
2302 let mut twin_id: Option<serde_json::Value> = None;
2303 if let Some((_, value)) = twin_entries.iter().find(|(key, _)| key == old_src_area_id) {
2304 used_key = Some(old_src_area_id.to_string());
2305 twin_id = Some(value.clone());
2306 } else {
2307 for (key, value) in &twin_entries {
2308 let Some(twin_b64) = value.as_str() else {
2309 continue;
2310 };
2311 let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) else {
2312 continue;
2313 };
2314 let owned_by_old_field = self
2315 .cortical_areas
2316 .get(&twin_cortical_id)
2317 .and_then(|twin| twin.properties.get("memory_twin_of"))
2318 .and_then(|property| property.as_str())
2319 == Some(old_src_area_id);
2320 if owned_by_old_field {
2321 used_key = Some(key.clone());
2322 twin_id = Some(value.clone());
2323 break;
2324 }
2325 }
2326 }
2327 let Some(used_key) = used_key else {
2328 return Ok(());
2329 };
2330 let Some(twin_id) = twin_id else {
2331 return Ok(());
2332 };
2333 if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
2334 if let Some(twins) = memory_area
2335 .properties
2336 .get_mut("memory_twin_areas")
2337 .and_then(|value| value.as_object_mut())
2338 {
2339 twins.remove(&used_key);
2340 twins.insert(new_src_area_id.to_string(), twin_id.clone());
2341 }
2342 }
2343 if let Some(twin_b64) = twin_id.as_str() {
2344 if let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) {
2345 if let Some(twin) = self.cortical_areas.get_mut(&twin_cortical_id) {
2346 twin.properties.insert(
2347 "memory_twin_of".to_string(),
2348 serde_json::json!(new_src_area_id),
2349 );
2350 }
2351 }
2352 }
2353 Ok(())
2354 }
2355
2356 fn ensure_memory_replay_mapping(
2357 &mut self,
2358 memory_area_id: &CorticalID,
2359 twin_id: &CorticalID,
2360 ) -> BduResult<()> {
2361 if !self.morphology_registry.contains("memory_replay") {
2362 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
2363 }
2364 self.refresh_morphologies_hash();
2365 let mapping_data = vec![serde_json::json!({
2366 "morphology_id": "memory_replay",
2367 "morphology_scalar": [1, 1, 1],
2368 "postSynapticCurrent_multiplier": 1,
2369 "plasticity_flag": false,
2370 "plasticity_constant": 0,
2371 "ltp_multiplier": 0,
2372 "ltd_multiplier": 0,
2373 "plasticity_window": 0,
2374 })];
2375 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
2376 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
2377 self.refresh_cortical_area_hashes(true, false);
2379 Ok(())
2380 }
2381
2382 pub fn teardown_owned_memory_twin_for_mapping(
2387 &mut self,
2388 memory_area_id: &CorticalID,
2389 upstream_area_id: &CorticalID,
2390 ) -> BduResult<Option<CorticalID>> {
2391 let upstream_id = upstream_area_id.as_base_64();
2392 let twin_id = self
2393 .cortical_areas
2394 .get(memory_area_id)
2395 .and_then(|memory_area| memory_area.properties.get("memory_twin_areas"))
2396 .and_then(|value| value.as_object())
2397 .and_then(|twins| twins.get(&upstream_id))
2398 .and_then(|value| value.as_str())
2399 .map(CorticalID::try_from_base_64)
2400 .transpose()
2401 .map_err(|error| {
2402 BduError::InvalidArea(format!(
2403 "Invalid owned memory twin ID for {} -> {}: {}",
2404 upstream_area_id.as_base_64(),
2405 memory_area_id.as_base_64(),
2406 error
2407 ))
2408 })?;
2409 let Some(twin_id) = twin_id else {
2410 return Ok(None);
2411 };
2412 if self.twin_is_classifier_stamp(&twin_id) {
2414 return Ok(None);
2415 }
2416
2417 let twin = self.cortical_areas.get(&twin_id).ok_or_else(|| {
2418 BduError::InvalidArea(format!(
2419 "Owned memory twin {} for {} -> {} does not exist",
2420 twin_id.as_base_64(),
2421 upstream_area_id.as_base_64(),
2422 memory_area_id.as_base_64()
2423 ))
2424 })?;
2425 let owned_by_source = twin
2426 .properties
2427 .get("memory_twin_of")
2428 .and_then(|value| value.as_str())
2429 == Some(upstream_id.as_str());
2430 let owned_by_memory = twin
2431 .properties
2432 .get("memory_twin_for")
2433 .and_then(|value| value.as_str())
2434 == Some(memory_area_id.as_base_64().as_str());
2435 if !owned_by_source || !owned_by_memory {
2436 return Err(BduError::InvalidArea(format!(
2437 "Cortical area {} is not the generated twin owned by {} -> {}",
2438 twin_id.as_base_64(),
2439 upstream_area_id.as_base_64(),
2440 memory_area_id.as_base_64()
2441 )));
2442 }
2443
2444 let memory_idx = *self.cortical_id_to_idx.get(memory_area_id).ok_or_else(|| {
2445 BduError::InvalidArea(format!(
2446 "Memory area idx missing for {}",
2447 memory_area_id.as_base_64()
2448 ))
2449 })?;
2450 let upstream_idx = *self
2451 .cortical_id_to_idx
2452 .get(upstream_area_id)
2453 .ok_or_else(|| {
2454 BduError::InvalidArea(format!(
2455 "Upstream area idx missing for {}",
2456 upstream_area_id.as_base_64()
2457 ))
2458 })?;
2459
2460 self.update_cortical_mapping(memory_area_id, &twin_id, Vec::new())?;
2461 let _ = self.regenerate_synapses_for_mapping(memory_area_id, &twin_id)?;
2462 if let Some(npu) = &self.npu {
2463 npu.lock()
2464 .unwrap()
2465 .unregister_memory_twin_mapping(memory_idx, upstream_idx);
2466 }
2467
2468 let memory_area = self.cortical_areas.get_mut(memory_area_id).ok_or_else(|| {
2469 BduError::InvalidArea(format!(
2470 "Memory area {} not found",
2471 memory_area_id.as_base_64()
2472 ))
2473 })?;
2474 let twins = memory_area
2475 .properties
2476 .get_mut("memory_twin_areas")
2477 .and_then(|value| value.as_object_mut())
2478 .ok_or_else(|| {
2479 BduError::InvalidArea(format!(
2480 "Memory area {} has invalid memory_twin_areas metadata",
2481 memory_area_id.as_base_64()
2482 ))
2483 })?;
2484 twins.remove(&upstream_id);
2485 if twins.is_empty() {
2486 memory_area.properties.remove("memory_twin_areas");
2487 }
2488
2489 for region_id in self
2490 .get_brain_region_ids()
2491 .into_iter()
2492 .cloned()
2493 .collect::<Vec<_>>()
2494 {
2495 if let Some(region) = self.get_brain_region_mut(®ion_id) {
2496 region.remove_area(&twin_id);
2497 }
2498 }
2499 self.remove_cortical_area(&twin_id)?;
2500 self.refresh_cortical_mappings_hash();
2501 Ok(Some(twin_id))
2502 }
2503
2504 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
2514 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
2515 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
2516 if let Some(arr) = upstream_prop.as_array_mut() {
2517 let src_value = serde_json::json!(src_cortical_idx);
2518 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
2519 arr.remove(pos);
2520 debug!(target: "feagi-bdu",
2521 "Removed upstream area idx={} from cortical area '{}'",
2522 src_cortical_idx, target_cortical_id.as_base_64()
2523 );
2524 }
2525 }
2526 }
2527 }
2528 }
2529
2530 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
2532 self.cortical_areas.contains_key(cortical_id)
2533 }
2534
2535 pub fn is_initialized(&self) -> bool {
2537 self.initialized && !self.cortical_areas.is_empty()
2538 }
2539
2540 pub fn add_brain_region(
2546 &mut self,
2547 region: BrainRegion,
2548 parent_id: Option<String>,
2549 ) -> BduResult<()> {
2550 self.brain_regions.add_region(region, parent_id)?;
2551 self.refresh_brain_regions_hash();
2552 Ok(())
2553 }
2554
2555 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
2557 self.brain_regions.remove_region(region_id)?;
2558 self.refresh_brain_regions_hash();
2559 Ok(())
2560 }
2561
2562 pub fn clear_brain_regions(&mut self) {
2567 self.brain_regions.clear();
2568 self.refresh_brain_regions_hash();
2569 }
2570
2571 pub fn replace_classifiers(
2573 &mut self,
2574 classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
2575 ) {
2576 self.classifiers = classifiers;
2577 self.refresh_classifiers_hash();
2578 }
2579
2580 pub fn apply_loaded_classifier_assemblies(&mut self) {
2587 let classifiers: Vec<_> = self.classifiers.values().cloned().collect();
2588 for classifier in classifiers {
2589 self.apply_loaded_classifier_assembly(&classifier);
2590 }
2591 }
2592
2593 fn apply_loaded_classifier_assembly(
2594 &mut self,
2595 classifier: &feagi_structures::genomic::classifiers::Classifier,
2596 ) {
2597 let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) else {
2598 return;
2599 };
2600 let Ok(class_mem_id) = CorticalID::try_from_base_64(&classifier.class_memory_id) else {
2601 return;
2602 };
2603
2604 let mut twin_map = serde_json::Map::new();
2605 for field in &classifier.fields {
2606 if field.field_area_id.is_empty() || field.scan_twin_id.is_empty() {
2607 continue;
2608 }
2609 let Ok(twin_id) = CorticalID::try_from_base_64(&field.scan_twin_id) else {
2610 continue;
2611 };
2612 if !self.cortical_areas.contains_key(&twin_id) {
2613 continue;
2614 }
2615 twin_map.insert(
2616 field.field_area_id.clone(),
2617 serde_json::json!(field.scan_twin_id),
2618 );
2619 if let Ok(field_id) = CorticalID::try_from_base_64(&field.field_area_id) {
2620 if let Some(field_area) = self.cortical_areas.get_mut(&field_id) {
2621 Self::ensure_assembly_burst(field_area);
2622 }
2623 }
2624 if let Some(twin_area) = self.cortical_areas.get_mut(&twin_id) {
2625 twin_area
2626 .properties
2627 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2628 twin_area.properties.insert(
2629 "classifier_role".to_string(),
2630 serde_json::json!("scan_twin"),
2631 );
2632 twin_area
2633 .properties
2634 .insert("scan_twin".to_string(), serde_json::json!(true));
2635 twin_area.properties.insert(
2636 "memory_twin_of".to_string(),
2637 serde_json::json!(field.field_area_id),
2638 );
2639 twin_area.properties.insert(
2640 "memory_twin_for".to_string(),
2641 serde_json::json!(classifier.kernel_memory_id),
2642 );
2643 Self::ensure_assembly_burst(twin_area);
2644 }
2645 }
2646
2647 if let Some(mask_area_id) = &classifier.mask_area_id {
2648 if let Ok(mask_id) = CorticalID::try_from_base_64(mask_area_id) {
2649 if let Some(mask_area) = self.cortical_areas.get_mut(&mask_id) {
2650 Self::ensure_assembly_burst(mask_area);
2651 }
2652 }
2653 }
2654
2655 if let Some(kernel_mem) = self.cortical_areas.get_mut(&kernel_mem_id) {
2656 kernel_mem
2657 .properties
2658 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2659 kernel_mem.properties.insert(
2660 "classifier_role".to_string(),
2661 serde_json::json!("kernel_memory"),
2662 );
2663 Self::write_classifier_training_properties(kernel_mem, classifier);
2664 kernel_mem.properties.insert(
2665 "classifier_class_memory_id".to_string(),
2666 serde_json::json!(classifier.class_memory_id),
2667 );
2668 kernel_mem.properties.insert(
2669 "memory_twin_areas".to_string(),
2670 serde_json::Value::Object(twin_map),
2671 );
2672 Self::ensure_assembly_burst(kernel_mem);
2673 }
2674 if let Some(class_mem) = self.cortical_areas.get_mut(&class_mem_id) {
2675 class_mem
2676 .properties
2677 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2678 class_mem.properties.insert(
2679 "classifier_role".to_string(),
2680 serde_json::json!("class_memory"),
2681 );
2682 class_mem.properties.insert(
2683 "classifier_kernel_memory_id".to_string(),
2684 serde_json::json!(classifier.kernel_memory_id),
2685 );
2686 if let Some(class_area_id) = &classifier.class_area_id {
2687 class_mem.properties.insert(
2688 "classifier_class_area_id".to_string(),
2689 serde_json::json!(class_area_id),
2690 );
2691 } else {
2692 class_mem.properties.remove("classifier_class_area_id");
2693 }
2694 Self::ensure_assembly_burst(class_mem);
2695 }
2696 }
2697
2698 fn write_classifier_training_properties(
2701 kernel_mem: &mut CorticalArea,
2702 classifier: &feagi_structures::genomic::classifiers::Classifier,
2703 ) {
2704 use feagi_structures::genomic::classifiers::ClassifierTrainingMode;
2705 kernel_mem.properties.insert(
2706 "classifier_training_mode".to_string(),
2707 serde_json::json!(classifier.training_mode),
2708 );
2709 match classifier.training_mode {
2710 ClassifierTrainingMode::Kernel => {
2711 kernel_mem.properties.remove("classifier_mask_area_id");
2712 kernel_mem.properties.remove("classifier_kernel_size");
2713 if let Some(kernel_area_id) = &classifier.kernel_area_id {
2714 kernel_mem.properties.insert(
2715 "classifier_kernel_area_id".to_string(),
2716 serde_json::json!(kernel_area_id),
2717 );
2718 } else {
2719 kernel_mem.properties.remove("classifier_kernel_area_id");
2720 }
2721 if let Some(class_area_id) = &classifier.class_area_id {
2722 kernel_mem.properties.insert(
2723 "classifier_class_area_id".to_string(),
2724 serde_json::json!(class_area_id),
2725 );
2726 } else {
2727 kernel_mem.properties.remove("classifier_class_area_id");
2728 }
2729 }
2730 ClassifierTrainingMode::Scanner => {
2731 kernel_mem.properties.remove("classifier_kernel_area_id");
2732 kernel_mem.properties.remove("classifier_class_area_id");
2733 if let Some(mask_area_id) = &classifier.mask_area_id {
2734 kernel_mem.properties.insert(
2735 "classifier_mask_area_id".to_string(),
2736 serde_json::json!(mask_area_id),
2737 );
2738 } else {
2739 kernel_mem.properties.remove("classifier_mask_area_id");
2740 }
2741 if let Some(kernel_size) = classifier.kernel_size {
2742 kernel_mem.properties.insert(
2743 "classifier_kernel_size".to_string(),
2744 serde_json::json!(kernel_size),
2745 );
2746 } else {
2747 kernel_mem.properties.remove("classifier_kernel_size");
2748 }
2749 }
2750 }
2751 }
2752
2753 fn ensure_assembly_burst(area: &mut CorticalArea) {
2756 if !area.properties.contains_key("burst_engine_active") {
2757 area.properties
2758 .insert("burst_engine_active".to_string(), serde_json::json!(true));
2759 }
2760 }
2761
2762 pub fn upsert_classifier(
2763 &mut self,
2764 classifier: feagi_structures::genomic::classifiers::Classifier,
2765 ) {
2766 self.classifiers
2767 .insert(classifier.classifier_id.clone(), classifier.clone());
2768 if let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) {
2769 if self.cortical_areas.contains_key(&kernel_mem_id) {
2770 self.apply_loaded_classifier_assembly(&classifier);
2771 }
2772 }
2773 self.refresh_classifiers_hash();
2774 }
2775
2776 pub fn reconfigure_classifier_scan(&self, kernel_memory_id: &str) {
2778 #[cfg(feature = "plasticity")]
2779 {
2780 let Ok(memory_id) = CorticalID::try_from_base_64(kernel_memory_id) else {
2781 return;
2782 };
2783 let Some(executor) = &self.plasticity_executor else {
2784 return;
2785 };
2786 let Ok(exec) = executor.lock() else {
2787 return;
2788 };
2789 self.configure_memory_scan_on_executor(&*exec, &memory_id);
2790 }
2791 #[cfg(not(feature = "plasticity"))]
2792 {
2793 let _ = kernel_memory_id;
2794 }
2795 }
2796
2797 pub fn get_classifier(
2798 &self,
2799 classifier_id: &str,
2800 ) -> Option<&feagi_structures::genomic::classifiers::Classifier> {
2801 self.classifiers.get(classifier_id)
2802 }
2803
2804 pub fn list_classifiers(
2805 &self,
2806 ) -> HashMap<String, feagi_structures::genomic::classifiers::Classifier> {
2807 self.classifiers.clone()
2808 }
2809
2810 pub fn remove_classifier(
2811 &mut self,
2812 classifier_id: &str,
2813 ) -> Option<feagi_structures::genomic::classifiers::Classifier> {
2814 let removed = self.classifiers.remove(classifier_id);
2815 if removed.is_some() {
2816 self.refresh_classifiers_hash();
2817 }
2818 removed
2819 }
2820
2821 pub fn classifiers_owning_area(
2823 &self,
2824 area_id: &str,
2825 ) -> Vec<feagi_structures::genomic::classifiers::Classifier> {
2826 self.classifiers
2827 .values()
2828 .filter(|classifier| classifier.owns_area(area_id))
2829 .cloned()
2830 .collect()
2831 }
2832
2833 pub fn clear_classifier_inputs_for_area(&mut self, area_id: &str) -> usize {
2835 let mut updated = Vec::new();
2836 for classifier in self.classifiers.values_mut() {
2837 if classifier.references_input(area_id) {
2838 classifier.clear_input(area_id);
2839 updated.push(classifier.clone());
2840 }
2841 }
2842 let count = updated.len();
2843 for classifier in &updated {
2844 if let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) {
2845 if self.cortical_areas.contains_key(&kernel_mem_id) {
2846 self.apply_loaded_classifier_assembly(classifier);
2847 }
2848 }
2849 }
2850 if count > 0 {
2851 self.refresh_classifiers_hash();
2852 }
2853 count
2854 }
2855
2856 pub fn apply_classifier_mapping_change(
2858 &mut self,
2859 src_area_id: &str,
2860 dst_area_id: &str,
2861 morphology_id: &str,
2862 removed: bool,
2863 ) -> usize {
2864 let mut updated = 0usize;
2865 for classifier in self.classifiers.values_mut() {
2866 if classifier.apply_mapping_change(src_area_id, dst_area_id, morphology_id, removed) {
2867 updated += 1;
2868 }
2869 }
2870 if updated > 0 {
2871 self.refresh_classifiers_hash();
2872 }
2873 updated
2874 }
2875
2876 pub fn change_brain_region_parent(
2878 &mut self,
2879 region_id: &str,
2880 new_parent_id: &str,
2881 ) -> BduResult<()> {
2882 self.brain_regions.change_parent(region_id, new_parent_id)?;
2883 self.refresh_brain_regions_hash();
2884 Ok(())
2885 }
2886
2887 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
2889 self.brain_regions.get_region(region_id)
2890 }
2891
2892 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
2894 self.brain_regions.get_region_mut(region_id)
2895 }
2896
2897 pub fn get_brain_region_ids(&self) -> Vec<&String> {
2899 self.brain_regions.get_all_region_ids()
2900 }
2901
2902 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
2904 &self.brain_regions
2905 }
2906
2907 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
2913 &self.morphology_registry
2914 }
2915
2916 pub fn get_morphology_count(&self) -> usize {
2918 self.morphology_registry.count()
2919 }
2920
2921 pub fn upsert_morphology(
2926 &mut self,
2927 morphology_id: String,
2928 morphology: feagi_evolutionary::Morphology,
2929 ) {
2930 self.morphology_registry
2931 .add_morphology(morphology_id, morphology);
2932 self.refresh_morphologies_hash();
2933 }
2934
2935 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
2941 let removed = self.morphology_registry.remove_morphology(morphology_id);
2942 if removed {
2943 self.refresh_morphologies_hash();
2944 }
2945 removed
2946 }
2947
2948 pub fn update_cortical_mapping(
2966 &mut self,
2967 src_area_id: &CorticalID,
2968 dst_area_id: &CorticalID,
2969 mapping_data: Vec<serde_json::Value>,
2970 ) -> BduResult<()> {
2971 use tracing::info;
2972
2973 self.validate_memory_outbound_mapping_contract(src_area_id, dst_area_id, &mapping_data)?;
2974 crate::region_io_designation::validate_cross_region_mapping_proposal(
2975 self,
2976 src_area_id,
2977 dst_area_id,
2978 &mapping_data,
2979 )?;
2980
2981 debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
2982
2983 {
2984 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
2986 crate::types::BduError::InvalidArea(format!(
2987 "Source area not found: {}",
2988 src_area_id
2989 ))
2990 })?;
2991
2992 let cortical_mapping_dst =
2994 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
2995 existing.as_object_mut().ok_or_else(|| {
2996 crate::types::BduError::InvalidMorphology(
2997 "cortical_mapping_dst is not an object".to_string(),
2998 )
2999 })?
3000 } else {
3001 src_area
3003 .properties
3004 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
3005 src_area
3006 .properties
3007 .get_mut("cortical_mapping_dst")
3008 .unwrap()
3009 .as_object_mut()
3010 .unwrap()
3011 };
3012
3013 if mapping_data.is_empty() {
3015 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
3017 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
3018 } else {
3019 cortical_mapping_dst.insert(
3020 dst_area_id.as_base_64(),
3021 serde_json::Value::Array(mapping_data.clone()),
3022 );
3023 debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
3024 src_area_id, dst_area_id, mapping_data.len());
3025 }
3026 }
3027
3028 self.refresh_cortical_mappings_hash();
3029
3030 Ok(())
3031 }
3032
3033 pub fn validate_memory_outbound_mapping_contract(
3040 &self,
3041 src_area_id: &CorticalID,
3042 dst_area_id: &CorticalID,
3043 mapping_data: &[serde_json::Value],
3044 ) -> BduResult<()> {
3045 if mapping_data.is_empty() {
3046 return Ok(());
3047 }
3048 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3049 BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3050 })?;
3051 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3052 BduError::InvalidArea(format!("Destination area not found: {}", dst_area_id))
3053 })?;
3054 if !matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3055 || matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3056 {
3057 return Ok(());
3058 }
3059
3060 for rule in mapping_data {
3061 let morphology_id = rule
3062 .as_object()
3063 .and_then(|rule_obj| rule_obj.get("morphology_id"))
3064 .and_then(|value| value.as_str())
3065 .or_else(|| {
3066 rule.as_array()
3067 .and_then(|rule_array| rule_array.first())
3068 .and_then(|value| value.as_str())
3069 });
3070 let is_replay_edge_to_own_twin = morphology_id == Some("memory_replay")
3071 && dst_area
3072 .properties
3073 .get("memory_twin_for")
3074 .and_then(|value| value.as_str())
3075 == Some(src_area_id.as_base_64().as_str());
3076 if morphology_id != Some("associative_memory") && !is_replay_edge_to_own_twin {
3077 return Err(BduError::InvalidMorphology(format!(
3078 "Memory-to-non-memory mapping {} -> {} only supports associative_memory \
3079 (or memory_replay to its own twin)",
3080 src_area_id, dst_area_id
3081 )));
3082 }
3083 }
3084 Ok(())
3085 }
3086
3087 pub fn regenerate_synapses_for_mapping(
3100 &mut self,
3101 src_area_id: &CorticalID,
3102 dst_area_id: &CorticalID,
3103 ) -> BduResult<usize> {
3104 use tracing::info;
3105
3106 debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
3107
3108 let mapping_rules_len = self
3109 .cortical_areas
3110 .get(src_area_id)
3111 .and_then(|area| area.properties.get("cortical_mapping_dst"))
3112 .and_then(|v| v.as_object())
3113 .and_then(|map| map.get(&dst_area_id.as_base_64()))
3114 .and_then(|v| v.as_array())
3115 .map(|arr| arr.len())
3116 .unwrap_or(0);
3117 tracing::debug!(
3118 target: "feagi-bdu",
3119 "Mapping rules for {} -> {}: {}",
3120 src_area_id,
3121 dst_area_id,
3122 mapping_rules_len
3123 );
3124
3125 let Some(npu_arc) = self.npu.clone() else {
3127 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
3128 return Ok(0);
3129 };
3130
3131 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3141 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
3142 })?;
3143 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3144 BduError::InvalidArea(format!(
3145 "No cortical idx for destination area {}",
3146 dst_area_id
3147 ))
3148 })?;
3149
3150 let mut pruned_synapse_count: usize = 0;
3153 use std::time::Instant;
3154 let start = Instant::now();
3155
3156 let (sources, targets) = {
3162 let lock_start = std::time::Instant::now();
3163 let npu = npu_arc.lock().unwrap();
3164 let lock_wait = lock_start.elapsed();
3165 tracing::debug!(
3166 target: "feagi-bdu",
3167 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
3168 lock_wait.as_secs_f64() * 1000.0,
3169 src_area_id,
3170 dst_area_id
3171 );
3172 let sources: Vec<NeuronId> = npu
3173 .get_neurons_in_cortical_area(src_idx)
3174 .into_iter()
3175 .map(NeuronId)
3176 .collect();
3177 let targets: Vec<NeuronId> = npu
3178 .get_neurons_in_cortical_area(dst_idx)
3179 .into_iter()
3180 .map(NeuronId)
3181 .collect();
3182 (sources, targets)
3183 };
3184
3185 tracing::debug!(
3186 target: "feagi-bdu",
3187 "Prune synapses: {} sources, {} targets",
3188 sources.len(),
3189 targets.len()
3190 );
3191
3192 if !sources.is_empty() && !targets.is_empty() {
3193 let remove_start = Instant::now();
3194 pruned_synapse_count = {
3195 let lock_start = std::time::Instant::now();
3196 let mut npu = npu_arc.lock().unwrap();
3197 let lock_wait = lock_start.elapsed();
3198 tracing::debug!(
3199 target: "feagi-bdu",
3200 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
3201 lock_wait.as_secs_f64() * 1000.0,
3202 src_area_id,
3203 dst_area_id
3204 );
3205 npu.remove_synapses_between(sources, targets)
3209 };
3210 let remove_time = remove_start.elapsed();
3211 let total_time = start.elapsed();
3212
3213 info!(
3214 target: "feagi-bdu",
3215 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
3216 pruned_synapse_count,
3217 src_area_id,
3218 dst_area_id,
3219 total_time.as_millis(),
3220 remove_time.as_millis()
3221 );
3222
3223 if pruned_synapse_count > 0 {
3225 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
3226 BduError::Internal(format!(
3227 "Pruned synapse count overflow (usize -> u32): {}",
3228 pruned_synapse_count
3229 ))
3230 })?;
3231 let state_manager = StateManager::instance();
3232 let state_manager = state_manager.read();
3233 let core_state = state_manager.get_core_state();
3234 core_state.subtract_synapse_count(pruned_u32);
3235 state_manager.subtract_cortical_area_outgoing_synapses(
3236 &src_area_id.as_base_64(),
3237 pruned_synapse_count,
3238 );
3239 state_manager.subtract_cortical_area_incoming_synapses(
3240 &dst_area_id.as_base_64(),
3241 pruned_synapse_count,
3242 );
3243
3244 {
3248 let mut cache = self.cached_synapse_counts_per_area.write();
3249 let entry = cache
3250 .entry(*src_area_id)
3251 .or_insert_with(|| AtomicUsize::new(0));
3252 let mut current = entry.load(Ordering::Relaxed);
3253 loop {
3254 let next = current.saturating_sub(pruned_synapse_count);
3255 match entry.compare_exchange(
3256 current,
3257 next,
3258 Ordering::Relaxed,
3259 Ordering::Relaxed,
3260 ) {
3261 Ok(_) => break,
3262 Err(v) => current = v,
3263 }
3264 }
3265 }
3266 }
3267 }
3268
3269 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
3276 tracing::debug!(
3277 target: "feagi-bdu",
3278 "Synaptogenesis created {} synapses for {} -> {}",
3279 synapse_count,
3280 src_area_id,
3281 dst_area_id
3282 );
3283
3284 if synapse_count > 0 {
3287 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
3288 BduError::Internal(format!(
3289 "Created synapse count overflow (usize -> u32): {}",
3290 synapse_count
3291 ))
3292 })?;
3293
3294 {
3296 let mut cache = self.cached_synapse_counts_per_area.write();
3297 cache
3298 .entry(*src_area_id)
3299 .or_insert_with(|| AtomicUsize::new(0))
3300 .fetch_add(synapse_count, Ordering::Relaxed);
3301 }
3302
3303 let state_manager = StateManager::instance();
3305 let state_manager = state_manager.read();
3306 let core_state = state_manager.get_core_state();
3307 core_state.add_synapse_count(created_u32);
3308 state_manager
3309 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
3310 state_manager
3311 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
3312 }
3313
3314 let src_idx_for_upstream = src_idx;
3317
3318 let has_mapping = self
3320 .cortical_areas
3321 .get(src_area_id)
3322 .and_then(|area| area.properties.get("cortical_mapping_dst"))
3323 .and_then(|v| v.as_object())
3324 .and_then(|map| map.get(&dst_area_id.as_base_64()))
3325 .is_some();
3326
3327 debug!(target: "feagi-bdu",
3328 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
3329 synapse_count,
3330 src_area_id.as_base_64(),
3331 dst_area_id.as_base_64(),
3332 has_mapping,
3333 if has_mapping { "" } else { "NOT " }
3334 );
3335
3336 if has_mapping {
3337 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
3339
3340 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
3341 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3342 && !Self::area_belongs_to_classifier_assembly(dst_area)
3343 {
3344 let is_scan = self.mapping_from_src_to_dst_has_scan(src_area_id, dst_area_id);
3345 let twin_result = if is_scan {
3346 self.ensure_scan_twin_area(dst_area_id, src_area_id)
3347 } else {
3348 self.ensure_memory_twin_area(dst_area_id, src_area_id)
3349 };
3350 if let Err(e) = twin_result {
3351 warn!(
3352 target: "feagi-bdu",
3353 "Failed to ensure {} twin for {} -> {}: {}",
3354 if is_scan { "scan" } else { "memory" },
3355 src_area_id.as_base_64(),
3356 dst_area_id.as_base_64(),
3357 e
3358 );
3359 }
3360 }
3361 }
3362
3363 #[cfg(feature = "plasticity")]
3365 if let Some(ref executor) = self.plasticity_executor {
3366 use feagi_evolutionary::extract_memory_properties;
3367
3368 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
3369 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
3370 let upstream_areas =
3371 self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
3372 debug!(
3373 target: "feagi-bdu",
3374 "Registering memory area idx={} id={} upstream={} depth={}",
3375 dst_area.cortical_idx,
3376 dst_area_id.as_base_64(),
3377 upstream_areas.len(),
3378 mem_props.temporal_depth
3379 );
3380
3381 if let Some(ref npu_arc) = self.npu {
3384 if let Ok(mut npu) = npu_arc.lock() {
3385 let existing_configs = npu.get_all_fire_ledger_configs();
3386 for &upstream_idx in &upstream_areas {
3387 let existing = existing_configs
3388 .iter()
3389 .find(|(idx, _)| *idx == upstream_idx)
3390 .map(|(_, w)| *w)
3391 .unwrap_or(0);
3392
3393 let desired = mem_props.temporal_depth as usize;
3394 let resolved = existing.max(desired);
3395 if resolved != existing {
3396 if let Err(e) =
3397 npu.configure_fire_ledger_window(upstream_idx, resolved)
3398 {
3399 warn!(
3400 target: "feagi-bdu",
3401 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
3402 upstream_idx,
3403 resolved,
3404 e
3405 );
3406 }
3407 }
3408 }
3409 } else {
3410 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
3411 }
3412 }
3413
3414 if let Ok(exec) = executor.lock() {
3415 use feagi_npu_plasticity::{
3416 MemoryNeuronLifecycleConfig, PlasticityExecutor,
3417 };
3418
3419 let lifecycle_config = MemoryNeuronLifecycleConfig {
3420 initial_lifespan: mem_props.init_lifespan,
3421 lifespan_growth_rate: mem_props.lifespan_growth_rate,
3422 longterm_threshold: mem_props.longterm_threshold,
3423 max_reactivations: 1000,
3424 };
3425
3426 exec.register_memory_area(
3427 dst_area.cortical_idx,
3428 dst_area_id.as_base_64(),
3429 mem_props.temporal_depth,
3430 upstream_areas.clone(),
3431 Some(lifecycle_config),
3432 mem_props.mp_learning_enabled,
3433 );
3434 self.configure_memory_scan_on_executor(&*exec, dst_area_id);
3435 } else {
3436 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
3437 }
3438 } else {
3439 debug!(
3440 target: "feagi-bdu",
3441 "Skipping plasticity registration: no memory properties for area {}",
3442 dst_area_id.as_base_64()
3443 );
3444 }
3445 } else {
3446 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
3447 }
3448 } else {
3449 warn!(
3450 target: "feagi-bdu",
3451 "PlasticityExecutor not available; memory area {} not registered",
3452 dst_area_id.as_base_64()
3453 );
3454 }
3455
3456 #[cfg(not(feature = "plasticity"))]
3457 {
3458 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
3459 }
3460 } else {
3461 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
3463
3464 let destination_is_memory = self
3465 .cortical_areas
3466 .get(dst_area_id)
3467 .is_some_and(|area| matches!(area.cortical_type, CorticalAreaType::Memory(_)));
3468 if destination_is_memory {
3469 self.teardown_owned_memory_twin_for_mapping(dst_area_id, src_area_id)?;
3470 }
3471
3472 let mut npu = npu_arc.lock().unwrap();
3474 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
3475 }
3476
3477 debug!(
3478 target: "feagi-bdu",
3479 "Created {} new synapses: {} -> {}",
3480 synapse_count,
3481 src_area_id,
3482 dst_area_id
3483 );
3484
3485 if pruned_synapse_count > 0 || synapse_count == 0 {
3488 let mut npu = npu_arc.lock().unwrap();
3489 npu.rebuild_synapse_index();
3490 info!(
3491 target: "feagi-bdu",
3492 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
3493 src_area_id,
3494 dst_area_id,
3495 pruned_synapse_count,
3496 synapse_count
3497 );
3498 } else {
3499 debug!(
3500 target: "feagi-bdu",
3501 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
3502 src_area_id,
3503 dst_area_id,
3504 synapse_count
3505 );
3506 }
3507
3508 {
3510 let npu = npu_arc.lock().unwrap();
3511 let fresh_count = npu.get_synapse_count();
3512 self.cached_synapse_count
3513 .store(fresh_count, Ordering::Relaxed);
3514 }
3515
3516 Ok(synapse_count)
3517 }
3518
3519 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
3521 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
3522 }
3523
3524 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
3525 v.as_u64()
3526 .map(|n| n as usize)
3527 .or_else(|| v.as_f64().map(|f| f as usize))
3528 }
3529
3530 #[allow(clippy::too_many_arguments)]
3532 fn register_stdp_mapping_for_rule(
3533 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3534 src_area_id: &CorticalID,
3535 dst_area_id: &CorticalID,
3536 src_cortical_idx: u32,
3537 dst_cortical_idx: u32,
3538 rule_obj: &serde_json::Map<String, serde_json::Value>,
3539 bidirectional_stdp: bool,
3540 synapse_psp: f32,
3541 synapse_type: feagi_npu_neural::SynapseType,
3542 ) -> BduResult<()> {
3543 let plasticity_window = rule_obj
3544 .get("plasticity_window")
3545 .and_then(Self::json_number_as_usize_for_stdp)
3546 .ok_or_else(|| {
3547 BduError::Internal(format!(
3548 "Missing plasticity_window in plastic mapping rule {} -> {}",
3549 src_area_id, dst_area_id
3550 ))
3551 })?;
3552 let plasticity_constant = rule_obj
3553 .get("plasticity_constant")
3554 .and_then(Self::json_number_as_i64_for_stdp)
3555 .ok_or_else(|| {
3556 BduError::Internal(format!(
3557 "Missing plasticity_constant in plastic mapping rule {} -> {}",
3558 src_area_id, dst_area_id
3559 ))
3560 })?;
3561 let ltp_i64 = rule_obj
3562 .get("ltp_multiplier")
3563 .and_then(Self::json_number_as_i64_for_stdp)
3564 .ok_or_else(|| {
3565 BduError::Internal(format!(
3566 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
3567 src_area_id, dst_area_id
3568 ))
3569 })?;
3570 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
3571 BduError::Internal(format!(
3572 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
3573 i8::MIN,
3574 i8::MAX,
3575 ltp_i64,
3576 src_area_id,
3577 dst_area_id
3578 ))
3579 })?;
3580 let ltd_i64 = rule_obj
3581 .get("ltd_multiplier")
3582 .and_then(Self::json_number_as_i64_for_stdp)
3583 .ok_or_else(|| {
3584 BduError::Internal(format!(
3585 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
3586 src_area_id, dst_area_id
3587 ))
3588 })?;
3589 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
3590 BduError::Internal(format!(
3591 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
3592 i8::MIN,
3593 i8::MAX,
3594 ltd_i64,
3595 src_area_id,
3596 dst_area_id
3597 ))
3598 })?;
3599
3600 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
3604 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
3605 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
3606 }
3607 Some(s) if s.eq_ignore_ascii_case("stdp") => {
3608 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
3609 }
3610 Some(s) if s.eq_ignore_ascii_case("off") => {
3611 feagi_npu_burst_engine::npu::PlasticityMode::Off
3612 }
3613 Some(other) => {
3614 return Err(BduError::Internal(format!(
3615 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
3616 other, src_area_id, dst_area_id
3617 )));
3618 }
3619 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
3620 };
3621
3622 let eligibility_decay_bursts = rule_obj
3624 .get("eligibility_decay_bursts")
3625 .and_then(|v| v.as_u64())
3626 .map(|n| n as u32)
3627 .unwrap_or(0);
3628 let reward_source_area_id = rule_obj
3629 .get("reward_source_area")
3630 .and_then(|v| v.as_str())
3631 .map(str::to_string);
3632 let punishment_source_area_id = rule_obj
3633 .get("punishment_source_area")
3634 .and_then(|v| v.as_str())
3635 .map(str::to_string);
3636
3637 let max_weight_provided = rule_obj.get("max_weight").is_some()
3642 && !rule_obj
3643 .get("max_weight")
3644 .map(|v| v.is_null())
3645 .unwrap_or(true);
3646 let max_weight: f32 = if max_weight_provided {
3647 let raw = rule_obj
3648 .get("max_weight")
3649 .and_then(|v| v.as_f64())
3650 .ok_or_else(|| {
3651 BduError::Internal(format!(
3652 "max_weight must be a number on mapping {} -> {}",
3653 src_area_id, dst_area_id
3654 ))
3655 })?;
3656 if raw.is_nan() || raw <= 0.0 {
3657 return Err(BduError::Internal(format!(
3658 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
3659 raw, src_area_id, dst_area_id
3660 )));
3661 }
3662 raw as f32
3663 } else {
3664 f32::INFINITY
3665 };
3666
3667 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
3670 && !rule_obj
3671 .get("plasticity_eta")
3672 .map(|v| v.is_null())
3673 .unwrap_or(true);
3674 let plasticity_eta: f32 = if plasticity_eta_provided {
3675 let raw = rule_obj
3676 .get("plasticity_eta")
3677 .and_then(|v| v.as_f64())
3678 .ok_or_else(|| {
3679 BduError::Internal(format!(
3680 "plasticity_eta must be a number on mapping {} -> {}",
3681 src_area_id, dst_area_id
3682 ))
3683 })?;
3684 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
3685 return Err(BduError::Internal(format!(
3686 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
3687 raw, src_area_id, dst_area_id
3688 )));
3689 }
3690 raw as f32
3691 } else {
3692 1.0
3693 };
3694
3695 if !matches!(
3697 plasticity_mode,
3698 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
3699 ) && (reward_source_area_id.is_some()
3700 || punishment_source_area_id.is_some()
3701 || eligibility_decay_bursts != 0)
3702 {
3703 return Err(BduError::Internal(format!(
3704 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
3705 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
3706 src_area_id, dst_area_id
3707 )));
3708 }
3709
3710 if matches!(
3714 plasticity_mode,
3715 feagi_npu_burst_engine::npu::PlasticityMode::Off
3716 ) && max_weight_provided
3717 {
3718 return Err(BduError::Internal(format!(
3719 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
3720 src_area_id, dst_area_id
3721 )));
3722 }
3723
3724 if matches!(
3725 plasticity_mode,
3726 feagi_npu_burst_engine::npu::PlasticityMode::Off
3727 ) && plasticity_eta_provided
3728 {
3729 return Err(BduError::Internal(format!(
3730 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
3731 src_area_id, dst_area_id
3732 )));
3733 }
3734
3735 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
3736 let mut npu_lock = npu
3737 .lock()
3738 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3739 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
3740
3741 let resolve_optional_area =
3746 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
3747 let Some(name) = name_opt else {
3748 return Ok(None);
3749 };
3750 match npu_lock.get_cortical_area_id(name.as_str()) {
3751 Some(idx) => Ok(Some(idx)),
3752 None => Err(BduError::Internal(format!(
3753 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
3754 label, name, src_area_id, dst_area_id
3755 ))),
3756 }
3757 };
3758 let reward_source_area =
3759 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
3760 let punishment_source_area =
3761 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
3762
3763 if matches!(
3765 plasticity_mode,
3766 feagi_npu_burst_engine::npu::PlasticityMode::Off
3767 ) {
3768 return Ok(());
3769 }
3770
3771 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
3772 plasticity_window,
3773 plasticity_constant,
3774 ltp_multiplier,
3775 ltd_multiplier,
3776 bidirectional_stdp,
3777 associative_memory_mapping: rule_obj
3778 .get("morphology_id")
3779 .and_then(|value| value.as_str())
3780 == Some("associative_memory"),
3781 synapse_psp,
3782 synapse_type,
3783 plasticity_mode,
3784 eligibility_decay_bursts,
3785 reward_source_area,
3786 punishment_source_area,
3787 max_weight,
3788 plasticity_eta,
3789 };
3790
3791 npu_lock
3792 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
3793 .map_err(|e| {
3794 BduError::Internal(format!(
3795 "Failed to register STDP mapping {} -> {}: {}",
3796 src_area_id, dst_area_id, e
3797 ))
3798 })?;
3799
3800 let mut areas_to_track: Vec<(u32, usize)> = vec![
3804 (src_cortical_idx, plasticity_window),
3805 (dst_cortical_idx, plasticity_window),
3806 ];
3807 if let Some(area) = reward_source_area {
3808 areas_to_track.push((area, 1));
3809 }
3810 if let Some(area) = punishment_source_area {
3811 areas_to_track.push((area, 1));
3812 }
3813
3814 let existing_configs = npu_lock.get_all_fire_ledger_configs();
3815 for (area_idx, required_depth) in areas_to_track {
3816 let existing = existing_configs
3817 .iter()
3818 .find(|(idx, _)| *idx == area_idx)
3819 .map(|(_, w)| *w)
3820 .unwrap_or(0);
3821 let resolved = existing.max(required_depth);
3822 if resolved != existing {
3823 npu_lock
3824 .configure_fire_ledger_window(area_idx, resolved)
3825 .map_err(|e| {
3826 BduError::Internal(format!(
3827 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
3828 area_idx, resolved, e
3829 ))
3830 })?;
3831 }
3832 }
3833
3834 Ok(())
3835 }
3836
3837 fn resolve_synapse_params_for_rule(
3839 &self,
3840 src_area_id: &CorticalID,
3841 rule: &serde_json::Value,
3842 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
3843 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3845 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3846 })?;
3847
3848 let (weight, synapse_type) = {
3850 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
3851 if let Some(i) = v.as_i64() {
3852 return Some(i as f64);
3853 }
3854 v.as_f64()
3855 };
3856
3857 let mult: f64 = if let Some(obj) = rule.as_object() {
3858 obj.get("postSynapticCurrent_multiplier")
3859 .and_then(parse_f64)
3860 .unwrap_or(1.0)
3861 } else if let Some(arr) = rule.as_array() {
3862 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
3863 } else {
3864 128.0
3865 };
3866
3867 if mult < 0.0 {
3868 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
3869 } else {
3870 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
3871 }
3872 };
3873
3874 use crate::models::cortical_area::CorticalAreaExt;
3876 let psp_f32 = src_area.postsynaptic_current();
3877
3878 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
3879 obj.get("synaptic_delay_bursts")
3880 .and_then(|v| v.as_u64())
3881 .map(|d| u8::try_from(d).unwrap_or(1))
3882 .unwrap_or(1)
3883 } else if let Some(arr) = rule.as_array() {
3884 arr.get(8)
3885 .and_then(|v| v.as_u64())
3886 .map(|d| u8::try_from(d).unwrap_or(1))
3887 .unwrap_or(1)
3888 } else {
3889 1
3890 };
3891 if delay_bursts < 1 {
3892 return Err(crate::types::BduError::Internal(format!(
3893 "synaptic_delay_bursts must be >= 1 (src area {})",
3894 src_area_id.as_base_64()
3895 )));
3896 }
3897
3898 tracing::debug!(
3899 target: "feagi-bdu",
3900 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
3901 src_area_id.as_base_64(),
3902 weight,
3903 psp_f32,
3904 synapse_type,
3905 delay_bursts
3906 );
3907
3908 Ok((weight, psp_f32, synapse_type, delay_bursts))
3909 }
3910
3911 fn apply_cortical_mapping_for_pair(
3913 &mut self,
3914 src_area_id: &CorticalID,
3915 dst_area_id: &CorticalID,
3916 ) -> BduResult<usize> {
3917 let rules = {
3923 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3924 crate::types::BduError::InvalidArea(format!(
3925 "Source area not found: {}",
3926 src_area_id
3927 ))
3928 })?;
3929
3930 let Some(mapping_dst) = src_area
3931 .properties
3932 .get("cortical_mapping_dst")
3933 .and_then(|v| v.as_object())
3934 else {
3935 return Ok(0);
3936 };
3937
3938 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
3939 else {
3940 return Ok(0);
3941 };
3942
3943 rules.clone()
3944 }; if rules.is_empty() {
3947 return Ok(0);
3948 }
3949
3950 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3952 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
3953 })?;
3954 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3955 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
3956 })?;
3957
3958 let npu_arc = self
3960 .npu
3961 .as_ref()
3962 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
3963 .clone();
3964
3965 tracing::debug!(
3966 target: "feagi-bdu",
3967 "Applying {} mapping rule(s) for {} -> {}",
3968 rules.len(),
3969 src_area_id,
3970 dst_area_id
3971 );
3972 let mut total_synapses = 0;
3974 for rule in &rules {
3975 let morphology_id = if let Some(rule_obj) = rule.as_object() {
3976 rule_obj
3977 .get("morphology_id")
3978 .and_then(|v| v.as_str())
3979 .unwrap_or("unknown")
3980 .to_string()
3981 } else if let Some(rule_arr) = rule.as_array() {
3982 rule_arr
3983 .first()
3984 .and_then(|v| v.as_str())
3985 .unwrap_or("unknown")
3986 .to_string()
3987 } else {
3988 "unknown".to_string()
3989 };
3990
3991 let rule_keys: Vec<String> = rule
3992 .as_object()
3993 .map(|obj| obj.keys().cloned().collect())
3994 .unwrap_or_default();
3995
3996 let mut plasticity_flag = rule
3998 .as_object()
3999 .and_then(|obj| obj.get("plasticity_flag"))
4000 .and_then(|v| v.as_bool())
4001 .unwrap_or(false);
4002 if morphology_id == "associative_memory" {
4003 plasticity_flag = true;
4004 }
4005 if plasticity_flag {
4006 let Some(rule_obj) = rule.as_object() else {
4007 return Err(crate::types::BduError::InvalidMorphology(
4008 "Plasticity mapping rule must be an object format".to_string(),
4009 ));
4010 };
4011 let (_weight, psp, synapse_type, _delay_bursts) =
4012 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
4013 let bidirectional_stdp = false;
4016 if let Err(e) = Self::register_stdp_mapping_for_rule(
4017 &npu_arc,
4018 src_area_id,
4019 dst_area_id,
4020 src_cortical_idx,
4021 dst_cortical_idx,
4022 rule_obj,
4023 bidirectional_stdp,
4024 psp,
4025 synapse_type,
4026 ) {
4027 tracing::error!(
4028 target: "feagi-bdu",
4029 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
4030 src_area_id,
4031 dst_area_id,
4032 morphology_id,
4033 rule_keys,
4034 e
4035 );
4036 return Err(e);
4037 }
4038 }
4039
4040 if let Some(gate_area_str) = rule
4044 .as_object()
4045 .and_then(|obj| obj.get("gate_source_area"))
4046 .and_then(|v| v.as_str())
4047 {
4048 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
4049 crate::types::BduError::Internal(format!(
4050 "Invalid gate_source_area '{}' on mapping {} -> {}",
4051 gate_area_str, src_area_id, dst_area_id
4052 ))
4053 })?;
4054 let gate_cortical_idx =
4055 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
4056 crate::types::BduError::Internal(format!(
4057 "Unknown gate_source_area '{}' on mapping {} -> {}",
4058 gate_area_str, src_area_id, dst_area_id
4059 ))
4060 })?;
4061
4062 let mut npu_lock = npu_arc.lock().map_err(|_| {
4063 crate::types::BduError::Internal(
4064 "Failed to acquire NPU lock for gate registration".to_string(),
4065 )
4066 })?;
4067 if let Err(e) = npu_lock.register_gate_mapping(
4068 src_cortical_idx,
4069 dst_cortical_idx,
4070 *gate_cortical_idx,
4071 ) {
4072 tracing::error!(
4073 target: "feagi-bdu",
4074 "Gate mapping registration failed for {} -> {} (gate={}): {}",
4075 src_area_id,
4076 dst_area_id,
4077 gate_area_str,
4078 e
4079 );
4080 return Err(crate::types::BduError::Internal(format!(
4081 "Gate registration failed: {}",
4082 e
4083 )));
4084 }
4085 }
4086
4087 let synapse_count = match self.apply_single_morphology_rule(
4089 src_area_id,
4090 dst_area_id,
4091 rule,
4092 ) {
4093 Ok(count) => count,
4094 Err(e) => {
4095 tracing::error!(
4096 target: "feagi-bdu",
4097 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
4098 src_area_id,
4099 dst_area_id,
4100 morphology_id,
4101 rule_keys,
4102 e
4103 );
4104 return Err(e);
4105 }
4106 };
4107 total_synapses += synapse_count;
4108 tracing::debug!(
4109 target: "feagi-bdu",
4110 "Rule {} created {} synapses for {} -> {}",
4111 morphology_id,
4112 synapse_count,
4113 src_area_id,
4114 dst_area_id
4115 );
4116 }
4117
4118 Ok(total_synapses)
4119 }
4120
4121 #[allow(clippy::too_many_arguments)]
4135 fn apply_function_morphology(
4136 &self,
4137 morphology_id: &str,
4138 rule: &serde_json::Value,
4139 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4140 npu: &mut feagi_npu_burst_engine::DynamicNPU,
4141 src_area_id: &CorticalID,
4142 dst_area_id: &CorticalID,
4143 src_idx: u32,
4144 dst_idx: u32,
4145 weight: f32,
4146 psp: f32,
4147 synapse_attractivity: u8,
4148 synapse_type: feagi_npu_neural::SynapseType,
4149 delay_bursts: u8,
4150 ) -> BduResult<usize> {
4151 match morphology_id {
4152 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
4153 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4155 crate::types::BduError::InvalidArea(format!(
4156 "Source area not found: {}",
4157 src_area_id
4158 ))
4159 })?;
4160 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4161 crate::types::BduError::InvalidArea(format!(
4162 "Destination area not found: {}",
4163 dst_area_id
4164 ))
4165 })?;
4166
4167 let src_dimensions = (
4168 src_area.dimensions.width as usize,
4169 src_area.dimensions.height as usize,
4170 src_area.dimensions.depth as usize,
4171 );
4172 let dst_dimensions = (
4173 dst_area.dimensions.width as usize,
4174 dst_area.dimensions.height as usize,
4175 dst_area.dimensions.depth as usize,
4176 );
4177
4178 let transpose = match morphology_id {
4181 "transpose_xy" => Some((1, 0, 2)),
4182 "transpose_yz" => Some((0, 2, 1)),
4183 "transpose_xz" => Some((2, 1, 0)),
4184 _ => None,
4185 };
4186
4187 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
4188 let count = apply_projector_morphology_with_dimensions(
4189 npu,
4190 src_idx,
4191 dst_idx,
4192 src_dimensions,
4193 dst_dimensions,
4194 transpose,
4195 None, weight,
4197 psp,
4198 synapse_attractivity,
4199 synapse_type,
4200 0,
4201 delay_bursts,
4202 )?;
4203 npu.rebuild_synapse_index();
4205 Ok(count as usize)
4206 }
4207 "centered_projector" => {
4208 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4209 crate::types::BduError::InvalidArea(format!(
4210 "Source area not found: {}",
4211 src_area_id
4212 ))
4213 })?;
4214 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4215 crate::types::BduError::InvalidArea(format!(
4216 "Destination area not found: {}",
4217 dst_area_id
4218 ))
4219 })?;
4220
4221 let src_dimensions = (
4222 src_area.dimensions.width as usize,
4223 src_area.dimensions.height as usize,
4224 src_area.dimensions.depth as usize,
4225 );
4226 let dst_dimensions = (
4227 dst_area.dimensions.width as usize,
4228 dst_area.dimensions.height as usize,
4229 dst_area.dimensions.depth as usize,
4230 );
4231
4232 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
4233 npu,
4234 src_idx,
4235 dst_idx,
4236 src_dimensions,
4237 dst_dimensions,
4238 weight,
4239 psp,
4240 synapse_attractivity,
4241 synapse_type,
4242 delay_bursts,
4243 )?;
4244 if count > 0 {
4245 npu.rebuild_synapse_index();
4246 }
4247 Ok(count as usize)
4248 }
4249 "episodic_memory" => {
4250 use tracing::trace;
4253 trace!(
4254 target: "feagi-bdu",
4255 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
4256 src_idx, dst_idx
4257 );
4258 Ok(0)
4259 }
4260 "episodic_scan" => {
4261 use tracing::trace;
4262 trace!(
4263 target: "feagi-bdu",
4264 "Episodic scan morphology: {} -> {} (no physical synapses, plasticity-driven)",
4265 src_idx, dst_idx
4266 );
4267 Ok(0)
4268 }
4269 "memory_replay" => {
4270 use tracing::trace;
4272 trace!(
4273 target: "feagi-bdu",
4274 "Memory replay morphology: {} -> {} (no physical synapses)",
4275 src_idx, dst_idx
4276 );
4277 Ok(0)
4278 }
4279 "associative_memory" => {
4280 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4284 crate::types::BduError::InvalidArea(format!(
4285 "Source area not found: {}",
4286 src_area_id
4287 ))
4288 })?;
4289 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4290 crate::types::BduError::InvalidArea(format!(
4291 "Destination area not found: {}",
4292 dst_area_id
4293 ))
4294 })?;
4295
4296 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
4297 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
4298 {
4299 let src_dimensions = (
4300 src_area.dimensions.width as usize,
4301 src_area.dimensions.height as usize,
4302 src_area.dimensions.depth as usize,
4303 );
4304 let dst_dimensions = (
4305 dst_area.dimensions.width as usize,
4306 dst_area.dimensions.height as usize,
4307 dst_area.dimensions.depth as usize,
4308 );
4309 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
4310 let count = apply_projector_morphology_with_dimensions(
4311 npu,
4312 src_idx,
4313 dst_idx,
4314 src_dimensions,
4315 dst_dimensions,
4316 None,
4317 None,
4318 weight,
4319 psp,
4320 synapse_attractivity,
4321 synapse_type,
4322 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
4323 delay_bursts,
4324 )?;
4325 npu.rebuild_synapse_index();
4326 Ok(count as usize)
4327 } else {
4328 Ok(0)
4329 }
4330 }
4331 "block_to_block" => {
4332 tracing::warn!(
4333 target: "feagi-bdu",
4334 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
4335 src_idx, dst_idx
4336 );
4337 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4339 crate::types::BduError::InvalidArea(format!(
4340 "Source area not found: {}",
4341 src_area_id
4342 ))
4343 })?;
4344 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4345 crate::types::BduError::InvalidArea(format!(
4346 "Destination area not found: {}",
4347 dst_area_id
4348 ))
4349 })?;
4350
4351 let src_dimensions = (
4352 src_area.dimensions.width as usize,
4353 src_area.dimensions.height as usize,
4354 src_area.dimensions.depth as usize,
4355 );
4356 let dst_dimensions = (
4357 dst_area.dimensions.width as usize,
4358 dst_area.dimensions.height as usize,
4359 dst_area.dimensions.depth as usize,
4360 );
4361
4362 let scalar = if let Some(obj) = rule.as_object() {
4364 if let Some(scalar_arr) =
4366 obj.get("morphology_scalar").and_then(|v| v.as_array())
4367 {
4368 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
4370 } else {
4371 1 }
4373 } else if let Some(arr) = rule.as_array() {
4374 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
4376 } else {
4377 1 };
4379
4380 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
4383 let count = if estimated_neurons > 100_000 {
4384 let _ = npu;
4386
4387 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
4388 npu_arc,
4389 src_idx,
4390 dst_idx,
4391 src_dimensions,
4392 dst_dimensions,
4393 scalar, weight,
4395 psp,
4396 synapse_attractivity,
4397 synapse_type,
4398 delay_bursts,
4399 )? as usize
4400 } else {
4401 tracing::warn!(
4403 target: "feagi-bdu",
4404 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
4405 src_idx, dst_idx, src_dimensions, dst_dimensions
4406 );
4407 let count =
4408 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
4409 npu,
4410 src_idx,
4411 dst_idx,
4412 src_dimensions,
4413 dst_dimensions,
4414 scalar, weight,
4416 psp,
4417 synapse_attractivity,
4418 synapse_type,
4419 delay_bursts,
4420 )? as usize;
4421 tracing::warn!(
4422 target: "feagi-bdu",
4423 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
4424 count
4425 );
4426 if count > 0 {
4428 npu.rebuild_synapse_index();
4429 }
4430 count
4431 };
4432
4433 if count > 0 && estimated_neurons > 100_000 {
4435 let mut npu_lock = npu_arc.lock().unwrap();
4436 npu_lock.rebuild_synapse_index();
4437 }
4438
4439 Ok(count)
4440 }
4441 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
4442 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
4443 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4444 crate::types::BduError::InvalidArea(format!(
4445 "Source area not found: {}",
4446 src_area_id
4447 ))
4448 })?;
4449 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4450 crate::types::BduError::InvalidArea(format!(
4451 "Destination area not found: {}",
4452 dst_area_id
4453 ))
4454 })?;
4455
4456 let src_dimensions = (
4457 src_area.dimensions.width as usize,
4458 src_area.dimensions.height as usize,
4459 src_area.dimensions.depth as usize,
4460 );
4461 let dst_dimensions = (
4462 dst_area.dimensions.width as usize,
4463 dst_area.dimensions.height as usize,
4464 dst_area.dimensions.depth as usize,
4465 );
4466
4467 let (axis, mode) = match morphology_id {
4468 "bitmask_encoder_x" => (
4469 crate::connectivity::core_morphologies::BitmaskAxis::X,
4470 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4471 ),
4472 "bitmask_encoder_y" => (
4473 crate::connectivity::core_morphologies::BitmaskAxis::Y,
4474 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4475 ),
4476 "bitmask_encoder_z" => (
4477 crate::connectivity::core_morphologies::BitmaskAxis::Z,
4478 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4479 ),
4480 "bitmask_decoder_x" => (
4481 crate::connectivity::core_morphologies::BitmaskAxis::X,
4482 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4483 ),
4484 "bitmask_decoder_y" => (
4485 crate::connectivity::core_morphologies::BitmaskAxis::Y,
4486 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4487 ),
4488 "bitmask_decoder_z" => (
4489 crate::connectivity::core_morphologies::BitmaskAxis::Z,
4490 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4491 ),
4492 _ => unreachable!("matched bitmask morphology above"),
4493 };
4494
4495 let count =
4496 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
4497 npu,
4498 src_idx,
4499 dst_idx,
4500 src_dimensions,
4501 dst_dimensions,
4502 axis,
4503 mode,
4504 weight,
4505 psp,
4506 synapse_attractivity,
4507 synapse_type,
4508 delay_bursts,
4509 )?;
4510 if count > 0 {
4511 npu.rebuild_synapse_index();
4512 }
4513 Ok(count as usize)
4514 }
4515 "sweeper" => {
4516 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4517 crate::types::BduError::InvalidArea(format!(
4518 "Destination area not found: {}",
4519 dst_area_id
4520 ))
4521 })?;
4522 let dst_dimensions = (
4523 dst_area.dimensions.width as usize,
4524 dst_area.dimensions.height as usize,
4525 dst_area.dimensions.depth as usize,
4526 );
4527
4528 let count =
4529 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
4530 npu,
4531 src_idx,
4532 dst_idx,
4533 dst_dimensions,
4534 weight,
4535 psp,
4536 synapse_attractivity,
4537 synapse_type,
4538 delay_bursts,
4539 )?;
4540 if count > 0 {
4541 npu.rebuild_synapse_index();
4542 }
4543 Ok(count as usize)
4544 }
4545 "last_to_first" => {
4546 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4547 crate::types::BduError::InvalidArea(format!(
4548 "Source area not found: {}",
4549 src_area_id
4550 ))
4551 })?;
4552 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4553 crate::types::BduError::InvalidArea(format!(
4554 "Destination area not found: {}",
4555 dst_area_id
4556 ))
4557 })?;
4558 let src_dimensions = (
4559 src_area.dimensions.width as usize,
4560 src_area.dimensions.height as usize,
4561 src_area.dimensions.depth as usize,
4562 );
4563 let dst_dimensions = (
4564 dst_area.dimensions.width as usize,
4565 dst_area.dimensions.height as usize,
4566 dst_area.dimensions.depth as usize,
4567 );
4568
4569 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
4570 npu,
4571 src_idx,
4572 dst_idx,
4573 src_dimensions,
4574 dst_dimensions,
4575 weight,
4576 psp,
4577 synapse_attractivity,
4578 synapse_type,
4579 delay_bursts,
4580 )?;
4581 if count > 0 {
4582 npu.rebuild_synapse_index();
4583 }
4584 Ok(count as usize)
4585 }
4586 "first_to_last" => {
4587 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4588 crate::types::BduError::InvalidArea(format!(
4589 "Source area not found: {}",
4590 src_area_id
4591 ))
4592 })?;
4593 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4594 crate::types::BduError::InvalidArea(format!(
4595 "Destination area not found: {}",
4596 dst_area_id
4597 ))
4598 })?;
4599 let src_dimensions = (
4600 src_area.dimensions.width as usize,
4601 src_area.dimensions.height as usize,
4602 src_area.dimensions.depth as usize,
4603 );
4604 let dst_dimensions = (
4605 dst_area.dimensions.width as usize,
4606 dst_area.dimensions.height as usize,
4607 dst_area.dimensions.depth as usize,
4608 );
4609
4610 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
4611 npu,
4612 src_idx,
4613 dst_idx,
4614 src_dimensions,
4615 dst_dimensions,
4616 weight,
4617 psp,
4618 synapse_attractivity,
4619 synapse_type,
4620 delay_bursts,
4621 )?;
4622 if count > 0 {
4623 npu.rebuild_synapse_index();
4624 }
4625 Ok(count as usize)
4626 }
4627 "rotator_z" => {
4628 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4629 crate::types::BduError::InvalidArea(format!(
4630 "Source area not found: {}",
4631 src_area_id
4632 ))
4633 })?;
4634 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4635 crate::types::BduError::InvalidArea(format!(
4636 "Destination area not found: {}",
4637 dst_area_id
4638 ))
4639 })?;
4640 let src_dimensions = (
4641 src_area.dimensions.width as usize,
4642 src_area.dimensions.height as usize,
4643 src_area.dimensions.depth as usize,
4644 );
4645 let dst_dimensions = (
4646 dst_area.dimensions.width as usize,
4647 dst_area.dimensions.height as usize,
4648 dst_area.dimensions.depth as usize,
4649 );
4650
4651 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
4652 npu,
4653 src_idx,
4654 dst_idx,
4655 src_dimensions,
4656 dst_dimensions,
4657 weight,
4658 psp,
4659 synapse_attractivity,
4660 synapse_type,
4661 delay_bursts,
4662 )?;
4663 if count > 0 {
4664 npu.rebuild_synapse_index();
4665 }
4666 Ok(count as usize)
4667 }
4668 _ => {
4669 use tracing::debug;
4672 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
4673 Ok(0)
4674 }
4675 }
4676 }
4677
4678 fn apply_single_morphology_rule(
4680 &mut self,
4681 src_area_id: &CorticalID,
4682 dst_area_id: &CorticalID,
4683 rule: &serde_json::Value,
4684 ) -> BduResult<usize> {
4685 let morphology_id = if let Some(arr) = rule.as_array() {
4687 arr.first().and_then(|v| v.as_str()).unwrap_or("")
4688 } else if let Some(obj) = rule.as_object() {
4689 obj.get("morphology_id")
4690 .and_then(|v| v.as_str())
4691 .unwrap_or("")
4692 } else {
4693 return Ok(0);
4694 };
4695
4696 if morphology_id.is_empty() {
4697 return Ok(0);
4698 }
4699
4700 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
4702 crate::types::BduError::InvalidMorphology(format!(
4703 "Morphology not found: {}",
4704 morphology_id
4705 ))
4706 })?;
4707
4708 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
4710 crate::types::BduError::InvalidArea(format!(
4711 "Source area ID not found: {}",
4712 src_area_id
4713 ))
4714 })?;
4715 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
4716 crate::types::BduError::InvalidArea(format!(
4717 "Destination area ID not found: {}",
4718 dst_area_id
4719 ))
4720 })?;
4721
4722 if let Some(ref npu_arc) = self.npu {
4724 let lock_start = std::time::Instant::now();
4725 let mut npu = npu_arc.lock().unwrap();
4726 let lock_wait = lock_start.elapsed();
4727 tracing::debug!(
4728 target: "feagi-bdu",
4729 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
4730 lock_wait.as_secs_f64() * 1000.0,
4731 src_area_id,
4732 dst_area_id,
4733 morphology_id
4734 );
4735
4736 let (weight, psp, synapse_type, delay_bursts) =
4737 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
4738
4739 let synapse_attractivity = if let Some(obj) = rule.as_object() {
4741 obj.get("synapse_attractivity")
4742 .and_then(|v| v.as_u64())
4743 .unwrap_or(100) as u8
4744 } else {
4745 100 };
4747
4748 match morphology.morphology_type {
4749 feagi_evolutionary::MorphologyType::Functions => {
4750 tracing::debug!(
4751 target: "feagi-bdu",
4752 "apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
4753 morphology_id
4754 );
4755 self.apply_function_morphology(
4758 morphology_id,
4759 rule,
4760 npu_arc,
4761 &mut npu,
4762 src_area_id,
4763 dst_area_id,
4764 *src_idx,
4765 *dst_idx,
4766 weight,
4767 psp,
4768 synapse_attractivity,
4769 synapse_type,
4770 delay_bursts,
4771 )
4772 }
4773 feagi_evolutionary::MorphologyType::Vectors => {
4774 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
4775
4776 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4778 crate::types::BduError::InvalidArea(format!(
4779 "Destination area not found: {}",
4780 dst_area_id
4781 ))
4782 })?;
4783
4784 let dst_dimensions = (
4785 dst_area.dimensions.width as usize,
4786 dst_area.dimensions.height as usize,
4787 dst_area.dimensions.depth as usize,
4788 );
4789
4790 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
4791 morphology.parameters
4792 {
4793 let vectors_tuples: Vec<(i32, i32, i32)> =
4795 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
4796
4797 let count = apply_vectors_morphology_with_dimensions(
4798 &mut npu,
4799 *src_idx,
4800 *dst_idx,
4801 vectors_tuples,
4802 dst_dimensions,
4803 weight, psp, synapse_attractivity, synapse_type,
4807 delay_bursts,
4808 )?;
4809 npu.rebuild_synapse_index();
4812 Ok(count as usize)
4813 } else {
4814 Ok(0)
4815 }
4816 }
4817 feagi_evolutionary::MorphologyType::Patterns => {
4818 use crate::connectivity::core_morphologies::apply_patterns_morphology;
4819 use crate::connectivity::rules::patterns::{
4820 Pattern3D, PatternElement as RulePatternElement,
4821 };
4822 use feagi_evolutionary::PatternElement as EvoPatternElement;
4823
4824 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
4825 morphology.parameters
4826 else {
4827 return Ok(0);
4828 };
4829
4830 let convert_element =
4831 |element: &EvoPatternElement|
4832 -> crate::types::BduResult<RulePatternElement> {
4833 match element {
4834 EvoPatternElement::Value(value) => {
4835 if *value < 0 {
4836 return Err(crate::types::BduError::InvalidMorphology(
4837 format!(
4838 "Pattern morphology {} contains negative voxel coordinate {}",
4839 morphology_id, value
4840 ),
4841 ));
4842 }
4843 Ok(RulePatternElement::Exact(*value))
4844 }
4845 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
4846 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
4847 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
4848 EvoPatternElement::DirectionPositive => {
4849 Ok(RulePatternElement::DirectionExclusive(
4850 crate::connectivity::rules::patterns::Direction::Positive,
4851 ))
4852 }
4853 EvoPatternElement::DirectionNegative => {
4854 Ok(RulePatternElement::DirectionExclusive(
4855 crate::connectivity::rules::patterns::Direction::Negative,
4856 ))
4857 }
4858 EvoPatternElement::DirectionPositiveInclusive => {
4859 Ok(RulePatternElement::DirectionInclusive(
4860 crate::connectivity::rules::patterns::Direction::Positive,
4861 ))
4862 }
4863 EvoPatternElement::DirectionNegativeInclusive => {
4864 Ok(RulePatternElement::DirectionInclusive(
4865 crate::connectivity::rules::patterns::Direction::Negative,
4866 ))
4867 }
4868 EvoPatternElement::Offset(off) => {
4869 Ok(RulePatternElement::Offset(*off))
4870 }
4871 EvoPatternElement::Range(lo, hi) => {
4872 Ok(RulePatternElement::Range(*lo, *hi))
4873 }
4874 EvoPatternElement::AbsoluteRange(lo, hi) => {
4875 Ok(RulePatternElement::AbsoluteRange(*lo, *hi))
4876 }
4877 }
4878 };
4879
4880 let mut converted_patterns = Vec::with_capacity(patterns.len());
4881 for pattern_pair in patterns {
4882 if pattern_pair.len() != 2 {
4883 return Err(crate::types::BduError::InvalidMorphology(format!(
4884 "Pattern morphology {} must contain [src, dst] pairs",
4885 morphology_id
4886 )));
4887 }
4888
4889 let src_pattern = &pattern_pair[0];
4890 let dst_pattern = &pattern_pair[1];
4891
4892 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
4893 return Err(crate::types::BduError::InvalidMorphology(format!(
4894 "Pattern morphology {} requires 3-axis patterns",
4895 morphology_id
4896 )));
4897 }
4898
4899 let src: Pattern3D = (
4900 convert_element(&src_pattern[0])?,
4901 convert_element(&src_pattern[1])?,
4902 convert_element(&src_pattern[2])?,
4903 );
4904 let dst: Pattern3D = (
4905 convert_element(&dst_pattern[0])?,
4906 convert_element(&dst_pattern[1])?,
4907 convert_element(&dst_pattern[2])?,
4908 );
4909
4910 converted_patterns.push((src, dst));
4911 }
4912
4913 let count = apply_patterns_morphology(
4914 &mut npu,
4915 *src_idx,
4916 *dst_idx,
4917 converted_patterns,
4918 weight,
4919 psp,
4920 synapse_attractivity,
4921 synapse_type,
4922 delay_bursts,
4923 )?;
4924 if count > 0 {
4925 npu.rebuild_synapse_index();
4926 }
4927 Ok(count as usize)
4928 }
4929 feagi_evolutionary::MorphologyType::Composite => {
4930 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
4931 morphology.parameters
4932 else {
4933 return Ok(0);
4934 };
4935
4936 if morphology_id != "tile" {
4937 use tracing::debug;
4938 debug!(
4939 target: "feagi-bdu",
4940 "Composite morphology {} not yet implemented",
4941 morphology_id
4942 );
4943 return Ok(0);
4944 }
4945
4946 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4947 crate::types::BduError::InvalidArea(format!(
4948 "Source area not found: {}",
4949 src_area_id
4950 ))
4951 })?;
4952 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4953 crate::types::BduError::InvalidArea(format!(
4954 "Destination area not found: {}",
4955 dst_area_id
4956 ))
4957 })?;
4958 let src_dimensions = (
4959 src_area.dimensions.width as usize,
4960 src_area.dimensions.height as usize,
4961 src_area.dimensions.depth as usize,
4962 );
4963 let dst_dimensions = (
4964 dst_area.dimensions.width as usize,
4965 dst_area.dimensions.height as usize,
4966 dst_area.dimensions.depth as usize,
4967 );
4968
4969 let count =
4970 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
4971 &mut npu,
4972 *src_idx,
4973 *dst_idx,
4974 src_dimensions,
4975 dst_dimensions,
4976 weight,
4977 psp,
4978 synapse_attractivity,
4979 synapse_type,
4980 delay_bursts,
4981 )?;
4982 if count > 0 {
4983 npu.rebuild_synapse_index();
4984 }
4985 Ok(count as usize)
4986 }
4987 }
4988 } else {
4989 Ok(0) }
4991 }
4992
4993 pub fn set_npu(
5006 &mut self,
5007 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
5008 ) {
5009 self.npu = Some(Arc::clone(&npu));
5010 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
5011
5012 #[cfg(not(feature = "wasm"))]
5015 {
5016 use feagi_state_manager::StateManager;
5017 let state_manager = StateManager::instance();
5018 let state_manager = state_manager.read();
5019 let core_state = state_manager.get_core_state();
5020 core_state.set_neuron_capacity(self.config.max_neurons as u32);
5022 core_state.set_synapse_capacity(self.config.max_synapses as u32);
5023 info!(
5024 target: "feagi-bdu",
5025 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
5026 self.config.max_neurons, self.config.max_synapses
5027 );
5028 }
5029
5030 let existing_area_count = self.cortical_id_to_idx.len();
5037 if existing_area_count > 0 {
5038 match npu.lock() {
5039 Ok(mut npu_lock) => {
5040 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
5041 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
5042 }
5043 info!(
5044 target: "feagi-bdu",
5045 "🔁 Backfilled {} cortical area registrations into NPU",
5046 existing_area_count
5047 );
5048 }
5049 Err(e) => {
5050 warn!(
5051 target: "feagi-bdu",
5052 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
5053 e
5054 );
5055 }
5056 }
5057 }
5058
5059 self.update_all_cached_stats();
5061 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
5062 self.get_neuron_count(), self.get_synapse_count());
5063 }
5064
5065 pub fn has_npu(&self) -> bool {
5067 self.npu.is_some()
5068 }
5069
5070 pub fn get_npu(
5077 &self,
5078 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
5079 {
5080 self.npu.as_ref()
5081 }
5082
5083 pub fn rebind_npu_runtime_after_connectome_apply(&mut self) -> BduResult<()> {
5089 #[cfg(feature = "plasticity")]
5090 if let Some(executor) = self.plasticity_executor.as_ref() {
5091 executor
5092 .lock()
5093 .map_err(|_| {
5094 BduError::Internal(
5095 "Failed to lock PlasticityExecutor for registration reset".to_string(),
5096 )
5097 })?
5098 .clear_memory_area_registrations()
5099 .map_err(BduError::Internal)?;
5100 }
5101 self.refresh_all_upstream_cortical_areas_from_mappings();
5102 self.rebuild_memory_twin_mappings()?;
5103 self.rebind_memory_twin_mappings_to_npu()?;
5104 self.rebind_stdp_mappings_to_npu()?;
5105 #[cfg(feature = "plasticity")]
5106 self.reregister_memory_areas_with_plasticity()?;
5107 Ok(())
5108 }
5109
5110 pub fn refresh_all_upstream_cortical_areas_from_mappings(&mut self) {
5116 let area_ids: Vec<CorticalID> = self.cortical_areas.keys().copied().collect();
5117 for area_id in area_ids {
5118 self.refresh_upstream_cortical_areas_from_mappings(&area_id);
5119 }
5120 }
5121
5122 pub fn rebuild_memory_twin_mappings(&mut self) -> BduResult<usize> {
5132 let jobs = self.collect_memory_twin_rebuild_jobs()?;
5133 let mut restored = 0usize;
5134 for (memory_id, upstream_id, known_twin) in jobs {
5135 match known_twin {
5136 Some(twin_id) => {
5137 self.restore_memory_twin_index(&memory_id, &upstream_id, &twin_id)?;
5138 restored += 1;
5139 }
5140 None => {
5141 self.ensure_memory_twin_area(&memory_id, &upstream_id)?;
5142 restored += 1;
5143 }
5144 }
5145 }
5146 if restored > 0 {
5147 info!(
5148 target: "feagi-bdu",
5149 "Rebuilt {} memory twin mapping(s) after connectome apply",
5150 restored
5151 );
5152 self.refresh_cortical_mappings_hash();
5153 }
5154 self.rebuild_scan_twin_mappings()?;
5155 Ok(restored)
5156 }
5157
5158 fn rebuild_scan_twin_mappings(&mut self) -> BduResult<usize> {
5159 let mut pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
5160 for (src_id, src_area) in &self.cortical_areas {
5161 if Self::area_is_memory(src_area) {
5162 continue;
5163 }
5164 let Some(dstmap) = src_area
5165 .properties
5166 .get("cortical_mapping_dst")
5167 .and_then(|value| value.as_object())
5168 else {
5169 continue;
5170 };
5171 for (dst_b64, rules) in dstmap {
5172 if !Self::mapping_rules_use_morphology(rules, "episodic_scan") {
5173 continue;
5174 }
5175 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
5176 continue;
5177 };
5178 pairs.push((memory_id, *src_id));
5179 }
5180 }
5181 let mut restored = 0usize;
5182 for (memory_id, field_id) in pairs {
5183 if self.ensure_scan_twin_area(&memory_id, &field_id).is_ok() {
5184 restored += 1;
5185 }
5186 }
5187 Ok(restored)
5188 }
5189
5190 fn collect_memory_twin_rebuild_jobs(
5191 &self,
5192 ) -> BduResult<Vec<(CorticalID, CorticalID, Option<CorticalID>)>> {
5193 let mut jobs: Vec<(CorticalID, CorticalID, Option<CorticalID>)> = Vec::new();
5194 let mut seen: HashSet<(CorticalID, CorticalID)> = HashSet::new();
5195
5196 for (memory_id, memory_area) in &self.cortical_areas {
5197 if !Self::area_is_memory(memory_area) {
5198 continue;
5199 }
5200 let Some(dstmap) = memory_area
5201 .properties
5202 .get("cortical_mapping_dst")
5203 .and_then(|value| value.as_object())
5204 else {
5205 continue;
5206 };
5207 for (dst_b64, rules) in dstmap {
5208 if !Self::mapping_rules_use_morphology(rules, "memory_replay") {
5209 continue;
5210 }
5211 let twin_id = match CorticalID::try_from_base_64(dst_b64) {
5212 Ok(id) => id,
5213 Err(_) => continue,
5214 };
5215 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
5216 continue;
5217 };
5218 let Some(upstream_b64) = twin_area
5219 .properties
5220 .get("memory_twin_of")
5221 .and_then(|value| value.as_str())
5222 else {
5223 continue;
5224 };
5225 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5226 BduError::InvalidArea(format!(
5227 "Invalid memory_twin_of '{}' on {}: {}",
5228 upstream_b64,
5229 twin_id.as_base_64(),
5230 error
5231 ))
5232 })?;
5233 if seen.insert((*memory_id, upstream_id)) {
5234 jobs.push((*memory_id, upstream_id, Some(twin_id)));
5235 }
5236 }
5237 }
5238
5239 for (twin_id, twin_area) in &self.cortical_areas {
5240 let Some(upstream_b64) = twin_area
5241 .properties
5242 .get("memory_twin_of")
5243 .and_then(|value| value.as_str())
5244 else {
5245 continue;
5246 };
5247 let Some(memory_b64) = twin_area
5248 .properties
5249 .get("memory_twin_for")
5250 .and_then(|value| value.as_str())
5251 else {
5252 continue;
5253 };
5254 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5255 BduError::InvalidArea(format!(
5256 "Invalid memory_twin_of '{}' on {}: {}",
5257 upstream_b64,
5258 twin_id.as_base_64(),
5259 error
5260 ))
5261 })?;
5262 let memory_id = CorticalID::try_from_base_64(memory_b64).map_err(|error| {
5263 BduError::InvalidArea(format!(
5264 "Invalid memory_twin_for '{}' on {}: {}",
5265 memory_b64,
5266 twin_id.as_base_64(),
5267 error
5268 ))
5269 })?;
5270 if !self.cortical_areas.contains_key(&memory_id)
5271 || !self.cortical_areas.contains_key(&upstream_id)
5272 {
5273 continue;
5274 }
5275 if seen.insert((memory_id, upstream_id)) {
5276 jobs.push((memory_id, upstream_id, Some(*twin_id)));
5277 }
5278 }
5279
5280 let mut episodic_pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
5281 for (src_id, src_area) in &self.cortical_areas {
5282 if Self::area_is_memory(src_area) {
5283 continue;
5284 }
5285 let Some(dstmap) = src_area
5286 .properties
5287 .get("cortical_mapping_dst")
5288 .and_then(|value| value.as_object())
5289 else {
5290 continue;
5291 };
5292 for (dst_b64, rules) in dstmap {
5293 if !Self::mapping_rules_use_morphology(rules, "episodic_memory") {
5294 continue;
5295 }
5296 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
5297 continue;
5298 };
5299 let Some(memory_area) = self.cortical_areas.get(&memory_id) else {
5300 continue;
5301 };
5302 if !Self::area_is_memory(memory_area) {
5303 continue;
5304 }
5305 episodic_pairs.push((memory_id, *src_id));
5306 }
5307 }
5308
5309 for (memory_id, upstream_id) in episodic_pairs {
5310 if !seen.insert((memory_id, upstream_id)) {
5311 continue;
5312 }
5313 let indexed_twin = self
5314 .cortical_areas
5315 .get(&memory_id)
5316 .and_then(|area| area.properties.get("memory_twin_areas"))
5317 .and_then(|value| value.as_object())
5318 .and_then(|map| map.get(&upstream_id.as_base_64()))
5319 .and_then(|value| value.as_str())
5320 .and_then(|twin_b64| CorticalID::try_from_base_64(twin_b64).ok());
5321 if let Some(twin_id) = indexed_twin {
5322 jobs.push((memory_id, upstream_id, Some(twin_id)));
5323 continue;
5324 }
5325 if let Ok(hash_twin_id) = self.build_memory_twin_id(&memory_id, &upstream_id) {
5326 if self.cortical_areas.contains_key(&hash_twin_id) {
5327 jobs.push((memory_id, upstream_id, Some(hash_twin_id)));
5328 continue;
5329 }
5330 }
5331 jobs.push((memory_id, upstream_id, None));
5332 }
5333
5334 Ok(jobs)
5335 }
5336
5337 fn restore_memory_twin_index(
5338 &mut self,
5339 memory_area_id: &CorticalID,
5340 upstream_area_id: &CorticalID,
5341 twin_id: &CorticalID,
5342 ) -> BduResult<()> {
5343 let expected_source = upstream_area_id.as_base_64();
5344 let expected_target = memory_area_id.as_base_64();
5345 let Some(existing) = self.cortical_areas.get_mut(twin_id) else {
5346 return Err(BduError::InvalidArea(format!(
5347 "Twin area {} not found while restoring memory twin index",
5348 twin_id.as_base_64()
5349 )));
5350 };
5351 let existing_source = existing
5352 .properties
5353 .get("memory_twin_of")
5354 .and_then(|value| value.as_str())
5355 .map(ToString::to_string);
5356 let existing_target = existing
5357 .properties
5358 .get("memory_twin_for")
5359 .and_then(|value| value.as_str())
5360 .map(ToString::to_string);
5361 if existing_source.as_deref() != Some(expected_source.as_str())
5362 || existing_target.as_deref() != Some(expected_target.as_str())
5363 {
5364 existing.properties.insert(
5365 "memory_twin_of".to_string(),
5366 serde_json::json!(expected_source),
5367 );
5368 existing.properties.insert(
5369 "memory_twin_for".to_string(),
5370 serde_json::json!(expected_target),
5371 );
5372 }
5373 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, twin_id);
5374
5375 let has_replay = self
5376 .cortical_areas
5377 .get(memory_area_id)
5378 .and_then(|area| area.properties.get("cortical_mapping_dst"))
5379 .and_then(|value| value.as_object())
5380 .and_then(|map| map.get(&twin_id.as_base_64()))
5381 .is_some_and(|rules| Self::mapping_rules_use_morphology(rules, "memory_replay"));
5382 if !has_replay {
5383 self.ensure_memory_replay_mapping(memory_area_id, twin_id)?;
5384 }
5385 Ok(())
5386 }
5387
5388 fn area_is_memory(area: &CorticalArea) -> bool {
5389 matches!(area.cortical_type, CorticalAreaType::Memory(_))
5390 || area
5391 .properties
5392 .get("is_mem_type")
5393 .and_then(|value| value.as_bool())
5394 == Some(true)
5395 }
5396
5397 fn mapping_rules_use_morphology(rules: &serde_json::Value, morphology_id: &str) -> bool {
5398 rules.as_array().is_some_and(|arr| {
5399 arr.iter().any(|rule| {
5400 rule.as_object()
5401 .and_then(|obj| obj.get("morphology_id"))
5402 .and_then(|value| value.as_str())
5403 == Some(morphology_id)
5404 })
5405 })
5406 }
5407
5408 fn rebind_memory_twin_mappings_to_npu(&mut self) -> BduResult<()> {
5409 use crate::models::CorticalAreaExt;
5410
5411 let Some(npu_arc) = self.npu.clone() else {
5412 return Ok(());
5413 };
5414 let mut bindings: Vec<(u32, u32, u32, f32)> = Vec::new();
5415 for (memory_id, area) in &self.cortical_areas {
5416 if !matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
5417 continue;
5418 }
5419 let Some(twins) = area
5420 .properties
5421 .get("memory_twin_areas")
5422 .and_then(|value| value.as_object())
5423 else {
5424 continue;
5425 };
5426 let Some(&memory_idx) = self.cortical_id_to_idx.get(memory_id) else {
5427 continue;
5428 };
5429 for (upstream_b64, twin_val) in twins {
5430 let Some(twin_b64) = twin_val.as_str() else {
5431 return Err(BduError::InvalidArea(format!(
5432 "memory_twin_areas entry for {} is not a string",
5433 upstream_b64
5434 )));
5435 };
5436 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5437 BduError::InvalidArea(format!(
5438 "Invalid upstream cortical ID {}: {}",
5439 upstream_b64, error
5440 ))
5441 })?;
5442 let twin_id = CorticalID::try_from_base_64(twin_b64).map_err(|error| {
5443 BduError::InvalidArea(format!(
5444 "Invalid twin cortical ID {}: {}",
5445 twin_b64, error
5446 ))
5447 })?;
5448 let Some(&upstream_idx) = self.cortical_id_to_idx.get(&upstream_id) else {
5449 continue;
5450 };
5451 let Some(&twin_idx) = self.cortical_id_to_idx.get(&twin_id) else {
5452 continue;
5453 };
5454 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
5455 continue;
5456 };
5457 let potential =
5458 twin_area.firing_threshold() + twin_area.firing_threshold_increment();
5459 bindings.push((memory_idx, upstream_idx, twin_idx, potential));
5460 }
5461 }
5462 let mut npu = npu_arc.lock().unwrap();
5463 for (memory_idx, upstream_idx, twin_idx, potential) in bindings {
5464 npu.register_memory_twin_mapping(memory_idx, upstream_idx, twin_idx, potential);
5465 }
5466 Ok(())
5467 }
5468
5469 fn rebind_stdp_mappings_to_npu(&mut self) -> BduResult<()> {
5470 let Some(npu_arc) = self.npu.clone() else {
5471 return Ok(());
5472 };
5473 let mut jobs: Vec<(CorticalID, CorticalID, u32, u32, Vec<serde_json::Value>)> = Vec::new();
5474 for (src_id, src_area) in &self.cortical_areas {
5475 let Some(dst_map) = src_area
5476 .properties
5477 .get("cortical_mapping_dst")
5478 .and_then(|value| value.as_object())
5479 else {
5480 continue;
5481 };
5482 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
5483 continue;
5484 };
5485 for (dst_b64, rules) in dst_map {
5486 let dst_id = CorticalID::try_from_base_64(dst_b64).map_err(|error| {
5487 BduError::InvalidArea(format!(
5488 "Invalid destination cortical ID {}: {}",
5489 dst_b64, error
5490 ))
5491 })?;
5492 let Some(&dst_idx) = self.cortical_id_to_idx.get(&dst_id) else {
5493 continue;
5494 };
5495 let Some(rules_arr) = rules.as_array() else {
5496 continue;
5497 };
5498 jobs.push((*src_id, dst_id, src_idx, dst_idx, rules_arr.clone()));
5499 }
5500 }
5501
5502 for (src_id, dst_id, src_idx, dst_idx, rules) in jobs {
5503 for rule in &rules {
5504 let morphology_id = rule
5505 .as_object()
5506 .and_then(|obj| obj.get("morphology_id"))
5507 .and_then(|value| value.as_str())
5508 .unwrap_or("");
5509 let mut plasticity_flag = rule
5510 .as_object()
5511 .and_then(|obj| obj.get("plasticity_flag"))
5512 .and_then(|value| value.as_bool())
5513 .unwrap_or(false);
5514 if morphology_id == "associative_memory" {
5515 plasticity_flag = true;
5516 }
5517 if !plasticity_flag {
5518 continue;
5519 }
5520 let Some(rule_obj) = rule.as_object() else {
5521 return Err(BduError::InvalidMorphology(
5522 "Plasticity mapping rule must be an object format".to_string(),
5523 ));
5524 };
5525 let (_weight, psp, synapse_type, _delay) =
5526 self.resolve_synapse_params_for_rule(&src_id, rule)?;
5527 Self::register_stdp_mapping_for_rule(
5528 &npu_arc,
5529 &src_id,
5530 &dst_id,
5531 src_idx,
5532 dst_idx,
5533 rule_obj,
5534 false,
5535 psp,
5536 synapse_type,
5537 )?;
5538 }
5539 }
5540 Ok(())
5541 }
5542
5543 #[cfg(feature = "plasticity")]
5544 fn reregister_memory_areas_with_plasticity(&mut self) -> BduResult<()> {
5545 use feagi_evolutionary::extract_memory_properties;
5546 use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
5547
5548 let Some(executor) = self.plasticity_executor.clone() else {
5549 return Ok(());
5550 };
5551 let memory_ids: Vec<CorticalID> = self
5552 .cortical_areas
5553 .iter()
5554 .filter(|(_, area)| matches!(area.cortical_type, CorticalAreaType::Memory(_)))
5555 .map(|(id, _)| *id)
5556 .collect();
5557
5558 for memory_id in memory_ids {
5559 let Some(area) = self.cortical_areas.get(&memory_id) else {
5560 continue;
5561 };
5562 let Some(mem_props) = extract_memory_properties(&area.properties) else {
5563 continue;
5564 };
5565 let Some(&area_idx) = self.cortical_id_to_idx.get(&memory_id) else {
5566 continue;
5567 };
5568 let area_name = memory_id.as_base_64();
5569 let upstream_areas = self.get_episodic_memory_upstream_cortical_areas(&memory_id);
5570 let lifecycle_config = MemoryNeuronLifecycleConfig {
5571 initial_lifespan: mem_props.init_lifespan,
5572 lifespan_growth_rate: mem_props.lifespan_growth_rate,
5573 longterm_threshold: mem_props.longterm_threshold,
5574 max_reactivations: 1000,
5575 };
5576 let exec = executor.lock().map_err(|_| {
5577 BduError::Internal(
5578 "Failed to lock PlasticityExecutor for memory-area rebind".to_string(),
5579 )
5580 })?;
5581 exec.register_memory_area(
5582 area_idx,
5583 area_name,
5584 mem_props.temporal_depth,
5585 upstream_areas,
5586 Some(lifecycle_config),
5587 mem_props.mp_learning_enabled,
5588 );
5589 self.configure_memory_scan_on_executor(&*exec, &memory_id);
5590 }
5591 Ok(())
5592 }
5593
5594 #[cfg(feature = "plasticity")]
5595 pub(crate) fn configure_memory_scan_on_executor(
5596 &self,
5597 exec: &dyn feagi_npu_plasticity::PlasticityExecutor,
5598 memory_id: &CorticalID,
5599 ) {
5600 let Some(scan) = self.build_memory_scan_config(memory_id) else {
5601 if let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) {
5602 exec.configure_memory_scan(area_idx, None);
5603 }
5604 return;
5605 };
5606 let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) else {
5607 return;
5608 };
5609 exec.configure_memory_scan(area_idx, Some(scan));
5610 }
5611
5612 #[cfg(feature = "plasticity")]
5613 fn build_memory_scan_config(
5614 &self,
5615 memory_id: &CorticalID,
5616 ) -> Option<feagi_npu_plasticity::MemoryScanConfig> {
5617 use feagi_evolutionary::extract_memory_properties;
5618 use feagi_npu_plasticity::{MemoryScanConfig, MemoryScanSource, ScanKernel};
5619
5620 let memory_area = self.cortical_areas.get(memory_id)?;
5621 let mem_props = extract_memory_properties(&memory_area.properties)?;
5622 let training_mode = memory_area
5623 .properties
5624 .get("classifier_training_mode")
5625 .and_then(|value| value.as_str())
5626 .unwrap_or("kernel");
5627 if training_mode == "scanner" {
5628 return self.build_scanner_memory_scan_config(memory_id, memory_area, &mem_props);
5629 }
5630 if training_mode != "kernel" {
5631 return None;
5632 }
5633 let kernel_b64 = memory_area
5634 .properties
5635 .get("classifier_kernel_area_id")
5636 .and_then(|v| v.as_str())?;
5637 let class_b64 = memory_area
5638 .properties
5639 .get("classifier_class_area_id")
5640 .and_then(|v| v.as_str())?;
5641 let class_mem_b64 = memory_area
5642 .properties
5643 .get("classifier_class_memory_id")
5644 .and_then(|v| v.as_str())?;
5645 let kernel_id = CorticalID::try_from_base_64(kernel_b64).ok()?;
5646 let class_id = CorticalID::try_from_base_64(class_b64).ok()?;
5647 let class_mem_id = CorticalID::try_from_base_64(class_mem_b64).ok()?;
5648 if !self.mapping_from_src_to_dst_has_associative(memory_id, &class_mem_id) {
5649 return None;
5650 }
5651 let kernel_area = self.cortical_areas.get(&kernel_id)?;
5652 let class_area = self.cortical_areas.get(&class_id)?;
5653 let class_memory_area_idx = *self.cortical_id_to_idx.get(&class_mem_id)?;
5654 let class_channel_count = class_area
5655 .dimensions
5656 .width
5657 .saturating_mul(class_area.dimensions.height)
5658 .saturating_mul(class_area.dimensions.depth);
5659 if class_channel_count == 0 {
5660 return None;
5661 }
5662 let scan_fields = self.get_episodic_scan_upstream_cortical_areas(memory_id);
5663 if scan_fields.is_empty() {
5664 return None;
5665 }
5666 let twin_map = memory_area
5667 .properties
5668 .get("memory_twin_areas")
5669 .and_then(|v| v.as_object())?;
5670 let mut sources = Vec::new();
5671 for field_idx in scan_fields {
5672 let field_id = *self.cortical_idx_to_id.get(&field_idx)?;
5673 let field_area = self.cortical_areas.get(&field_id)?;
5674 let twin_b64 = twin_map.get(&field_id.as_base_64())?.as_str()?;
5675 let twin_id = CorticalID::try_from_base_64(twin_b64).ok()?;
5676 let twin_idx = *self.cortical_id_to_idx.get(&twin_id)?;
5677 sources.push(MemoryScanSource {
5678 field_area_idx: field_idx,
5679 twin_area_idx: twin_idx,
5680 field_width: field_area.dimensions.width,
5681 field_height: field_area.dimensions.height,
5682 field_depth: field_area.dimensions.depth,
5683 });
5684 }
5685 if sources.is_empty() {
5686 return None;
5687 }
5688 Some(MemoryScanConfig {
5689 kernel: ScanKernel {
5690 width: kernel_area.dimensions.width,
5691 height: kernel_area.dimensions.height,
5692 depth: kernel_area.dimensions.depth,
5693 },
5694 min_window_activity: mem_props.min_window_activity,
5695 scan_skip_density: mem_props.scan_skip_density,
5696 class_channel_count,
5697 class_area_width: class_area.dimensions.width,
5698 class_area_height: class_area.dimensions.height,
5699 class_memory_area_idx,
5700 sources,
5701 scanner_mask: None,
5702 })
5703 }
5704
5705 #[cfg(feature = "plasticity")]
5706 fn build_scanner_memory_scan_config(
5707 &self,
5708 memory_id: &CorticalID,
5709 memory_area: &CorticalArea,
5710 mem_props: &feagi_evolutionary::MemoryAreaProperties,
5711 ) -> Option<feagi_npu_plasticity::MemoryScanConfig> {
5712 use feagi_npu_plasticity::{
5713 MemoryScanConfig, MemoryScanSource, ScanKernel, ScannerMaskSource,
5714 };
5715
5716 let kernel_size = memory_area
5717 .properties
5718 .get("classifier_kernel_size")
5719 .and_then(|value| value.as_array())
5720 .filter(|values| values.len() == 3)?;
5721 let kernel = ScanKernel {
5722 width: kernel_size[0].as_u64().filter(|axis| *axis > 0)? as u32,
5723 height: kernel_size[1].as_u64().filter(|axis| *axis > 0)? as u32,
5724 depth: kernel_size[2].as_u64().filter(|axis| *axis > 0)? as u32,
5725 };
5726 let mask_b64 = memory_area
5727 .properties
5728 .get("classifier_mask_area_id")
5729 .and_then(|value| value.as_str())?;
5730 let class_mem_b64 = memory_area
5731 .properties
5732 .get("classifier_class_memory_id")
5733 .and_then(|value| value.as_str())?;
5734 let mask_id = CorticalID::try_from_base_64(mask_b64).ok()?;
5735 let class_mem_id = CorticalID::try_from_base_64(class_mem_b64).ok()?;
5736 if !self.mapping_from_src_to_dst_has_associative(memory_id, &class_mem_id) {
5737 return None;
5738 }
5739 let mask_area = self.cortical_areas.get(&mask_id)?;
5740 let class_memory_area_idx = *self.cortical_id_to_idx.get(&class_mem_id)?;
5741 let class_channel_count = mask_area.dimensions.depth;
5742 if class_channel_count == 0 {
5743 return None;
5744 }
5745 let scan_fields = self.get_episodic_scan_upstream_cortical_areas(memory_id);
5746 if scan_fields.is_empty() {
5747 return None;
5748 }
5749 let twin_map = memory_area
5750 .properties
5751 .get("memory_twin_areas")
5752 .and_then(|value| value.as_object())?;
5753 let mut sources = Vec::new();
5754 for field_idx in scan_fields {
5755 let field_id = *self.cortical_idx_to_id.get(&field_idx)?;
5756 let field_area = self.cortical_areas.get(&field_id)?;
5757 let twin_b64 = twin_map.get(&field_id.as_base_64())?.as_str()?;
5758 let twin_id = CorticalID::try_from_base_64(twin_b64).ok()?;
5759 let twin_idx = *self.cortical_id_to_idx.get(&twin_id)?;
5760 sources.push(MemoryScanSource {
5761 field_area_idx: field_idx,
5762 twin_area_idx: twin_idx,
5763 field_width: field_area.dimensions.width,
5764 field_height: field_area.dimensions.height,
5765 field_depth: field_area.dimensions.depth,
5766 });
5767 }
5768 if sources.is_empty() {
5769 return None;
5770 }
5771 Some(MemoryScanConfig {
5772 kernel,
5773 min_window_activity: mem_props.min_window_activity,
5774 scan_skip_density: mem_props.scan_skip_density,
5775 class_channel_count,
5776 class_area_width: 1,
5777 class_area_height: 1,
5778 class_memory_area_idx,
5779 sources,
5780 scanner_mask: Some(ScannerMaskSource {
5781 mask_area_idx: *self.cortical_id_to_idx.get(&mask_id)?,
5782 mask_width: mask_area.dimensions.width,
5783 mask_height: mask_area.dimensions.height,
5784 mask_depth: mask_area.dimensions.depth,
5785 }),
5786 })
5787 }
5788
5789 #[cfg(feature = "plasticity")]
5790 fn mapping_from_src_to_dst_has_associative(
5791 &self,
5792 src_area_id: &CorticalID,
5793 dst_area_id: &CorticalID,
5794 ) -> bool {
5795 let Some(src_area) = self.cortical_areas.get(src_area_id) else {
5796 return false;
5797 };
5798 let Some(mapping_dst) = src_area
5799 .properties
5800 .get("cortical_mapping_dst")
5801 .and_then(|v| v.as_object())
5802 else {
5803 return false;
5804 };
5805 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
5806 return false;
5807 };
5808 Self::mapping_has_morphology_rule(rules, "associative_memory")
5809 }
5810
5811 #[cfg(feature = "plasticity")]
5814 pub fn set_plasticity_executor(
5815 &mut self,
5816 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
5817 ) {
5818 if let Ok(mut exec) = executor.lock() {
5819 use feagi_npu_plasticity::executor::PlasticityExecutor;
5820 if !exec.is_running() {
5821 exec.start();
5822 }
5823 } else {
5824 warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
5825 }
5826 self.plasticity_executor = Some(executor);
5827 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
5828 }
5829
5830 #[cfg(feature = "plasticity")]
5832 pub fn get_plasticity_executor(
5833 &self,
5834 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
5835 self.plasticity_executor.as_ref()
5836 }
5837
5838 pub fn get_neuron_capacity(&self) -> usize {
5850 self.config.max_neurons
5852 }
5853
5854 pub fn get_synapse_capacity(&self) -> usize {
5866 self.config.max_synapses
5868 }
5869
5870 pub fn update_fatigue_index(&self) -> Option<u8> {
5885 let mut last_calc = match self.last_fatigue_calculation.lock() {
5887 Ok(guard) => guard,
5888 Err(_) => return None, };
5890
5891 let now = std::time::Instant::now();
5892 if now.duration_since(*last_calc).as_secs() < 2 {
5893 return None; }
5895 *last_calc = now;
5896 drop(last_calc);
5897
5898 let regular_neuron_count = self.get_neuron_count();
5900 let regular_neuron_capacity = self.get_neuron_capacity();
5901 let regular_neuron_util = if regular_neuron_capacity > 0 {
5902 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
5903 } else {
5904 0
5905 };
5906
5907 let memory_neuron_util = match StateManager::instance().try_read() {
5911 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
5912 None => {
5913 return None;
5915 }
5916 };
5917
5918 let synapse_count = self.get_synapse_count();
5920 let synapse_capacity = self.get_synapse_capacity();
5921 let synapse_util = if synapse_capacity > 0 {
5922 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
5923 } else {
5924 0
5925 };
5926
5927 let fatigue_index = regular_neuron_util
5929 .max(memory_neuron_util)
5930 .max(synapse_util);
5931
5932 let current_fatigue_active = {
5934 StateManager::instance()
5936 .try_read()
5937 .map(|m| m.get_core_state().is_fatigue_active())
5938 .unwrap_or(false)
5939 };
5940
5941 let new_fatigue_active = if fatigue_index >= 85 {
5942 true
5943 } else if fatigue_index < 80 {
5944 false
5945 } else {
5946 current_fatigue_active };
5948
5949 if let Some(state_manager) = StateManager::instance().try_write() {
5953 let core_state = state_manager.get_core_state();
5954 core_state.set_fatigue_index(fatigue_index);
5955 core_state.set_fatigue_active(new_fatigue_active);
5956 core_state.set_regular_neuron_util(regular_neuron_util);
5957 core_state.set_memory_neuron_util(memory_neuron_util);
5958 core_state.set_synapse_util(synapse_util);
5959 } else {
5960 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
5962 }
5963
5964 if let Some(ref npu) = self.npu {
5966 if let Ok(mut npu_lock) = npu.lock() {
5967 npu_lock.set_fatigue_active(new_fatigue_active);
5968 }
5969 }
5970
5971 trace!(
5972 target: "feagi-bdu",
5973 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
5974 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
5975 );
5976
5977 Some(fatigue_index)
5978 }
5979
5980 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
5997 let area = self
5999 .cortical_areas
6000 .get(cortical_id)
6001 .ok_or_else(|| {
6002 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6003 })?
6004 .clone();
6005
6006 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
6008 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
6009 })?;
6010
6011 let npu = self
6013 .npu
6014 .as_ref()
6015 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6016
6017 use crate::models::CorticalAreaExt;
6020 let per_voxel_cnt = area.neurons_per_voxel();
6021 let firing_threshold = area.firing_threshold();
6022 let firing_threshold_increment_x = area.firing_threshold_increment_x();
6023 let firing_threshold_increment_y = area.firing_threshold_increment_y();
6024 let firing_threshold_increment_z = area.firing_threshold_increment_z();
6025 let firing_threshold_limit_raw = area.firing_threshold_limit();
6027 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
6028 f32::MAX } else {
6030 firing_threshold_limit_raw
6031 };
6032
6033 if firing_threshold_increment_x != 0.0
6035 || firing_threshold_increment_y != 0.0
6036 || firing_threshold_increment_z != 0.0
6037 {
6038 info!(
6039 target: "feagi-bdu",
6040 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
6041 cortical_id.as_base_64(),
6042 firing_threshold_increment_x,
6043 firing_threshold_increment_y,
6044 firing_threshold_increment_z
6045 );
6046 } else {
6047 if area.properties.contains_key("firing_threshold_increment_x")
6049 || area.properties.contains_key("firing_threshold_increment_y")
6050 || area.properties.contains_key("firing_threshold_increment_z")
6051 {
6052 info!(
6053 target: "feagi-bdu",
6054 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
6055 cortical_id.as_base_64(),
6056 area.properties.get("firing_threshold_increment_x"),
6057 area.properties.get("firing_threshold_increment_y"),
6058 area.properties.get("firing_threshold_increment_z")
6059 );
6060 }
6061 }
6062
6063 let leak_coefficient = area.leak_coefficient();
6064 let excitability = area.neuron_excitability();
6065 let refractory_period = area.refractory_period();
6066 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
6068 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
6069 u16::MAX } else {
6071 consecutive_fire_limit_raw
6072 };
6073 let snooze_length = area.snooze_period();
6074 let mp_charge_accumulation = area.mp_charge_accumulation();
6075
6076 let voxels = area.dimensions.width as usize
6078 * area.dimensions.height as usize
6079 * area.dimensions.depth as usize;
6080 let expected_neurons = voxels * per_voxel_cnt as usize;
6081
6082 trace!(
6083 target: "feagi-bdu",
6084 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
6085 cortical_id.as_base_64(),
6086 area.dimensions.width,
6087 area.dimensions.height,
6088 area.dimensions.depth,
6089 per_voxel_cnt,
6090 expected_neurons
6091 );
6092
6093 let neuron_count: u32 = {
6098 let mut npu_lock = npu
6099 .lock()
6100 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6101 npu_lock
6102 .create_cortical_area_neurons(
6103 *cortical_idx,
6104 area.dimensions.width,
6105 area.dimensions.height,
6106 area.dimensions.depth,
6107 per_voxel_cnt,
6108 firing_threshold,
6109 firing_threshold_increment_x,
6110 firing_threshold_increment_y,
6111 firing_threshold_increment_z,
6112 firing_threshold_limit,
6113 leak_coefficient,
6114 0.0, 0, refractory_period,
6117 excitability,
6118 consecutive_fire_limit,
6119 snooze_length,
6120 mp_charge_accumulation,
6121 )
6122 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
6123 };
6124
6125 trace!(
6126 target: "feagi-bdu",
6127 "Created {} neurons for area {} via NPU",
6128 neuron_count,
6129 cortical_id.as_base_64()
6130 );
6131
6132 {
6135 let mut cache = self.cached_neuron_counts_per_area.write();
6136 cache
6137 .entry(*cortical_id)
6138 .or_insert_with(|| AtomicUsize::new(0))
6139 .store(neuron_count as usize, Ordering::Relaxed);
6140 }
6141
6142 let state_manager = StateManager::instance();
6144 let state_manager = state_manager.read();
6145 state_manager
6146 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
6147
6148 self.cached_neuron_count
6150 .fetch_add(neuron_count as usize, Ordering::Relaxed);
6151
6152 let state_manager = StateManager::instance();
6154 let state_manager = state_manager.read();
6155 let core_state = state_manager.get_core_state();
6156 core_state.add_neuron_count(neuron_count);
6157 core_state.add_regular_neuron_count(neuron_count);
6158
6159 if let Some(npu) = &self.npu {
6161 if let Ok(mut npl) = npu.lock() {
6162 if let Some(v) = area.properties.get("rate_modulated_leak") {
6163 use crate::models::CorticalAreaExt;
6164 let idxs: Vec<usize> = npl
6165 .get_neurons_in_cortical_area(*cortical_idx)
6166 .into_iter()
6167 .map(|id| id as usize)
6168 .collect();
6169 npl.sync_rate_modulated_leak_from_cortical_property(
6170 *cortical_idx,
6171 v,
6172 area.leak_coefficient(),
6173 idxs,
6174 );
6175 } else {
6176 npl.remove_rate_modulated_leak(*cortical_idx);
6177 }
6178 }
6179 }
6180
6181 Ok(neuron_count)
6189 }
6190
6191 #[allow(clippy::too_many_arguments)]
6215 pub fn add_neuron(
6216 &mut self,
6217 cortical_id: &CorticalID,
6218 x: u32,
6219 y: u32,
6220 z: u32,
6221 firing_threshold: f32,
6222 firing_threshold_limit: f32,
6223 leak_coefficient: f32,
6224 resting_potential: f32,
6225 neuron_type: u8,
6226 refractory_period: u16,
6227 excitability: f32,
6228 consecutive_fire_limit: u16,
6229 snooze_length: u16,
6230 mp_charge_accumulation: bool,
6231 ) -> BduResult<u64> {
6232 self.add_neuron_with_area_stats(
6233 cortical_id,
6234 x,
6235 y,
6236 z,
6237 firing_threshold,
6238 firing_threshold_limit,
6239 leak_coefficient,
6240 resting_potential,
6241 neuron_type,
6242 refractory_period,
6243 excitability,
6244 consecutive_fire_limit,
6245 snooze_length,
6246 mp_charge_accumulation,
6247 true,
6248 )
6249 }
6250
6251 #[allow(clippy::too_many_arguments)]
6257 pub fn add_auxiliary_neuron(
6258 &mut self,
6259 cortical_id: &CorticalID,
6260 x: u32,
6261 y: u32,
6262 z: u32,
6263 firing_threshold: f32,
6264 firing_threshold_limit: f32,
6265 leak_coefficient: f32,
6266 resting_potential: f32,
6267 neuron_type: u8,
6268 refractory_period: u16,
6269 excitability: f32,
6270 consecutive_fire_limit: u16,
6271 snooze_length: u16,
6272 mp_charge_accumulation: bool,
6273 ) -> BduResult<u64> {
6274 self.add_neuron_with_area_stats(
6275 cortical_id,
6276 x,
6277 y,
6278 z,
6279 firing_threshold,
6280 firing_threshold_limit,
6281 leak_coefficient,
6282 resting_potential,
6283 neuron_type,
6284 refractory_period,
6285 excitability,
6286 consecutive_fire_limit,
6287 snooze_length,
6288 mp_charge_accumulation,
6289 false,
6290 )
6291 }
6292
6293 #[allow(clippy::too_many_arguments)]
6294 fn add_neuron_with_area_stats(
6295 &mut self,
6296 cortical_id: &CorticalID,
6297 x: u32,
6298 y: u32,
6299 z: u32,
6300 firing_threshold: f32,
6301 firing_threshold_limit: f32,
6302 leak_coefficient: f32,
6303 resting_potential: f32,
6304 neuron_type: u8,
6305 refractory_period: u16,
6306 excitability: f32,
6307 consecutive_fire_limit: u16,
6308 snooze_length: u16,
6309 mp_charge_accumulation: bool,
6310 update_area_stats: bool,
6311 ) -> BduResult<u64> {
6312 if !self.cortical_areas.contains_key(cortical_id) {
6314 return Err(BduError::InvalidArea(format!(
6315 "Cortical area {} not found",
6316 cortical_id
6317 )));
6318 }
6319
6320 let cortical_idx = *self
6321 .cortical_id_to_idx
6322 .get(cortical_id)
6323 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
6324
6325 let npu = self
6327 .npu
6328 .as_ref()
6329 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6330
6331 let mut npu_lock = npu
6332 .lock()
6333 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6334
6335 let neuron_id = npu_lock
6337 .add_neuron(
6338 firing_threshold,
6339 firing_threshold_limit,
6340 leak_coefficient,
6341 resting_potential,
6342 neuron_type as i32,
6343 refractory_period,
6344 excitability,
6345 consecutive_fire_limit,
6346 snooze_length,
6347 mp_charge_accumulation,
6348 cortical_idx,
6349 x,
6350 y,
6351 z,
6352 )
6353 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
6354
6355 trace!(
6356 target: "feagi-bdu",
6357 "Created neuron {} in area {} at ({}, {}, {})",
6358 neuron_id.0,
6359 cortical_id,
6360 x,
6361 y,
6362 z
6363 );
6364
6365 let state_manager = StateManager::instance();
6367 let state_manager = state_manager.read();
6368 let core_state = state_manager.get_core_state();
6369 core_state.add_neuron_count(1);
6370 core_state.add_regular_neuron_count(1);
6371 if update_area_stats {
6372 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
6373 }
6374
6375 Ok(neuron_id.0 as u64)
6376 }
6377
6378 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
6389 let npu = self
6391 .npu
6392 .as_ref()
6393 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6394
6395 let mut npu_lock = npu
6396 .lock()
6397 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6398
6399 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
6400 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6401
6402 let deleted = npu_lock.delete_neuron(neuron_id as u32);
6403
6404 if deleted {
6405 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
6406
6407 let state_manager = StateManager::instance();
6409 let state_manager = state_manager.read();
6410 let core_state = state_manager.get_core_state();
6411 core_state.subtract_neuron_count(1);
6412 core_state.subtract_regular_neuron_count(1);
6413 if let Some(cortical_id) = cortical_id {
6414 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
6415 }
6416
6417 }
6421
6422 Ok(deleted)
6423 }
6424
6425 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
6439 let src_area = self
6441 .cortical_areas
6442 .get(src_cortical_id)
6443 .ok_or_else(|| {
6444 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
6445 })?
6446 .clone();
6447
6448 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
6450 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
6451 _ => return Ok(0), };
6453
6454 let src_cortical_idx = *self
6455 .cortical_id_to_idx
6456 .get(src_cortical_id)
6457 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
6458
6459 let mut total_synapses = 0u32;
6460 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
6464 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
6466 Ok(id) => id,
6467 Err(_) => {
6468 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
6469 continue;
6470 }
6471 };
6472
6473 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
6475 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
6476 continue;
6477 }
6478
6479 let synapse_count =
6481 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
6482 total_synapses += synapse_count as u32;
6483
6484 upstream_updates.push((dst_cortical_id, src_cortical_idx));
6487 }
6488
6489 for (dst_id, src_idx) in upstream_updates {
6491 self.add_upstream_area(&dst_id, src_idx);
6492 }
6493
6494 trace!(
6495 target: "feagi-bdu",
6496 "Created {} synapses for area {} via NPU",
6497 total_synapses,
6498 src_cortical_id
6499 );
6500
6501 if total_synapses > 0 {
6504 let mut cache = self.cached_synapse_counts_per_area.write();
6505 cache
6506 .entry(*src_cortical_id)
6507 .or_insert_with(|| AtomicUsize::new(0))
6508 .fetch_add(total_synapses as usize, Ordering::Relaxed);
6509 }
6510
6511 self.cached_synapse_count
6513 .fetch_add(total_synapses as usize, Ordering::Relaxed);
6514
6515 if total_synapses > 0 {
6517 let state_manager = StateManager::instance();
6518 let state_manager = state_manager.read();
6519 let core_state = state_manager.get_core_state();
6520 core_state.add_synapse_count(total_synapses);
6521 }
6522
6523 Ok(total_synapses)
6524 }
6525
6526 pub fn has_neuron(&self, neuron_id: u64) -> bool {
6545 if let Some(ref npu) = self.npu {
6546 if let Ok(npu_lock) = npu.lock() {
6547 npu_lock.is_neuron_valid(neuron_id as u32)
6549 } else {
6550 false
6551 }
6552 } else {
6553 false
6554 }
6555 }
6556
6557 pub fn get_neuron_count(&self) -> usize {
6569 if let Some(ref npu) = self.npu {
6571 if let Ok(npu_lock) = npu.try_lock() {
6572 let fresh_count = npu_lock.get_neuron_count();
6573 self.cached_neuron_count
6574 .store(fresh_count, Ordering::Relaxed);
6575 }
6576 }
6578
6579 self.cached_neuron_count.load(Ordering::Relaxed)
6581 }
6582
6583 pub fn update_cached_neuron_count(&self) {
6589 if let Some(ref npu) = self.npu {
6590 if let Ok(npu_lock) = npu.try_lock() {
6591 let count = npu_lock.get_neuron_count();
6592 self.cached_neuron_count.store(count, Ordering::Relaxed);
6593 }
6594 }
6595 }
6596
6597 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
6601 let npu = self.npu.as_ref()?;
6602 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
6603 let npu_lock = npu.lock().ok()?;
6604 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
6605 drop(npu_lock);
6606
6607 let mut cache = self.cached_neuron_counts_per_area.write();
6608 cache
6609 .entry(*cortical_id)
6610 .or_insert_with(|| AtomicUsize::new(0))
6611 .store(count, Ordering::Relaxed);
6612
6613 let state_manager = StateManager::instance();
6615 let state_manager = state_manager.read();
6616 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
6617
6618 self.update_cached_neuron_count();
6619
6620 Some(count)
6621 }
6622
6623 pub fn get_synapse_count(&self) -> usize {
6635 if let Some(ref npu) = self.npu {
6637 if let Ok(npu_lock) = npu.try_lock() {
6638 let fresh_count = npu_lock.get_synapse_count();
6639 self.cached_synapse_count
6640 .store(fresh_count, Ordering::Relaxed);
6641 }
6642 }
6644
6645 self.cached_synapse_count.load(Ordering::Relaxed)
6647 }
6648
6649 pub fn update_cached_synapse_count(&self) {
6655 if let Some(ref npu) = self.npu {
6656 if let Ok(npu_lock) = npu.try_lock() {
6657 let count = npu_lock.get_synapse_count();
6658 self.cached_synapse_count.store(count, Ordering::Relaxed);
6659 }
6660 }
6661 }
6662
6663 pub fn update_all_cached_stats(&self) {
6669 self.update_cached_neuron_count();
6670 self.update_cached_synapse_count();
6671 }
6672
6673 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
6684 #[cfg(feature = "plasticity")]
6689 {
6690 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
6691 return (0, 0, 0);
6692 }
6693 }
6694 if let Some(ref npu) = self.npu {
6695 if let Ok(npu_lock) = npu.lock() {
6696 npu_lock
6697 .get_neuron_coordinates(neuron_id as u32)
6698 .unwrap_or((0, 0, 0))
6699 } else {
6700 (0, 0, 0)
6701 }
6702 } else {
6703 (0, 0, 0)
6704 }
6705 }
6706
6707 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
6718 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
6719 }
6720
6721 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
6727 #[cfg(feature = "plasticity")]
6728 {
6729 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
6730 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
6731 }
6732 }
6733 if let Some(ref npu) = self.npu {
6734 if let Ok(npu_lock) = npu.lock() {
6735 npu_lock.get_neuron_cortical_area(neuron_id as u32)
6736 } else {
6737 None
6738 }
6739 } else {
6740 None
6741 }
6742 }
6743
6744 #[cfg(feature = "plasticity")]
6746 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
6747 let exec = self.get_plasticity_executor()?;
6748 let guard = exec.lock().ok()?;
6749 guard
6750 .memory_neuron_detail(neuron_id)
6751 .map(|d| d.cortical_area_idx)
6752 }
6753
6754 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6765 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
6767 Some(idx) => *idx,
6768 None => return Vec::new(),
6769 };
6770
6771 if let Some(ref npu) = self.npu {
6772 if let Ok(npu_lock) = npu.lock() {
6773 npu_lock
6775 .get_neurons_in_cortical_area(cortical_idx)
6776 .into_iter()
6777 .map(|id| id as u64)
6778 .collect()
6779 } else {
6780 Vec::new()
6781 }
6782 } else {
6783 Vec::new()
6784 }
6785 }
6786
6787 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
6798 if let Some(ref npu) = self.npu {
6799 if let Ok(npu_lock) = npu.lock() {
6800 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
6801 } else {
6802 Vec::new()
6803 }
6804 } else {
6805 Vec::new()
6806 }
6807 }
6808
6809 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
6820 if let Some(ref npu) = self.npu {
6821 if let Ok(npu_lock) = npu.lock() {
6822 npu_lock.get_incoming_synapses(target_neuron_id as u32)
6823 } else {
6824 Vec::new()
6825 }
6826 } else {
6827 Vec::new()
6828 }
6829 }
6830
6831 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6857 let is_memory_area = self
6858 .cortical_areas
6859 .get(cortical_id)
6860 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
6861 .is_some();
6862
6863 if is_memory_area {
6864 #[cfg(feature = "plasticity")]
6868 if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
6869 return count;
6870 }
6871
6872 return StateManager::instance()
6874 .try_read()
6875 .and_then(|state_manager| {
6876 state_manager
6877 .get_cortical_area_stats(&cortical_id.as_base_64())
6878 .map(|stats| stats.neuron_count)
6879 })
6880 .unwrap_or(0);
6881 }
6882
6883 let cache = self.cached_neuron_counts_per_area.read();
6885 cache
6886 .get(cortical_id)
6887 .map(|count| count.load(Ordering::Relaxed))
6888 .unwrap_or(0)
6889 }
6890
6891 #[cfg(feature = "plasticity")]
6893 fn active_memory_neuron_count_from_plasticity(
6894 &self,
6895 cortical_id: &CorticalID,
6896 ) -> Option<usize> {
6897 let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
6898 let exec = self.get_plasticity_executor()?;
6899 let guard = exec.lock().ok()?;
6900 let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
6901 Some(runtime.active_memory_neuron_count())
6902 }
6903
6904 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
6911 let mut result = Vec::new();
6912
6913 for cortical_id in self.cortical_areas.keys() {
6914 let count = self.get_neuron_count_in_area(cortical_id);
6915 if count > 0 {
6916 result.push((cortical_id.to_string(), count));
6917 }
6918 }
6919
6920 result
6921 }
6922
6923 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
6934 self.get_neuron_count_in_area(cortical_id) > 0
6935 }
6936
6937 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6953 let cache = self.cached_synapse_counts_per_area.read();
6955 cache
6956 .get(cortical_id)
6957 .map(|count| count.load(Ordering::Relaxed))
6958 .unwrap_or(0)
6959 }
6960
6961 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6971 if !self.cortical_id_to_idx.contains_key(cortical_id) {
6972 return 0;
6973 }
6974
6975 if let Some(state_manager) = StateManager::instance().try_read() {
6976 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
6977 return stats.incoming_synapse_count;
6978 }
6979 }
6980
6981 0
6982 }
6983
6984 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6994 if !self.cortical_id_to_idx.contains_key(cortical_id) {
6995 return 0;
6996 }
6997
6998 if let Some(state_manager) = StateManager::instance().try_read() {
6999 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
7000 return stats.outgoing_synapse_count;
7001 }
7002 }
7003
7004 0
7005 }
7006
7007 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
7019 let synapses = self.get_outgoing_synapses(source_neuron_id);
7020 synapses
7021 .iter()
7022 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
7023 }
7024
7025 pub fn get_connection_weight(
7037 &self,
7038 source_neuron_id: u64,
7039 target_neuron_id: u64,
7040 ) -> Option<f32> {
7041 let synapses = self.get_outgoing_synapses(source_neuron_id);
7042 synapses
7043 .iter()
7044 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
7045 .map(|(_, weight, _, _)| *weight)
7046 }
7047
7048 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
7059 let neurons = self.get_neurons_in_area(cortical_id);
7060 let neuron_count = neurons.len();
7061
7062 if neuron_count == 0 {
7063 return (0, 0, 0.0);
7064 }
7065
7066 let mut total_synapses = 0;
7067 for neuron_id in neurons {
7068 total_synapses += self.get_outgoing_synapses(neuron_id).len();
7069 }
7070
7071 let avg_synapses = total_synapses as f32 / neuron_count as f32;
7072
7073 (neuron_count, total_synapses, avg_synapses)
7074 }
7075
7076 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
7087 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
7088 self.cortical_idx_to_id.get(&cortical_idx).copied()
7089 }
7090
7091 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
7102 let area = match self.cortical_areas.get(cortical_id) {
7103 Some(a) => a,
7104 None => return 0.0,
7105 };
7106
7107 let neuron_count = self.get_neuron_count_in_area(cortical_id);
7108 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
7109
7110 if volume == 0 {
7111 return 0.0;
7112 }
7113
7114 neuron_count as f32 / volume as f32
7115 }
7116
7117 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
7124 let mut stats = Vec::new();
7125
7126 for cortical_id in self.cortical_areas.keys() {
7127 let neuron_count = self.get_neuron_count_in_area(cortical_id);
7128 let synapse_count = self.get_synapse_count_in_area(cortical_id);
7129 let density = self.get_neuron_density(cortical_id);
7130
7131 stats.push((
7132 cortical_id.to_string(),
7133 neuron_count,
7134 synapse_count,
7135 density,
7136 ));
7137 }
7138
7139 stats
7140 }
7141
7142 pub fn get_config(&self) -> &ConnectomeConfig {
7148 &self.config
7149 }
7150
7151 pub fn set_config(&mut self, config: ConnectomeConfig) {
7153 self.config = config;
7154 }
7155
7156 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
7179 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
7180
7181 use feagi_structures::genomic::cortical_area::{
7182 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
7183 };
7184
7185 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
7187 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
7188 })?;
7189
7190 let core_position = (0, 0, 0).into();
7192
7193 let death_id = CoreCorticalType::Death.to_cortical_id();
7195 if !self.cortical_areas.contains_key(&death_id) {
7196 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
7197 let death_area = CorticalArea::new(
7198 death_id,
7199 0, "_death".to_string(),
7201 core_dimensions,
7202 core_position,
7203 CorticalAreaType::Core(CoreCorticalType::Death),
7204 )
7205 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
7206 match self.add_cortical_area(death_area) {
7207 Ok(idx) => {
7208 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
7209 }
7210 Err(e) => {
7211 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
7212 return Err(e);
7213 }
7214 }
7215 } else {
7216 info!(target: "feagi-bdu", " ✓ _death area already exists");
7217 }
7218
7219 let power_id = CoreCorticalType::Power.to_cortical_id();
7221 if !self.cortical_areas.contains_key(&power_id) {
7222 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
7223 let power_area = CorticalArea::new(
7224 power_id,
7225 1, "_power".to_string(),
7227 core_dimensions,
7228 core_position,
7229 CorticalAreaType::Core(CoreCorticalType::Power),
7230 )
7231 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
7232 match self.add_cortical_area(power_area) {
7233 Ok(idx) => {
7234 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
7235 }
7236 Err(e) => {
7237 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
7238 return Err(e);
7239 }
7240 }
7241 } else {
7242 info!(target: "feagi-bdu", " ✓ _power area already exists");
7243 }
7244
7245 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
7247 let pain_id = CoreCorticalType::Pain.to_cortical_id();
7248 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
7249 let fear_id = CoreCorticalType::Fear.to_cortical_id();
7250 let hope_id = CoreCorticalType::Hope.to_cortical_id();
7251 if !self.cortical_areas.contains_key(&fatigue_id) {
7252 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
7253 let fatigue_area = CorticalArea::new(
7254 fatigue_id,
7255 2, "_fatigue".to_string(),
7257 core_dimensions,
7258 core_position,
7259 CorticalAreaType::Core(CoreCorticalType::Fatigue),
7260 )
7261 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
7262 match self.add_cortical_area(fatigue_area) {
7263 Ok(idx) => {
7264 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
7265 }
7266 Err(e) => {
7267 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
7268 return Err(e);
7269 }
7270 }
7271 } else {
7272 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
7273 }
7274
7275 if !self.cortical_areas.contains_key(&pain_id) {
7277 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
7278 let pain_area = CorticalArea::new(
7279 pain_id,
7280 3, "_pain".to_string(),
7282 core_dimensions,
7283 core_position,
7284 CorticalAreaType::Core(CoreCorticalType::Pain),
7285 )
7286 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
7287 match self.add_cortical_area(pain_area) {
7288 Ok(idx) => {
7289 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
7290 }
7291 Err(e) => {
7292 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
7293 return Err(e);
7294 }
7295 }
7296 } else {
7297 info!(target: "feagi-bdu", " ✓ _pain area already exists");
7298 }
7299
7300 if !self.cortical_areas.contains_key(&pleasure_id) {
7302 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
7303 let pleasure_area = CorticalArea::new(
7304 pleasure_id,
7305 4, "_pleasure".to_string(),
7307 core_dimensions,
7308 core_position,
7309 CorticalAreaType::Core(CoreCorticalType::Pleasure),
7310 )
7311 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
7312 match self.add_cortical_area(pleasure_area) {
7313 Ok(idx) => {
7314 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
7315 }
7316 Err(e) => {
7317 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
7318 return Err(e);
7319 }
7320 }
7321 } else {
7322 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
7323 }
7324
7325 if !self.cortical_areas.contains_key(&fear_id) {
7327 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
7328 let fear_area = CorticalArea::new(
7329 fear_id,
7330 5, "_fear".to_string(),
7332 core_dimensions,
7333 core_position,
7334 CorticalAreaType::Core(CoreCorticalType::Fear),
7335 )
7336 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
7337 match self.add_cortical_area(fear_area) {
7338 Ok(idx) => {
7339 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
7340 }
7341 Err(e) => {
7342 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
7343 return Err(e);
7344 }
7345 }
7346 } else {
7347 info!(target: "feagi-bdu", " ✓ _fear area already exists");
7348 }
7349
7350 if !self.cortical_areas.contains_key(&hope_id) {
7352 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
7353 let hope_area = CorticalArea::new(
7354 hope_id,
7355 6, "_hope".to_string(),
7357 core_dimensions,
7358 core_position,
7359 CorticalAreaType::Core(CoreCorticalType::Hope),
7360 )
7361 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
7362 match self.add_cortical_area(hope_area) {
7363 Ok(idx) => {
7364 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
7365 }
7366 Err(e) => {
7367 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
7368 return Err(e);
7369 }
7370 }
7371 } else {
7372 info!(target: "feagi-bdu", " ✓ _hope area already exists");
7373 }
7374
7375 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
7376 Ok(())
7377 }
7378
7379 #[deprecated(
7396 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
7397 )]
7398 #[allow(deprecated)]
7399 pub fn save_genome_to_json(
7400 &self,
7401 genome_id: Option<String>,
7402 genome_title: Option<String>,
7403 ) -> BduResult<String> {
7404 let mut brain_regions_with_parents = std::collections::HashMap::new();
7406
7407 for region_id in self.brain_regions.get_all_region_ids() {
7408 if let Some(region) = self.brain_regions.get_region(region_id) {
7409 let parent_id = self
7410 .brain_regions
7411 .get_parent(region_id)
7412 .map(|s| s.to_string());
7413 brain_regions_with_parents
7414 .insert(region_id.to_string(), (region.clone(), parent_id));
7415 }
7416 }
7417
7418 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
7420 &self.cortical_areas,
7421 &brain_regions_with_parents,
7422 genome_id,
7423 genome_title,
7424 )?)
7425 }
7426
7427 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
7458 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
7459
7460 self.cortical_areas.clear();
7462 self.cortical_id_to_idx.clear();
7463 self.cortical_idx_to_id.clear();
7464 self.next_cortical_idx = 7;
7466 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 7 (reserves 0=_death, 1=_power, 2=_fatigue, 3=_pain, 4=_pleasure, 5=_fear, 6=_hope)");
7467
7468 self.brain_regions = BrainRegionHierarchy::new();
7470
7471 #[cfg(feature = "plasticity")]
7474 if let Some(executor) = self.plasticity_executor.as_ref() {
7475 executor
7476 .lock()
7477 .map_err(|_| {
7478 BduError::Internal(
7479 "Failed to lock PlasticityExecutor for memory state reset".to_string(),
7480 )
7481 })?
7482 .reset_all_memory_state()
7483 .map_err(BduError::Internal)?;
7484 }
7485
7486 if let Some(ref npu) = self.npu {
7488 let mut npu_lock = npu
7489 .lock()
7490 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7491 npu_lock
7492 .reset_for_new_genome()
7493 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
7494 }
7495
7496 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
7497 Ok(())
7498 }
7499
7500 pub fn resize_for_genome(
7510 &mut self,
7511 genome: &feagi_evolutionary::RuntimeGenome,
7512 ) -> BduResult<()> {
7513 self.morphology_registry = genome.morphologies.clone();
7515 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
7516
7517 let required_neurons = genome.stats.innate_neuron_count;
7519 let required_synapses = genome.stats.innate_synapse_count;
7520
7521 info!(target: "feagi-bdu",
7522 "Genome requires: {} neurons, {} synapses",
7523 required_neurons,
7524 required_synapses
7525 );
7526
7527 let mut total_voxels = 0;
7529 for area in genome.cortical_areas.values() {
7530 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
7531 }
7532
7533 info!(target: "feagi-bdu",
7534 "Genome has {} cortical areas with {} total voxels",
7535 genome.cortical_areas.len(),
7536 total_voxels
7537 );
7538
7539 Ok(())
7544 }
7545
7546 pub fn create_synapse(
7565 &mut self,
7566 source_neuron_id: u64,
7567 target_neuron_id: u64,
7568 weight: f32,
7569 psp: f32,
7570 synapse_type: u8,
7571 ) -> BduResult<()> {
7572 let npu = self
7574 .npu
7575 .as_ref()
7576 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7577
7578 let mut npu_lock = npu
7579 .lock()
7580 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7581
7582 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
7584 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
7585
7586 if !source_exists {
7587 return Err(BduError::InvalidNeuron(format!(
7588 "Source neuron {} not found",
7589 source_neuron_id
7590 )));
7591 }
7592 if !target_exists {
7593 return Err(BduError::InvalidNeuron(format!(
7594 "Target neuron {} not found",
7595 target_neuron_id
7596 )));
7597 }
7598
7599 let syn_type = if synapse_type == 0 {
7601 feagi_npu_neural::synapse::SynapseType::Excitatory
7602 } else {
7603 feagi_npu_neural::synapse::SynapseType::Inhibitory
7604 };
7605
7606 let synapse_idx = npu_lock
7607 .add_synapse(
7608 NeuronId(source_neuron_id as u32),
7609 NeuronId(target_neuron_id as u32),
7610 feagi_npu_neural::types::SynapticWeight(weight),
7611 feagi_npu_neural::types::SynapticPsp(psp),
7612 syn_type,
7613 0,
7614 1,
7615 )
7616 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
7617
7618 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
7619 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
7620
7621 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
7622 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
7623 let source_cortical_id =
7624 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7625 let target_cortical_id =
7626 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7627
7628 let state_manager = StateManager::instance();
7629 let state_manager = state_manager.read();
7630 let core_state = state_manager.get_core_state();
7631 core_state.add_synapse_count(1);
7632 if let Some(cortical_id) = source_cortical_id {
7633 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
7634 }
7635 if let Some(cortical_id) = target_cortical_id {
7636 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
7637 }
7638
7639 Ok(())
7644 }
7645
7646 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
7649 if let Some(ref npu) = self.npu {
7650 if let Ok(mut npu_lock) = npu.lock() {
7651 let mut psp_uniform_flags = ahash::AHashMap::new();
7653 let mut mp_driven_psp_flags = ahash::AHashMap::new();
7654 let mut postsynaptic_current_flags = ahash::AHashMap::new();
7655 let mut degeneration_flags = ahash::AHashMap::new();
7656
7657 for (cortical_id, area) in &self.cortical_areas {
7658 let default_psp_uniform = *cortical_id
7661 == CoreCorticalType::Power.to_cortical_id()
7662 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
7663 let psp_uniform = area
7664 .get_property("psp_uniform_distribution")
7665 .and_then(|v| v.as_bool())
7666 .unwrap_or(default_psp_uniform);
7667 psp_uniform_flags.insert(*cortical_id, psp_uniform);
7668
7669 let mp_driven_psp = area
7671 .get_property("mp_driven_psp")
7672 .and_then(|v| v.as_bool())
7673 .unwrap_or(false);
7674 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
7675
7676 let postsynaptic_current = area
7678 .get_property("postsynaptic_current")
7679 .and_then(|v| v.as_f64())
7680 .unwrap_or(1.0) as f32;
7681 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
7682
7683 let degeneration = area
7685 .get_property("degeneration")
7686 .and_then(|v| v.as_f64())
7687 .unwrap_or(0.0) as f32;
7688 if degeneration > 0.0 {
7689 degeneration_flags.insert(*cortical_id, degeneration);
7690 }
7691 }
7692
7693 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
7695 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
7696 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
7697 npu_lock.set_degeneration_flags(degeneration_flags);
7698
7699 trace!(
7700 target: "feagi-bdu",
7701 "Synchronized cortical area flags to NPU ({} areas)",
7702 self.cortical_areas.len()
7703 );
7704 }
7705 }
7706
7707 Ok(())
7708 }
7709
7710 pub fn get_synapse(
7722 &self,
7723 source_neuron_id: u64,
7724 target_neuron_id: u64,
7725 ) -> Option<(f32, f32, u8)> {
7726 let npu = self.npu.as_ref()?;
7728 let npu_lock = npu.lock().ok()?;
7729
7730 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
7733
7734 for (source_id, weight, psp, synapse_type) in incoming {
7736 if source_id == source_neuron_id as u32 {
7737 return Some((weight, psp, synapse_type));
7738 }
7739 }
7740
7741 None
7742 }
7743
7744 pub fn update_synapse_weight(
7757 &mut self,
7758 source_neuron_id: u64,
7759 target_neuron_id: u64,
7760 new_weight: f32,
7761 ) -> BduResult<()> {
7762 let npu = self
7764 .npu
7765 .as_ref()
7766 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7767
7768 let mut npu_lock = npu
7769 .lock()
7770 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7771
7772 let updated = npu_lock.update_synapse_weight(
7774 NeuronId(source_neuron_id as u32),
7775 NeuronId(target_neuron_id as u32),
7776 feagi_npu_neural::types::SynapticWeight(new_weight),
7777 );
7778
7779 if updated {
7780 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
7781 Ok(())
7782 } else {
7783 Err(BduError::InvalidSynapse(format!(
7784 "Synapse {} -> {} not found",
7785 source_neuron_id, target_neuron_id
7786 )))
7787 }
7788 }
7789
7790 pub fn remove_synapse(
7802 &mut self,
7803 source_neuron_id: u64,
7804 target_neuron_id: u64,
7805 ) -> BduResult<bool> {
7806 let npu = self
7808 .npu
7809 .as_ref()
7810 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7811
7812 let mut npu_lock = npu
7813 .lock()
7814 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7815
7816 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
7817 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
7818 let source_cortical_id =
7819 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7820 let target_cortical_id =
7821 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7822
7823 let removed = npu_lock.remove_synapse(
7825 NeuronId(source_neuron_id as u32),
7826 NeuronId(target_neuron_id as u32),
7827 );
7828
7829 if removed {
7830 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
7831
7832 let state_manager = StateManager::instance();
7834 let state_manager = state_manager.read();
7835 let core_state = state_manager.get_core_state();
7836 core_state.subtract_synapse_count(1);
7837 if let Some(cortical_id) = source_cortical_id {
7838 state_manager
7839 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
7840 }
7841 if let Some(cortical_id) = target_cortical_id {
7842 state_manager
7843 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
7844 }
7845 }
7846
7847 Ok(removed)
7848 }
7849
7850 pub fn batch_create_neurons(
7868 &mut self,
7869 cortical_id: &CorticalID,
7870 neurons: Vec<NeuronData>,
7871 ) -> BduResult<Vec<u64>> {
7872 let npu = self
7874 .npu
7875 .as_ref()
7876 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7877
7878 let mut npu_lock = npu
7879 .lock()
7880 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7881
7882 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
7884 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
7885 })?;
7886 let cortical_idx = area.cortical_idx;
7887
7888 let count = neurons.len();
7889
7890 let mut x_coords = Vec::with_capacity(count);
7892 let mut y_coords = Vec::with_capacity(count);
7893 let mut z_coords = Vec::with_capacity(count);
7894 let mut firing_thresholds = Vec::with_capacity(count);
7895 let mut threshold_limits = Vec::with_capacity(count);
7896 let mut leak_coeffs = Vec::with_capacity(count);
7897 let mut resting_potentials = Vec::with_capacity(count);
7898 let mut neuron_types = Vec::with_capacity(count);
7899 let mut refractory_periods = Vec::with_capacity(count);
7900 let mut excitabilities = Vec::with_capacity(count);
7901 let mut consec_fire_limits = Vec::with_capacity(count);
7902 let mut snooze_lengths = Vec::with_capacity(count);
7903 let mut mp_accums = Vec::with_capacity(count);
7904 let mut cortical_areas = Vec::with_capacity(count);
7905
7906 for (
7907 x,
7908 y,
7909 z,
7910 threshold,
7911 threshold_limit,
7912 leak,
7913 resting,
7914 ntype,
7915 refract,
7916 excit,
7917 consec_limit,
7918 snooze,
7919 mp_accum,
7920 ) in neurons
7921 {
7922 x_coords.push(x);
7923 y_coords.push(y);
7924 z_coords.push(z);
7925 firing_thresholds.push(threshold);
7926 threshold_limits.push(threshold_limit);
7927 leak_coeffs.push(leak);
7928 resting_potentials.push(resting);
7929 neuron_types.push(ntype);
7930 refractory_periods.push(refract);
7931 excitabilities.push(excit);
7932 consec_fire_limits.push(consec_limit);
7933 snooze_lengths.push(snooze);
7934 mp_accums.push(mp_accum);
7935 cortical_areas.push(cortical_idx);
7936 }
7937
7938 let first_neuron_id = npu_lock.get_neuron_count() as u32;
7940
7941 let firing_thresholds_t = firing_thresholds;
7946 let threshold_limits_t = threshold_limits;
7947 let resting_potentials_t = resting_potentials;
7948 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
7949 firing_thresholds_t,
7950 threshold_limits_t,
7951 leak_coeffs,
7952 resting_potentials_t,
7953 neuron_types,
7954 refractory_periods,
7955 excitabilities,
7956 consec_fire_limits,
7957 snooze_lengths,
7958 mp_accums,
7959 cortical_areas,
7960 x_coords,
7961 y_coords,
7962 z_coords,
7963 );
7964
7965 let mut neuron_ids = Vec::with_capacity(count);
7967 for i in 0..neurons_created {
7968 neuron_ids.push((first_neuron_id + i) as u64);
7969 }
7970
7971 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
7972
7973 let state_manager = StateManager::instance();
7975 let state_manager = state_manager.read();
7976 let core_state = state_manager.get_core_state();
7977 core_state.add_neuron_count(neurons_created);
7978 core_state.add_regular_neuron_count(neurons_created);
7979 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
7980
7981 {
7983 let mut cache = self.cached_neuron_counts_per_area.write();
7984 cache
7985 .entry(*cortical_id)
7986 .or_insert_with(|| AtomicUsize::new(0))
7987 .fetch_add(count, Ordering::Relaxed);
7988 }
7989
7990 Ok(neuron_ids)
7991 }
7992
7993 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
8004 let npu = self
8006 .npu
8007 .as_ref()
8008 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
8009
8010 let mut npu_lock = npu
8011 .lock()
8012 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
8013
8014 let mut deleted_count = 0;
8015 let mut per_area_deleted: std::collections::HashMap<String, usize> =
8016 std::collections::HashMap::new();
8017
8018 for neuron_id in neuron_ids {
8021 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
8022 let cortical_id =
8023 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
8024
8025 if npu_lock.delete_neuron(neuron_id as u32) {
8026 deleted_count += 1;
8027 if let Some(cortical_id) = cortical_id {
8028 let key = cortical_id.as_base_64();
8029 *per_area_deleted.entry(key).or_insert(0) += 1;
8030 }
8031 }
8032 }
8033
8034 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
8035
8036 if deleted_count > 0 {
8038 let state_manager = StateManager::instance();
8039 let state_manager = state_manager.read();
8040 let core_state = state_manager.get_core_state();
8041 core_state.subtract_neuron_count(deleted_count as u32);
8042 core_state.subtract_regular_neuron_count(deleted_count as u32);
8043 for (cortical_id, count) in per_area_deleted {
8044 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
8045 }
8046 }
8047
8048 Ok(deleted_count)
8055 }
8056
8057 pub fn update_neuron_properties(
8076 &mut self,
8077 neuron_id: u64,
8078 firing_threshold: Option<f32>,
8079 leak_coefficient: Option<f32>,
8080 resting_potential: Option<f32>,
8081 excitability: Option<f32>,
8082 ) -> BduResult<()> {
8083 let npu = self
8085 .npu
8086 .as_ref()
8087 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
8088
8089 let mut npu_lock = npu
8090 .lock()
8091 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
8092
8093 let neuron_id_u32 = neuron_id as u32;
8094
8095 let mut updated = false;
8097
8098 if let Some(threshold) = firing_threshold {
8100 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
8101 updated = true;
8102 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
8103 } else if !updated {
8104 return Err(BduError::InvalidNeuron(format!(
8105 "Neuron {} not found",
8106 neuron_id
8107 )));
8108 }
8109 }
8110
8111 if let Some(leak) = leak_coefficient {
8112 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
8113 updated = true;
8114 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
8115 } else if !updated {
8116 return Err(BduError::InvalidNeuron(format!(
8117 "Neuron {} not found",
8118 neuron_id
8119 )));
8120 }
8121 }
8122
8123 if let Some(resting) = resting_potential {
8124 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
8125 updated = true;
8126 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
8127 } else if !updated {
8128 return Err(BduError::InvalidNeuron(format!(
8129 "Neuron {} not found",
8130 neuron_id
8131 )));
8132 }
8133 }
8134
8135 if let Some(excit) = excitability {
8136 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
8137 updated = true;
8138 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
8139 } else if !updated {
8140 return Err(BduError::InvalidNeuron(format!(
8141 "Neuron {} not found",
8142 neuron_id
8143 )));
8144 }
8145 }
8146
8147 if !updated {
8148 return Err(BduError::Internal(
8149 "No properties provided for update".to_string(),
8150 ));
8151 }
8152
8153 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
8154
8155 Ok(())
8156 }
8157
8158 pub fn set_neuron_firing_threshold(
8170 &mut self,
8171 neuron_id: u64,
8172 new_threshold: f32,
8173 ) -> BduResult<()> {
8174 let npu = self
8176 .npu
8177 .as_ref()
8178 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
8179
8180 let mut npu_lock = npu
8181 .lock()
8182 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
8183
8184 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
8186 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
8187 Ok(())
8188 } else {
8189 Err(BduError::InvalidNeuron(format!(
8190 "Neuron {} not found",
8191 neuron_id
8192 )))
8193 }
8194 }
8195
8196 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
8211 self.cortical_areas
8212 .values()
8213 .find(|area| area.name == name)
8214 .cloned()
8215 }
8216
8217 pub fn resize_cortical_area(
8234 &mut self,
8235 cortical_id: &CorticalID,
8236 new_dimensions: CorticalAreaDimensions,
8237 ) -> BduResult<()> {
8238 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
8240 return Err(BduError::InvalidArea(format!(
8241 "Invalid dimensions: {:?} (all must be > 0)",
8242 new_dimensions
8243 )));
8244 }
8245
8246 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
8248 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
8249 })?;
8250
8251 let old_dimensions = area.dimensions;
8252 area.dimensions = new_dimensions;
8253
8254 info!(target: "feagi-bdu",
8258 "Resized cortical area {} from {:?} to {:?}",
8259 cortical_id,
8260 old_dimensions,
8261 new_dimensions
8262 );
8263
8264 self.refresh_cortical_area_hashes(false, true);
8265
8266 Ok(())
8267 }
8268
8269 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
8280 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
8281 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8282 })?;
8283
8284 Ok(region
8286 .cortical_areas
8287 .iter()
8288 .map(|id| id.as_base_64())
8289 .collect())
8290 }
8291
8292 pub fn update_brain_region(
8305 &mut self,
8306 region_id: &str,
8307 new_name: Option<String>,
8308 new_description: Option<String>,
8309 ) -> BduResult<()> {
8310 let region = self
8311 .brain_regions
8312 .get_region_mut(region_id)
8313 .ok_or_else(|| {
8314 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8315 })?;
8316
8317 if let Some(name) = new_name {
8318 region.name = name;
8319 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
8320 }
8321
8322 if let Some(desc) = new_description {
8323 region
8325 .properties
8326 .insert("description".to_string(), serde_json::json!(desc));
8327 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
8328 }
8329
8330 info!(target: "feagi-bdu","Updated brain region {}", region_id);
8331
8332 self.refresh_brain_regions_hash();
8333
8334 Ok(())
8335 }
8336
8337 pub fn update_brain_region_properties(
8351 &mut self,
8352 region_id: &str,
8353 properties: std::collections::HashMap<String, serde_json::Value>,
8354 ) -> BduResult<Option<BrainRegionIoRegistry>> {
8355 use tracing::{debug, info};
8356
8357 let should_recompute_io = properties
8358 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
8359 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
8360
8361 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
8362 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
8363 {
8364 let region_snapshot = self
8365 .brain_regions
8366 .get_region(region_id)
8367 .ok_or_else(|| {
8368 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8369 })?
8370 .clone();
8371 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
8372 ®ion_snapshot,
8373 &properties,
8374 )?;
8375 crate::region_io_designation::validate_merged_designations_against_connectivity(
8376 self,
8377 ®ion_snapshot,
8378 &merged_in,
8379 &merged_out,
8380 )?;
8381 }
8382
8383 let region = self
8384 .brain_regions
8385 .get_region_mut(region_id)
8386 .ok_or_else(|| {
8387 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8388 })?;
8389
8390 for (key, value) in properties {
8391 match key.as_str() {
8392 "title" | "name" | "region_title" => {
8394 if let Some(name) = value.as_str() {
8395 region.name = name.to_string();
8396 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
8397 }
8398 }
8399 "coordinate_3d" | "coordinates_3d" => {
8400 region
8401 .properties
8402 .insert("coordinate_3d".to_string(), value.clone());
8403 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
8404 }
8405 "coordinate_2d" | "coordinates_2d" => {
8406 region
8407 .properties
8408 .insert("coordinate_2d".to_string(), value.clone());
8409 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
8410 }
8411 "description" => {
8412 region
8413 .properties
8414 .insert("description".to_string(), value.clone());
8415 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
8416 }
8417 "region_type" => {
8418 if let Some(type_str) = value.as_str() {
8419 region.region_type = feagi_structures::genomic::RegionType::Undefined;
8422 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
8423 }
8424 }
8425 _ => {
8427 region.properties.insert(key.clone(), value.clone());
8428 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
8429 }
8430 }
8431 }
8432
8433 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
8434
8435 if should_recompute_io {
8438 let registry = self.recompute_brain_region_io_registry()?;
8439 return Ok(Some(registry));
8440 }
8441
8442 self.refresh_brain_regions_hash();
8446
8447 Ok(None)
8448 }
8449
8450 pub fn get_neuron_by_coordinates(
8468 &self,
8469 cortical_id: &CorticalID,
8470 x: u32,
8471 y: u32,
8472 z: u32,
8473 ) -> Option<u64> {
8474 let area = self.get_cortical_area(cortical_id)?;
8476 let cortical_idx = area.cortical_idx;
8477
8478 let npu = self.npu.as_ref()?;
8480 let npu_lock = npu.lock().ok()?;
8481
8482 npu_lock
8483 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
8484 .map(|id| id as u64)
8485 }
8486
8487 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
8498 let npu = self.npu.as_ref()?;
8499 let npu_lock = npu.lock().ok()?;
8500
8501 let neuron_count = npu_lock.get_neuron_count();
8503 if (neuron_id as usize) >= neuron_count {
8504 return None;
8505 }
8506
8507 Some(
8508 npu_lock
8509 .get_neuron_coordinates(neuron_id as u32)
8510 .unwrap_or((0, 0, 0)),
8511 )
8512 }
8513
8514 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
8525 let npu = self.npu.as_ref()?;
8526 let npu_lock = npu.lock().ok()?;
8527
8528 let neuron_count = npu_lock.get_neuron_count();
8530 if (neuron_id as usize) >= neuron_count {
8531 return None;
8532 }
8533
8534 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
8535
8536 self.cortical_areas
8538 .values()
8539 .find(|area| area.cortical_idx == cortical_idx)
8540 .map(|area| area.cortical_id)
8541 }
8542
8543 pub fn get_neuron_properties(
8554 &self,
8555 neuron_id: u64,
8556 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8557 let npu = self.npu.as_ref()?;
8558 let npu_lock = npu.lock().ok()?;
8559
8560 let neuron_id_u32 = neuron_id as u32;
8561 let idx = neuron_id as usize;
8562
8563 let neuron_count = npu_lock.get_neuron_count();
8565 if idx >= neuron_count {
8566 return None;
8567 }
8568
8569 let mut properties = std::collections::HashMap::new();
8570
8571 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
8573
8574 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
8576 properties.insert("x".to_string(), serde_json::json!(x));
8577 properties.insert("y".to_string(), serde_json::json!(y));
8578 properties.insert("z".to_string(), serde_json::json!(z));
8579
8580 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
8582 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
8583
8584 properties.insert(
8586 "mp_charge_accumulation".to_string(),
8587 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
8588 );
8589 properties.insert(
8590 "neuron_type".to_string(),
8591 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
8592 );
8593 let (mp_drv, psp_uni) = self
8594 .cortical_idx_to_id
8595 .get(&cortical_idx)
8596 .map(|cid| {
8597 (
8598 npu_lock.get_mp_driven_psp_for_cortical(cid),
8599 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
8600 )
8601 })
8602 .unwrap_or((false, false));
8603 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
8604 properties.insert(
8605 "psp_uniform_distribution".to_string(),
8606 serde_json::json!(psp_uni),
8607 );
8608
8609 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
8612 .get_neuron_state(NeuronId(neuron_id_u32))
8613 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
8614 properties.insert(
8615 "consecutive_fire_count".to_string(),
8616 serde_json::json!(consec_count),
8617 );
8618 properties.insert(
8619 "consecutive_fire_limit".to_string(),
8620 serde_json::json!(consec_limit),
8621 );
8622 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
8623 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
8624 properties.insert("threshold".to_string(), serde_json::json!(threshold));
8625 properties.insert(
8626 "refractory_countdown".to_string(),
8627 serde_json::json!(refract_countdown),
8628 );
8629
8630 properties.insert(
8632 "leak_coefficient".to_string(),
8633 serde_json::json!(npu_lock
8634 .get_neuron_property_by_index(idx, "leak_coefficient")
8635 .unwrap_or(0.0)),
8636 );
8637 properties.insert(
8638 "resting_potential".to_string(),
8639 serde_json::json!(npu_lock
8640 .get_neuron_property_by_index(idx, "resting_potential")
8641 .unwrap_or(0.0)),
8642 );
8643 properties.insert(
8644 "excitability".to_string(),
8645 serde_json::json!(npu_lock
8646 .get_neuron_property_by_index(idx, "excitability")
8647 .unwrap_or(0.0)),
8648 );
8649 properties.insert(
8650 "threshold_limit".to_string(),
8651 serde_json::json!(npu_lock
8652 .get_neuron_property_by_index(idx, "threshold_limit")
8653 .unwrap_or(0.0)),
8654 );
8655 properties.insert(
8656 "refractory_period".to_string(),
8657 serde_json::json!(npu_lock
8658 .get_neuron_property_u16_by_index(idx, "refractory_period")
8659 .unwrap_or(0)),
8660 );
8661
8662 Some(properties)
8663 }
8664
8665 pub fn get_neuron_property(
8677 &self,
8678 neuron_id: u64,
8679 property_name: &str,
8680 ) -> Option<serde_json::Value> {
8681 self.get_neuron_properties(neuron_id)?
8682 .get(property_name)
8683 .cloned()
8684 }
8685
8686 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
8697 self.cortical_areas.keys().copied().collect()
8698 }
8699
8700 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
8707 self.cortical_areas
8708 .values()
8709 .map(|area| area.cortical_idx)
8710 .collect()
8711 }
8712
8713 pub fn get_cortical_area_names(&self) -> Vec<String> {
8720 self.cortical_areas
8721 .values()
8722 .map(|area| area.name.clone())
8723 .collect()
8724 }
8725
8726 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
8733 use crate::models::CorticalAreaExt;
8734 self.cortical_areas
8735 .values()
8736 .filter(|area| area.is_input_area())
8737 .map(|area| area.cortical_id)
8738 .collect()
8739 }
8740
8741 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
8748 use crate::models::CorticalAreaExt;
8749 self.cortical_areas
8750 .values()
8751 .filter(|area| area.is_output_area())
8752 .map(|area| area.cortical_id)
8753 .collect()
8754 }
8755
8756 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
8763 self.cortical_areas
8764 .values()
8765 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
8766 (
8767 max_w.max(area.dimensions.width as usize),
8768 max_h.max(area.dimensions.height as usize),
8769 max_d.max(area.dimensions.depth as usize),
8770 )
8771 })
8772 }
8773
8774 pub fn get_cortical_area_properties(
8785 &self,
8786 cortical_id: &CorticalID,
8787 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8788 let area = self.get_cortical_area(cortical_id)?;
8789
8790 let mut properties = std::collections::HashMap::new();
8791 properties.insert(
8792 "cortical_id".to_string(),
8793 serde_json::json!(area.cortical_id),
8794 );
8795 properties.insert(
8796 "cortical_id_s".to_string(),
8797 serde_json::json!(area.cortical_id.to_string()),
8798 );
8799 properties.insert(
8800 "cortical_idx".to_string(),
8801 serde_json::json!(area.cortical_idx),
8802 );
8803 properties.insert("name".to_string(), serde_json::json!(area.name));
8804 use crate::models::CorticalAreaExt;
8805 properties.insert(
8806 "area_type".to_string(),
8807 serde_json::json!(area.get_cortical_group()),
8808 );
8809 properties.insert(
8810 "dimensions".to_string(),
8811 serde_json::json!({
8812 "width": area.dimensions.width,
8813 "height": area.dimensions.height,
8814 "depth": area.dimensions.depth,
8815 }),
8816 );
8817 properties.insert("position".to_string(), serde_json::json!(area.position));
8818
8819 for (key, value) in &area.properties {
8821 properties.insert(key.clone(), value.clone());
8822 }
8823
8824 properties.extend(area.properties.clone());
8826
8827 Some(properties)
8828 }
8829
8830 pub fn get_all_cortical_area_properties(
8837 &self,
8838 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
8839 self.cortical_areas
8840 .keys()
8841 .filter_map(|id| self.get_cortical_area_properties(id))
8842 .collect()
8843 }
8844
8845 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
8856 self.brain_regions
8857 .get_all_region_ids()
8858 .into_iter()
8859 .cloned()
8860 .collect()
8861 }
8862
8863 pub fn get_brain_region_names(&self) -> Vec<String> {
8870 self.brain_regions
8871 .get_all_region_ids()
8872 .iter()
8873 .filter_map(|id| {
8874 self.brain_regions
8875 .get_region(id)
8876 .map(|region| region.name.clone())
8877 })
8878 .collect()
8879 }
8880
8881 pub fn get_brain_region_properties(
8892 &self,
8893 region_id: &str,
8894 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8895 let region = self.brain_regions.get_region(region_id)?;
8896
8897 let mut properties = std::collections::HashMap::new();
8898 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
8899 properties.insert("name".to_string(), serde_json::json!(region.name));
8900 properties.insert(
8901 "region_type".to_string(),
8902 serde_json::json!(format!("{:?}", region.region_type)),
8903 );
8904 properties.insert(
8905 "cortical_areas".to_string(),
8906 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
8907 );
8908
8909 properties.extend(region.properties.clone());
8911
8912 Some(properties)
8913 }
8914
8915 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
8926 self.cortical_areas.contains_key(cortical_id)
8927 }
8928
8929 pub fn brain_region_exists(&self, region_id: &str) -> bool {
8940 self.brain_regions.get_region(region_id).is_some()
8941 }
8942
8943 pub fn get_brain_region_count(&self) -> usize {
8950 self.brain_regions.region_count()
8951 }
8952
8953 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
8964 self.get_neurons_in_area(cortical_id)
8968 }
8969}
8970
8971impl std::fmt::Debug for ConnectomeManager {
8973 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8974 f.debug_struct("ConnectomeManager")
8975 .field("cortical_areas", &self.cortical_areas.len())
8976 .field("next_cortical_idx", &self.next_cortical_idx)
8977 .field("brain_regions", &self.brain_regions)
8978 .field(
8979 "npu",
8980 &if self.npu.is_some() {
8981 "Connected"
8982 } else {
8983 "Not connected"
8984 },
8985 )
8986 .field("initialized", &self.initialized)
8987 .finish()
8988 }
8989}
8990
8991#[cfg(test)]
8992mod tests {
8993 use super::*;
8994 use feagi_structures::genomic::cortical_area::CoreCorticalType;
8995
8996 #[cfg(feature = "plasticity")]
9000 #[test]
9001 fn prepare_for_new_genome_discards_memory_neurons_from_previous_brain() {
9002 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
9003 use feagi_npu_plasticity::executor::PlasticityExecutor;
9004 use feagi_npu_plasticity::{
9005 create_memory_stats_cache, AsyncPlasticityExecutor, MemoryNeuronDetail,
9006 PlasticityConfig,
9007 };
9008 use feagi_npu_runtime::StdRuntime;
9009
9010 let npu = Arc::new(TracingMutex::new(
9011 DynamicNPU::new_f32(
9012 StdRuntime::new(),
9013 feagi_npu_burst_engine::backend::CPUBackend::new(),
9014 16,
9015 16,
9016 8,
9017 )
9018 .unwrap(),
9019 "prepare-for-new-genome-test-npu",
9020 ));
9021 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
9022 PlasticityConfig::default(),
9023 create_memory_stats_cache(),
9024 npu.clone(),
9025 )));
9026
9027 let mut manager = ConnectomeManager::new_for_testing();
9028 manager.set_npu(npu);
9029 manager.set_plasticity_executor(executor.clone());
9030
9031 let previous_brain_memory_idx = 8;
9032 {
9033 let exec = executor.lock().expect("plasticity executor");
9034 PlasticityExecutor::register_memory_area(
9035 &*exec,
9036 previous_brain_memory_idx,
9037 "previous_brain_memory".to_string(),
9038 1,
9039 vec![7],
9040 None,
9041 false,
9042 );
9043 exec.restore_long_term_memory_neurons(&[MemoryNeuronDetail {
9044 neuron_id: 50_000_003,
9045 cortical_area_idx: previous_brain_memory_idx,
9046 pattern_hash: Some(9_631_261_403_772_054_764),
9047 is_longterm_memory: true,
9048 is_active: true,
9049 lifespan_current: 120,
9050 lifespan_initial: 20,
9051 lifespan_growth_rate: 3.0,
9052 creation_burst: 0,
9053 last_activation_burst: 0,
9054 activation_count: 5,
9055 spatial_signature: None,
9056 class_channels: Vec::new(),
9057 }])
9058 .expect("seeded long-term memory neuron");
9059 assert_eq!(
9060 exec.export_long_term_memory_neurons()
9061 .expect("plasticity service is initialized")
9062 .len(),
9063 1
9064 );
9065 }
9066
9067 manager
9068 .prepare_for_new_genome()
9069 .expect("genome preparation must succeed");
9070
9071 let exec = executor.lock().expect("plasticity executor");
9072 assert!(
9073 exec.export_long_term_memory_neurons()
9074 .expect("plasticity service is initialized")
9075 .is_empty(),
9076 "memory neurons from the previous brain must not survive a genome load"
9077 );
9078 assert_eq!(
9079 exec.paginated_memory_neuron_ids_in_area(previous_brain_memory_idx, 0, 10),
9080 Some((Vec::new(), 0)),
9081 "the previous brain's memory area must no longer report any memory neurons"
9082 );
9083 }
9084
9085 #[cfg(feature = "plasticity")]
9088 #[test]
9089 fn remove_cortical_area_unregisters_its_memory() {
9090 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
9091 use feagi_npu_plasticity::executor::PlasticityExecutor;
9092 use feagi_npu_plasticity::{
9093 create_memory_stats_cache, AsyncPlasticityExecutor, PlasticityConfig,
9094 };
9095 use feagi_npu_runtime::StdRuntime;
9096 use feagi_structures::genomic::cortical_area::{
9097 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
9098 };
9099
9100 let npu = Arc::new(TracingMutex::new(
9101 DynamicNPU::new_f32(
9102 StdRuntime::new(),
9103 feagi_npu_burst_engine::backend::CPUBackend::new(),
9104 16,
9105 16,
9106 8,
9107 )
9108 .unwrap(),
9109 "remove-memory-area-test-npu",
9110 ));
9111 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
9112 PlasticityConfig::default(),
9113 create_memory_stats_cache(),
9114 npu.clone(),
9115 )));
9116 let mut manager = ConnectomeManager::new_for_testing();
9117 manager.set_npu(npu);
9118 manager.set_plasticity_executor(executor.clone());
9119
9120 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
9121 let mut area = CorticalArea::new(
9122 mem_id,
9123 0,
9124 "kernel_mem".to_string(),
9125 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9126 (0, 0, 0).into(),
9127 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9128 )
9129 .unwrap();
9130 area.properties
9131 .insert("is_mem_type".to_string(), serde_json::json!(true));
9132 let idx = manager.add_cortical_area(area).unwrap();
9133 {
9134 let exec = executor.lock().expect("plasticity executor");
9135 PlasticityExecutor::register_memory_area(
9136 &*exec,
9137 idx,
9138 mem_id.as_base_64(),
9139 1,
9140 vec![7],
9141 None,
9142 false,
9143 );
9144 }
9145
9146 manager
9147 .remove_cortical_area(&mem_id)
9148 .expect("memory area removal");
9149
9150 assert!(manager.get_cortical_id(idx).is_none());
9151 let exec = executor.lock().expect("plasticity executor");
9152 let indexes = exec
9153 .get_service()
9154 .expect("plasticity service")
9155 .registered_memory_area_indexes();
9156 assert!(
9157 !indexes.contains(&idx),
9158 "deleted memory area idx={idx} must leave plasticity, still registered: {indexes:?}"
9159 );
9160 }
9161
9162 #[test]
9163 fn test_singleton_instance() {
9164 let instance1 = ConnectomeManager::instance();
9165 let instance2 = ConnectomeManager::instance();
9166
9167 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
9169 }
9170
9171 #[test]
9172 fn test_add_cortical_area() {
9173 ConnectomeManager::reset_for_testing();
9174
9175 let instance = ConnectomeManager::instance();
9176 let mut manager = instance.write();
9177
9178 use feagi_structures::genomic::cortical_area::{
9179 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9180 };
9181 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9183 let area = CorticalArea::new(
9184 cortical_id,
9185 0,
9186 "Visual Input".to_string(),
9187 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
9188 (0, 0, 0).into(),
9189 cortical_type,
9190 )
9191 .unwrap();
9192
9193 let initial_count = manager.get_cortical_area_count();
9194 let _cortical_idx = manager.add_cortical_area(area).unwrap();
9195
9196 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
9197 assert!(manager.has_cortical_area(&cortical_id));
9198 assert!(manager.is_initialized());
9199 }
9200
9201 #[test]
9202 fn refresh_all_connectome_hashes_publishes_mappings() {
9203 use feagi_structures::genomic::cortical_area::{
9204 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CustomCorticalType,
9205 };
9206
9207 let src_id = CorticalID::try_from_bytes(b"chashsrc").unwrap();
9208 let dst_id = CorticalID::try_from_bytes(b"chashdst").unwrap();
9209 let mut src = CorticalArea::new(
9210 src_id,
9211 1,
9212 "src".to_string(),
9213 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9214 (0, 0, 0).into(),
9215 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
9216 )
9217 .unwrap();
9218 src.properties.insert(
9219 "cortical_mapping_dst".to_string(),
9220 serde_json::json!({
9221 dst_id.as_base_64(): [{ "morphology_id": "projector" }]
9222 }),
9223 );
9224 let mut manager = ConnectomeManager::new_for_testing();
9225 manager.add_cortical_area(src).unwrap();
9226 manager.refresh_all_connectome_hashes();
9227 let mappings_hash = feagi_state_manager::StateManager::instance()
9228 .read()
9229 .get_cortical_mappings_hash();
9230 assert_ne!(
9231 mappings_hash, 0,
9232 "refresh_all_connectome_hashes must publish cortical_mappings_hash"
9233 );
9234 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
9235 classifier_id: "clf-hash".to_string(),
9236 name: "hash_demo".to_string(),
9237 parent_region_id: "root".to_string(),
9238 coordinates_3d: [0, 0, 0],
9239 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
9240 kernel_area_id: None,
9241 class_area_id: None,
9242 mask_area_id: None,
9243 kernel_size: None,
9244 fields: Vec::new(),
9245 kernel_memory_id: "mkmem1".to_string(),
9246 class_memory_id: "mcmem1".to_string(),
9247 properties: HashMap::new(),
9248 });
9249 manager.refresh_all_connectome_hashes();
9250 let classifiers_hash = feagi_state_manager::StateManager::instance()
9251 .read()
9252 .get_classifiers_hash();
9253 assert_ne!(
9254 classifiers_hash, 0,
9255 "refresh_all_connectome_hashes must publish classifiers_hash"
9256 );
9257 }
9258
9259 #[test]
9260 fn test_cortical_area_lookups() {
9261 ConnectomeManager::reset_for_testing();
9262
9263 let instance = ConnectomeManager::instance();
9264 let mut manager = instance.write();
9265
9266 use feagi_structures::genomic::cortical_area::{
9267 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9268 };
9269 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9271 let area = CorticalArea::new(
9272 cortical_id,
9273 0,
9274 "Test Area".to_string(),
9275 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9276 (0, 0, 0).into(),
9277 cortical_type,
9278 )
9279 .unwrap();
9280
9281 let cortical_idx = manager.add_cortical_area(area).unwrap();
9282
9283 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
9285
9286 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
9288
9289 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
9291 assert_eq!(retrieved_area.name, "Test Area");
9292 }
9293
9294 #[test]
9295 fn test_remove_cortical_area() {
9296 ConnectomeManager::reset_for_testing();
9297
9298 let instance = ConnectomeManager::instance();
9299 let mut manager = instance.write();
9300
9301 use feagi_structures::genomic::cortical_area::{
9302 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9303 };
9304 let cortical_id = CoreCorticalType::Power.to_cortical_id();
9305
9306 if manager.has_cortical_area(&cortical_id) {
9308 manager.remove_cortical_area(&cortical_id).unwrap();
9309 }
9310
9311 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9312 let area = CorticalArea::new(
9313 cortical_id,
9314 0,
9315 "Test".to_string(),
9316 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9317 (0, 0, 0).into(),
9318 cortical_type,
9319 )
9320 .unwrap();
9321
9322 let initial_count = manager.get_cortical_area_count();
9323 manager.add_cortical_area(area).unwrap();
9324 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
9325
9326 let areas_hash_before = feagi_state_manager::StateManager::instance()
9327 .read()
9328 .get_cortical_areas_hash();
9329 let geometry_hash_before = feagi_state_manager::StateManager::instance()
9330 .read()
9331 .get_brain_geometry_hash();
9332
9333 manager.remove_cortical_area(&cortical_id).unwrap();
9334 assert_eq!(manager.get_cortical_area_count(), initial_count);
9335 assert!(!manager.has_cortical_area(&cortical_id));
9336
9337 let areas_hash_after = feagi_state_manager::StateManager::instance()
9338 .read()
9339 .get_cortical_areas_hash();
9340 let geometry_hash_after = feagi_state_manager::StateManager::instance()
9341 .read()
9342 .get_brain_geometry_hash();
9343 assert_ne!(
9344 areas_hash_before, areas_hash_after,
9345 "remove_cortical_area must publish a new cortical_areas_hash"
9346 );
9347 assert_ne!(
9348 geometry_hash_before, geometry_hash_after,
9349 "remove_cortical_area must publish a new brain_geometry_hash"
9350 );
9351 }
9352
9353 #[test]
9354 fn test_duplicate_area_error() {
9355 ConnectomeManager::reset_for_testing();
9356
9357 let instance = ConnectomeManager::instance();
9358 let mut manager = instance.write();
9359
9360 use feagi_structures::genomic::cortical_area::{
9361 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9362 };
9363 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
9366 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9367 let area1 = CorticalArea::new(
9368 cortical_id,
9369 0,
9370 "First".to_string(),
9371 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9372 (0, 0, 0).into(),
9373 cortical_type,
9374 )
9375 .unwrap();
9376
9377 let area2 = CorticalArea::new(
9378 cortical_id, 1,
9380 "Second".to_string(),
9381 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9382 (0, 0, 0).into(),
9383 cortical_type,
9384 )
9385 .unwrap();
9386
9387 manager.add_cortical_area(area1).unwrap();
9388 let result = manager.add_cortical_area(area2);
9389
9390 assert!(result.is_err());
9391 }
9392
9393 #[test]
9394 fn test_brain_region_management() {
9395 ConnectomeManager::reset_for_testing();
9396
9397 let instance = ConnectomeManager::instance();
9398 let mut manager = instance.write();
9399
9400 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
9401 let region_id_str = region_id.to_string();
9402 let root = BrainRegion::new(
9403 region_id,
9404 "Root".to_string(),
9405 feagi_structures::genomic::brain_regions::RegionType::Undefined,
9406 )
9407 .unwrap();
9408
9409 let initial_count = manager.get_brain_region_ids().len();
9410 manager.add_brain_region(root, None).unwrap();
9411
9412 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
9413 assert!(manager.get_brain_region(®ion_id_str).is_some());
9414 }
9415
9416 #[test]
9417 fn test_update_brain_region_description_property() {
9418 ConnectomeManager::reset_for_testing();
9419
9420 let instance = ConnectomeManager::instance();
9421 let mut manager = instance.write();
9422
9423 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
9424 let region_id_str = region_id.to_string();
9425 let root = BrainRegion::new(
9426 region_id,
9427 "Root".to_string(),
9428 feagi_structures::genomic::brain_regions::RegionType::Undefined,
9429 )
9430 .unwrap();
9431 manager.add_brain_region(root, None).unwrap();
9432
9433 let mut properties = std::collections::HashMap::new();
9434 properties.insert(
9435 "description".to_string(),
9436 serde_json::json!("Holds core physiology"),
9437 );
9438 manager
9439 .update_brain_region_properties(®ion_id_str, properties)
9440 .unwrap();
9441
9442 let updated = manager
9443 .get_brain_region(®ion_id_str)
9444 .expect("region exists after description update");
9445 assert_eq!(
9446 updated.get_property("description"),
9447 Some(&serde_json::json!("Holds core physiology"))
9448 );
9449 }
9450
9451 #[test]
9452 fn test_synapse_operations() {
9453 use feagi_npu_burst_engine::npu::RustNPU;
9454 use feagi_npu_burst_engine::TracingMutex;
9455 use std::sync::Arc;
9456
9457 use feagi_npu_burst_engine::backend::CPUBackend;
9459 use feagi_npu_burst_engine::DynamicNPU;
9460 use feagi_npu_runtime::StdRuntime;
9461
9462 let runtime = StdRuntime;
9463 let backend = CPUBackend::new();
9464 let npu_result =
9465 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
9466 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
9467 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
9468
9469 use feagi_structures::genomic::cortical_area::{
9471 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9472 };
9473 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9475 let area = CorticalArea::new(
9476 cortical_id,
9477 0, "Test Area".to_string(),
9479 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
9480 (0, 0, 0).into(), cortical_type,
9482 )
9483 .unwrap();
9484 let cortical_idx = manager.add_cortical_area(area).unwrap();
9485
9486 if let Some(npu_arc) = manager.get_npu() {
9488 if let Ok(mut npu_guard) = npu_arc.try_lock() {
9489 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
9490 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
9491 }
9492 }
9493 }
9494
9495 let neuron1_id = manager
9497 .add_neuron(
9498 &cortical_id,
9499 0,
9500 0,
9501 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
9513 .unwrap();
9514
9515 let neuron2_id = manager
9516 .add_neuron(
9517 &cortical_id,
9518 1,
9519 0,
9520 0, 100.0,
9522 f32::MAX, 0.1,
9524 -60.0,
9525 0,
9526 2,
9527 1.0,
9528 5,
9529 10,
9530 false,
9531 )
9532 .unwrap();
9533
9534 manager
9536 .create_synapse(
9537 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
9541 .unwrap();
9542
9543 println!("✅ Synapse creation test passed");
9546 }
9547
9548 #[test]
9549 fn test_apply_cortical_mapping_missing_rules_is_ok() {
9550 let mut manager = ConnectomeManager::new_for_testing();
9553
9554 use feagi_structures::genomic::cortical_area::{
9555 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9556 };
9557
9558 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
9559 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
9560
9561 let src_area = CorticalArea::new(
9562 src_id,
9563 0,
9564 "src".to_string(),
9565 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9566 (0, 0, 0).into(),
9567 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9568 )
9569 .unwrap();
9570
9571 let dst_area = CorticalArea::new(
9572 dst_id,
9573 1,
9574 "dst".to_string(),
9575 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9576 (0, 0, 0).into(),
9577 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9578 )
9579 .unwrap();
9580
9581 manager.add_cortical_area(src_area).unwrap();
9582 manager.add_cortical_area(dst_area).unwrap();
9583
9584 let count = manager
9586 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
9587 .unwrap();
9588 assert_eq!(count, 0);
9589
9590 manager
9592 .update_cortical_mapping(
9593 &src_id,
9594 &dst_id,
9595 vec![serde_json::json!({"morphology_id":"m1"})],
9596 )
9597 .unwrap();
9598 manager
9599 .update_cortical_mapping(&src_id, &dst_id, vec![])
9600 .unwrap();
9601
9602 let count2 = manager
9603 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
9604 .unwrap();
9605 assert_eq!(count2, 0);
9606 }
9607
9608 #[test]
9609 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
9610 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
9611 let mapping_dst = serde_json::json!({
9612 "csrc0002": [
9613 {"morphology_id": "m1"}
9614 ]
9615 });
9616 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
9617
9618 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
9619 .expect("legacy destination key should resolve");
9620 assert_eq!(rules.len(), 1);
9621 assert_eq!(
9622 rules[0].get("morphology_id").and_then(|v| v.as_str()),
9623 Some("m1")
9624 );
9625 }
9626
9627 #[test]
9628 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
9629 use feagi_npu_burst_engine::backend::CPUBackend;
9630 use feagi_npu_burst_engine::RustNPU;
9631 use feagi_npu_burst_engine::TracingMutex;
9632 use feagi_npu_runtime::StdRuntime;
9633 use feagi_structures::genomic::cortical_area::{
9634 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9635 };
9636 use std::sync::Arc;
9637
9638 let runtime = StdRuntime;
9639 let backend = CPUBackend::new();
9640 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9641 let dyn_npu = Arc::new(TracingMutex::new(
9642 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9643 "TestNPU",
9644 ));
9645 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9646
9647 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
9648 let area = CorticalArea::new(
9649 area_id,
9650 0,
9651 "n".to_string(),
9652 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9653 (0, 0, 0).into(),
9654 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9655 )
9656 .unwrap();
9657
9658 manager.add_cortical_area(area).unwrap();
9659 let nid = manager
9660 .add_neuron(
9661 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
9662 )
9663 .unwrap();
9664
9665 let props = manager
9666 .get_neuron_properties(nid)
9667 .expect("neuron properties");
9668 for key in [
9669 "consecutive_fire_count",
9670 "consecutive_fire_limit",
9671 "snooze_period",
9672 "membrane_potential",
9673 "threshold",
9674 "refractory_countdown",
9675 "mp_charge_accumulation",
9676 "neuron_type",
9677 "mp_driven_psp",
9678 "psp_uniform_distribution",
9679 "leak_coefficient",
9680 "resting_potential",
9681 "excitability",
9682 "threshold_limit",
9683 "refractory_period",
9684 ] {
9685 assert!(props.contains_key(key), "missing neuron state key: {key}");
9686 }
9687 }
9688
9689 #[test]
9690 fn test_mapping_deletion_prunes_synapses_between_areas() {
9691 use feagi_npu_burst_engine::backend::CPUBackend;
9692 use feagi_npu_burst_engine::RustNPU;
9693 use feagi_npu_burst_engine::TracingMutex;
9694 use feagi_npu_runtime::StdRuntime;
9695 use feagi_structures::genomic::cortical_area::{
9696 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9697 };
9698 use std::sync::Arc;
9699
9700 let runtime = StdRuntime;
9702 let backend = CPUBackend::new();
9703 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9704 let dyn_npu = Arc::new(TracingMutex::new(
9705 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9706 "TestNPU",
9707 ));
9708 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9709
9710 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
9712 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
9713
9714 let src_area = CorticalArea::new(
9715 src_id,
9716 0,
9717 "src".to_string(),
9718 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9719 (0, 0, 0).into(),
9720 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9721 )
9722 .unwrap();
9723 let dst_area = CorticalArea::new(
9724 dst_id,
9725 1,
9726 "dst".to_string(),
9727 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9728 (0, 0, 0).into(),
9729 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9730 )
9731 .unwrap();
9732
9733 manager.add_cortical_area(src_area).unwrap();
9734 manager.add_cortical_area(dst_area).unwrap();
9735
9736 let s0 = manager
9738 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9739 .unwrap();
9740 let s1 = manager
9741 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9742 .unwrap();
9743 let t0 = manager
9744 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9745 .unwrap();
9746 let t1 = manager
9747 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9748 .unwrap();
9749
9750 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
9752 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
9753
9754 {
9756 let mut npu = dyn_npu.lock().unwrap();
9757 npu.rebuild_synapse_index();
9758 assert_eq!(npu.get_synapse_count(), 2);
9759 }
9760
9761 manager
9763 .update_cortical_mapping(&src_id, &dst_id, vec![])
9764 .unwrap();
9765 let created = manager
9766 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9767 .unwrap();
9768 assert_eq!(created, 0);
9769
9770 {
9772 let mut npu = dyn_npu.lock().unwrap();
9773 npu.rebuild_synapse_index();
9775 assert_eq!(npu.get_synapse_count(), 0);
9776 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
9777 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
9778 }
9779 }
9780
9781 #[test]
9782 fn test_mapping_update_prunes_synapses_between_areas() {
9783 use feagi_npu_burst_engine::backend::CPUBackend;
9784 use feagi_npu_burst_engine::RustNPU;
9785 use feagi_npu_burst_engine::TracingMutex;
9786 use feagi_npu_runtime::StdRuntime;
9787 use feagi_structures::genomic::cortical_area::{
9788 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9789 };
9790 use std::sync::Arc;
9791
9792 let runtime = StdRuntime;
9794 let backend = CPUBackend::new();
9795 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9796 let dyn_npu = Arc::new(TracingMutex::new(
9797 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9798 "TestNPU",
9799 ));
9800 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9801
9802 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9804
9805 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
9808 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
9809
9810 let src_area = CorticalArea::new(
9811 src_id,
9812 0,
9813 "src".to_string(),
9814 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9815 (0, 0, 0).into(),
9816 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9817 )
9818 .unwrap();
9819 let dst_area = CorticalArea::new(
9820 dst_id,
9821 0,
9822 "dst".to_string(),
9823 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9824 (0, 0, 0).into(),
9825 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9826 )
9827 .unwrap();
9828
9829 manager.add_cortical_area(src_area).unwrap();
9830 manager.add_cortical_area(dst_area).unwrap();
9831
9832 let s0 = manager
9834 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9835 .unwrap();
9836 let s1 = manager
9837 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9838 .unwrap();
9839 let t0 = manager
9840 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9841 .unwrap();
9842 let t1 = manager
9843 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9844 .unwrap();
9845
9846 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
9848 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
9849
9850 {
9852 let mut npu = dyn_npu.lock().unwrap();
9853 npu.rebuild_synapse_index();
9854 assert_eq!(npu.get_synapse_count(), 2);
9855 }
9856
9857 manager
9863 .update_cortical_mapping(
9864 &src_id,
9865 &dst_id,
9866 vec![serde_json::json!({
9867 "morphology_id": "episodic_memory",
9868 "morphology_scalar": [1],
9869 "postSynapticCurrent_multiplier": 1,
9870 "plasticity_flag": false,
9871 "plasticity_constant": 0,
9872 "ltp_multiplier": 0,
9873 "ltd_multiplier": 0,
9874 "plasticity_window": 0,
9875 })],
9876 )
9877 .unwrap();
9878 let created = manager
9879 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9880 .unwrap();
9881 assert_eq!(created, 0);
9882
9883 {
9885 let mut npu = dyn_npu.lock().unwrap();
9886 npu.rebuild_synapse_index();
9888 assert_eq!(npu.get_synapse_count(), 0);
9889 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
9890 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
9891 }
9892 }
9893
9894 #[test]
9895 fn test_upstream_area_tracking() {
9896 use crate::models::cortical_area::CorticalArea;
9898 use feagi_npu_burst_engine::backend::CPUBackend;
9899 use feagi_npu_burst_engine::TracingMutex;
9900 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9901 use feagi_npu_runtime::StdRuntime;
9902 use feagi_structures::genomic::cortical_area::{
9903 CorticalAreaDimensions, CorticalAreaType, CorticalID,
9904 };
9905
9906 let runtime = StdRuntime;
9908 let backend = CPUBackend::new();
9909 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9910 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9911 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9912
9913 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9916
9917 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
9919 let src_area = CorticalArea::new(
9920 src_id,
9921 0,
9922 "Source Area".to_string(),
9923 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9924 (0, 0, 0).into(),
9925 CorticalAreaType::Custom(
9926 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9927 ),
9928 )
9929 .unwrap();
9930 let src_idx = manager.add_cortical_area(src_area).unwrap();
9931
9932 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
9934 let dst_area = CorticalArea::new(
9935 dst_id,
9936 0,
9937 "Dest Area".to_string(),
9938 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9939 (0, 0, 0).into(),
9940 CorticalAreaType::Custom(
9941 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9942 ),
9943 )
9944 .unwrap();
9945 manager.add_cortical_area(dst_area).unwrap();
9946
9947 {
9949 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
9950 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
9951 assert!(
9952 upstream.as_array().unwrap().is_empty(),
9953 "Upstream areas should be empty initially"
9954 );
9955 }
9956
9957 let mapping_data = vec![serde_json::json!({
9959 "morphology_id": "episodic_memory",
9960 "morphology_scalar": 1,
9961 "postSynapticCurrent_multiplier": 1.0,
9962 })];
9963 manager
9964 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
9965 .unwrap();
9966 manager
9967 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9968 .unwrap();
9969
9970 {
9972 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
9973 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
9974 assert_eq!(
9975 upstream_areas[0], src_idx,
9976 "Upstream area should be src_idx"
9977 );
9978 }
9979
9980 manager
9982 .update_cortical_mapping(&src_id, &dst_id, vec![])
9983 .unwrap();
9984 manager
9985 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9986 .unwrap();
9987
9988 {
9990 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
9991 assert_eq!(
9992 upstream_areas.len(),
9993 0,
9994 "Should have 0 upstream areas after deletion"
9995 );
9996 }
9997 }
9998
9999 #[test]
10000 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
10001 use crate::models::cortical_area::CorticalArea;
10002 use feagi_npu_burst_engine::backend::CPUBackend;
10003 use feagi_npu_burst_engine::TracingMutex;
10004 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10005 use feagi_npu_runtime::StdRuntime;
10006 use feagi_structures::genomic::cortical_area::{
10007 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10008 };
10009 use std::sync::Arc;
10010
10011 let runtime = StdRuntime;
10012 let backend = CPUBackend::new();
10013 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10014 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10015 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10016 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10017
10018 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
10019 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
10020 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
10021 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
10022
10023 let a1_area = CorticalArea::new(
10024 a1_id,
10025 0,
10026 "A1".to_string(),
10027 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10028 (0, 0, 0).into(),
10029 CorticalAreaType::Custom(
10030 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10031 ),
10032 )
10033 .unwrap();
10034 let a2_area = CorticalArea::new(
10035 a2_id,
10036 0,
10037 "A2".to_string(),
10038 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10039 (0, 0, 0).into(),
10040 CorticalAreaType::Custom(
10041 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10042 ),
10043 )
10044 .unwrap();
10045
10046 let mut m1_area = CorticalArea::new(
10047 m1_id,
10048 0,
10049 "M1".to_string(),
10050 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10051 (0, 0, 0).into(),
10052 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10053 )
10054 .unwrap();
10055 m1_area
10056 .properties
10057 .insert("is_mem_type".to_string(), serde_json::json!(true));
10058 m1_area
10059 .properties
10060 .insert("temporal_depth".to_string(), serde_json::json!(1));
10061
10062 let mut m2_area = CorticalArea::new(
10063 m2_id,
10064 0,
10065 "M2".to_string(),
10066 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10067 (0, 0, 0).into(),
10068 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10069 )
10070 .unwrap();
10071 m2_area
10072 .properties
10073 .insert("is_mem_type".to_string(), serde_json::json!(true));
10074 m2_area
10075 .properties
10076 .insert("temporal_depth".to_string(), serde_json::json!(1));
10077
10078 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
10079 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
10080 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
10081 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
10082
10083 manager
10084 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10085 .unwrap();
10086 manager
10087 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10088 .unwrap();
10089
10090 let episodic_mapping = vec![serde_json::json!({
10091 "morphology_id": "episodic_memory",
10092 "morphology_scalar": 1,
10093 "postSynapticCurrent_multiplier": 1.0,
10094 })];
10095 manager
10096 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
10097 .unwrap();
10098 manager
10099 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
10100 .unwrap();
10101 manager
10102 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
10103 .unwrap();
10104 manager
10105 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
10106 .unwrap();
10107
10108 let assoc_mapping = vec![serde_json::json!({
10109 "morphology_id": "associative_memory",
10110 "morphology_scalar": 1,
10111 "postSynapticCurrent_multiplier": 1.0,
10112 "plasticity_flag": true,
10113 "plasticity_constant": 1,
10114 "ltp_multiplier": 1,
10115 "ltd_multiplier": 1,
10116 "plasticity_window": 5,
10117 })];
10118 manager
10119 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
10120 .unwrap();
10121 manager
10122 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
10123 .unwrap();
10124 manager
10126 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
10127 .unwrap();
10128 manager
10129 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
10130 .unwrap();
10131
10132 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
10133 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
10134 assert_eq!(
10135 upstream_m1.len(),
10136 2,
10137 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
10138 );
10139 assert_eq!(
10140 upstream_m2.len(),
10141 2,
10142 "M2 should have A2 and M1 as upstreams"
10143 );
10144
10145 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
10146 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
10147
10148 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
10149 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
10150 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
10151 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
10152 assert!(upstream_m1.contains(&a1_idx));
10153 assert!(upstream_m1.contains(&m2_idx));
10154 assert!(upstream_m2.contains(&a2_idx));
10155 assert!(upstream_m2.contains(&m1_idx));
10156
10157 {
10159 let mut npu_lock = dyn_npu.lock().unwrap();
10160 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
10161 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
10162 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
10163 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
10164 npu_lock.process_burst().expect("Burst processing failed");
10165 }
10166
10167 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
10168 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
10169 assert_eq!(
10170 upstream_m1.len(),
10171 2,
10172 "M1 should keep 2 upstreams after firing"
10173 );
10174 assert_eq!(
10175 upstream_m2.len(),
10176 2,
10177 "M2 should keep 2 upstreams after firing"
10178 );
10179
10180 let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
10181 let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
10182 assert_eq!(
10183 episodic_upstream_m1,
10184 vec![a1_idx],
10185 "Episodic upstream list for M1 should exclude associative-only memory source M2"
10186 );
10187 assert_eq!(
10188 episodic_upstream_m2,
10189 vec![a2_idx],
10190 "Episodic upstream list for M2 should exclude associative-only memory source M1"
10191 );
10192 }
10193
10194 #[test]
10195 fn test_memory_to_non_memory_requires_associative_morphology() {
10196 use crate::models::cortical_area::CorticalArea;
10197 use feagi_structures::genomic::cortical_area::{
10198 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10199 MemoryCorticalType,
10200 };
10201
10202 let mut manager = ConnectomeManager::new_for_testing();
10203 let memory_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10204 let destination_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
10205 let memory_area = CorticalArea::new(
10206 memory_id,
10207 0,
10208 "Memory Area".to_string(),
10209 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10210 (0, 0, 0).into(),
10211 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10212 )
10213 .unwrap();
10214 let destination_area = CorticalArea::new(
10215 destination_id,
10216 0,
10217 "Destination Area".to_string(),
10218 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10219 (1, 0, 0).into(),
10220 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10221 )
10222 .unwrap();
10223 manager.add_cortical_area(memory_area).unwrap();
10224 manager.add_cortical_area(destination_area).unwrap();
10225
10226 let invalid = manager.update_cortical_mapping(
10227 &memory_id,
10228 &destination_id,
10229 vec![serde_json::json!({"morphology_id": "episodic_memory"})],
10230 );
10231 assert!(matches!(invalid, Err(BduError::InvalidMorphology(_))));
10232
10233 manager
10234 .update_cortical_mapping(
10235 &memory_id,
10236 &destination_id,
10237 vec![serde_json::json!({"morphology_id": "associative_memory"})],
10238 )
10239 .unwrap();
10240 }
10241
10242 #[test]
10243 fn test_memory_twin_created_for_memory_mapping() {
10244 use crate::models::cortical_area::CorticalArea;
10245 use feagi_npu_burst_engine::backend::CPUBackend;
10246 use feagi_npu_burst_engine::TracingMutex;
10247 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10248 use feagi_npu_runtime::StdRuntime;
10249 use feagi_structures::genomic::cortical_area::{
10250 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10251 MemoryCorticalType,
10252 };
10253 use std::sync::Arc;
10254
10255 let runtime = StdRuntime;
10256 let backend = CPUBackend::new();
10257 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10258 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10259 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10260 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10261
10262 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
10263 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10264
10265 let src_area = CorticalArea::new(
10266 src_id,
10267 0,
10268 "Source Area".to_string(),
10269 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10270 (0, 0, 0).into(),
10271 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10272 )
10273 .unwrap();
10274 let mut dst_area = CorticalArea::new(
10275 dst_id,
10276 0,
10277 "Memory Area".to_string(),
10278 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10279 (0, 0, 0).into(),
10280 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10281 )
10282 .unwrap();
10283 dst_area
10284 .properties
10285 .insert("is_mem_type".to_string(), serde_json::json!(true));
10286 dst_area
10287 .properties
10288 .insert("temporal_depth".to_string(), serde_json::json!(1));
10289
10290 manager.add_cortical_area(src_area).unwrap();
10291 manager.add_cortical_area(dst_area).unwrap();
10292
10293 let mapping_data = vec![serde_json::json!({
10294 "morphology_id": "episodic_memory",
10295 "morphology_scalar": 1,
10296 "postSynapticCurrent_multiplier": 1.0,
10297 })];
10298 manager
10299 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
10300 .unwrap();
10301 manager
10302 .regenerate_synapses_for_mapping(&src_id, &dst_id)
10303 .unwrap();
10304
10305 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
10306 let twin_map = memory_area
10307 .properties
10308 .get("memory_twin_areas")
10309 .and_then(|v| v.as_object())
10310 .expect("memory_twin_areas should be set");
10311 let twin_id_str = twin_map
10312 .get(&src_id.as_base_64())
10313 .and_then(|v| v.as_str())
10314 .expect("Missing twin entry for upstream area");
10315 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
10316 let mapping = memory_area
10317 .properties
10318 .get("cortical_mapping_dst")
10319 .and_then(|v| v.as_object())
10320 .and_then(|map| map.get(&twin_id.as_base_64()))
10321 .and_then(|v| v.as_array())
10322 .expect("Missing memory replay mapping for twin area");
10323 let uses_replay = mapping.iter().any(|rule| {
10324 rule.get("morphology_id")
10325 .and_then(|v| v.as_str())
10326 .is_some_and(|id| id == "memory_replay")
10327 });
10328 assert!(uses_replay, "Expected memory_replay mapping for twin area");
10329
10330 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10331 assert!(matches!(
10332 twin_area.cortical_type,
10333 CorticalAreaType::Custom(_)
10334 ));
10335 assert_eq!(
10336 twin_area
10337 .properties
10338 .get("memory_twin_of")
10339 .and_then(|v| v.as_str()),
10340 Some(src_id.as_base_64().as_str())
10341 );
10342 assert_eq!(
10343 twin_area
10344 .properties
10345 .get("memory_twin_for")
10346 .and_then(|v| v.as_str()),
10347 Some(dst_id.as_base_64().as_str())
10348 );
10349 }
10350
10351 #[test]
10352 fn test_associative_memory_between_memory_areas_creates_synapses() {
10353 use crate::models::cortical_area::CorticalArea;
10354 use feagi_npu_burst_engine::backend::CPUBackend;
10355 use feagi_npu_burst_engine::TracingMutex;
10356 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10357 use feagi_npu_runtime::StdRuntime;
10358 use feagi_structures::genomic::cortical_area::{
10359 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10360 };
10361 use std::sync::Arc;
10362
10363 let runtime = StdRuntime;
10364 let backend = CPUBackend::new();
10365 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10366 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10367 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10368 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10369
10370 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
10371 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
10372
10373 let mut m1_area = CorticalArea::new(
10374 m1_id,
10375 0,
10376 "Memory M1".to_string(),
10377 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10378 (0, 0, 0).into(),
10379 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10380 )
10381 .unwrap();
10382 m1_area
10383 .properties
10384 .insert("is_mem_type".to_string(), serde_json::json!(true));
10385 m1_area
10386 .properties
10387 .insert("temporal_depth".to_string(), serde_json::json!(1));
10388
10389 let mut m2_area = CorticalArea::new(
10390 m2_id,
10391 0,
10392 "Memory M2".to_string(),
10393 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10394 (0, 0, 0).into(),
10395 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10396 )
10397 .unwrap();
10398 m2_area
10399 .properties
10400 .insert("is_mem_type".to_string(), serde_json::json!(true));
10401 m2_area
10402 .properties
10403 .insert("temporal_depth".to_string(), serde_json::json!(1));
10404
10405 manager.add_cortical_area(m1_area).unwrap();
10406 manager.add_cortical_area(m2_area).unwrap();
10407
10408 manager
10409 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10410 .unwrap();
10411 manager
10412 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10413 .unwrap();
10414
10415 let mapping_data = vec![serde_json::json!({
10416 "morphology_id": "associative_memory",
10417 "morphology_scalar": 1,
10418 "postSynapticCurrent_multiplier": 1.0,
10419 "plasticity_flag": true,
10420 "plasticity_constant": 1,
10421 "ltp_multiplier": 1,
10422 "ltd_multiplier": 1,
10423 "plasticity_window": 5,
10424 })];
10425 manager
10426 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
10427 .unwrap();
10428 let created = manager
10429 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
10430 .unwrap();
10431 assert!(
10432 created > 0,
10433 "Expected associative memory mapping between memory areas to create synapses"
10434 );
10435 let npu_guard = dyn_npu.lock().unwrap();
10436 let assoc_tagged =
10437 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
10438 assert!(
10439 assoc_tagged >= 1,
10440 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
10441 );
10442 }
10443
10444 #[test]
10445 fn test_scan_twin_uses_field_xy_and_class_count() {
10446 use crate::models::cortical_area::CorticalArea;
10447 use feagi_npu_burst_engine::backend::CPUBackend;
10448 use feagi_npu_burst_engine::TracingMutex;
10449 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10450 use feagi_npu_runtime::StdRuntime;
10451 use feagi_structures::genomic::cortical_area::{
10452 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10453 MemoryCorticalType,
10454 };
10455 use std::sync::Arc;
10456
10457 let runtime = StdRuntime;
10458 let backend = CPUBackend::new();
10459 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10460 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10461 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10462 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10463
10464 let field_id = CorticalID::try_from_bytes(b"cfld0001").unwrap();
10465 let class_id = CorticalID::try_from_bytes(b"ccls0001").unwrap();
10466 let mem_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10467
10468 let field_area = CorticalArea::new(
10469 field_id,
10470 0,
10471 "Field".to_string(),
10472 CorticalAreaDimensions::new(8, 6, 4).unwrap(),
10473 (0, 0, 0).into(),
10474 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10475 )
10476 .unwrap();
10477 let class_area = CorticalArea::new(
10478 class_id,
10479 0,
10480 "Class".to_string(),
10481 CorticalAreaDimensions::new(1, 1, 5).unwrap(),
10482 (20, 0, 0).into(),
10483 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10484 )
10485 .unwrap();
10486 let mut mem_area = CorticalArea::new(
10487 mem_id,
10488 0,
10489 "KernelMem".to_string(),
10490 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10491 (40, 0, 0).into(),
10492 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10493 )
10494 .unwrap();
10495 mem_area
10496 .properties
10497 .insert("is_mem_type".to_string(), serde_json::json!(true));
10498 mem_area.properties.insert(
10499 "classifier_class_area_id".to_string(),
10500 serde_json::json!(class_id.as_base_64()),
10501 );
10502
10503 manager.add_cortical_area(field_area).unwrap();
10504 manager.add_cortical_area(class_area).unwrap();
10505 manager.add_cortical_area(mem_area).unwrap();
10506
10507 let mapping_data = vec![serde_json::json!({
10508 "morphology_id": "episodic_scan",
10509 "morphology_scalar": 1,
10510 "postSynapticCurrent_multiplier": 1.0,
10511 })];
10512 manager
10513 .update_cortical_mapping(&field_id, &mem_id, mapping_data)
10514 .unwrap();
10515 manager
10516 .regenerate_synapses_for_mapping(&field_id, &mem_id)
10517 .unwrap();
10518
10519 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
10520 let twin_map = memory_area
10521 .properties
10522 .get("memory_twin_areas")
10523 .and_then(|v| v.as_object())
10524 .expect("memory_twin_areas should be set");
10525 let twin_id_str = twin_map
10526 .get(&field_id.as_base_64())
10527 .and_then(|v| v.as_str())
10528 .expect("Missing scan twin entry");
10529 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
10530 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10531 assert_eq!(twin_area.dimensions.width, 8);
10532 assert_eq!(twin_area.dimensions.height, 6);
10533 assert_eq!(twin_area.dimensions.depth, 5);
10534 assert_eq!(
10535 twin_area
10536 .properties
10537 .get("scan_twin")
10538 .and_then(|v| v.as_bool()),
10539 Some(true)
10540 );
10541 let has_replay = memory_area
10542 .properties
10543 .get("cortical_mapping_dst")
10544 .and_then(|v| v.as_object())
10545 .and_then(|map| map.get(&twin_id.as_base_64()))
10546 .is_some();
10547 assert!(!has_replay, "scan twin must not receive memory_replay");
10548 let episodic_upstreams = manager.get_episodic_memory_upstream_cortical_areas(&mem_id);
10549 let field_idx = manager.get_cortical_idx(&field_id).unwrap();
10550 assert!(
10551 !episodic_upstreams.contains(&field_idx),
10552 "scan source must not enter episodic hash upstreams"
10553 );
10554 }
10555
10556 #[test]
10557 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
10558 use crate::models::cortical_area::CorticalArea;
10559 use feagi_npu_burst_engine::backend::CPUBackend;
10560 use feagi_npu_burst_engine::TracingMutex;
10561 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10562 use feagi_npu_runtime::StdRuntime;
10563 use feagi_structures::genomic::cortical_area::{
10564 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10565 MemoryCorticalType,
10566 };
10567 use std::sync::Arc;
10568
10569 let runtime = StdRuntime;
10570 let backend = CPUBackend::new();
10571 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10572 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10573 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10574 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10575
10576 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
10577 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
10578
10579 let src_area = CorticalArea::new(
10580 src_id,
10581 0,
10582 "Source Area".to_string(),
10583 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10584 (0, 0, 0).into(),
10585 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10586 )
10587 .unwrap();
10588 let mut mem_area = CorticalArea::new(
10589 mem_id,
10590 0,
10591 "Memory Area".to_string(),
10592 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10593 (0, 0, 0).into(),
10594 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10595 )
10596 .unwrap();
10597 mem_area
10598 .properties
10599 .insert("is_mem_type".to_string(), serde_json::json!(true));
10600 mem_area
10601 .properties
10602 .insert("temporal_depth".to_string(), serde_json::json!(1));
10603
10604 manager.add_cortical_area(src_area).unwrap();
10605 manager.add_cortical_area(mem_area).unwrap();
10606
10607 let twin_id = manager
10608 .build_memory_twin_id(&mem_id, &src_id)
10609 .expect("Failed to build twin id");
10610 let twin_area = CorticalArea::new(
10611 twin_id,
10612 0,
10613 "Source Area_twin".to_string(),
10614 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10615 (0, 0, 0).into(),
10616 CorticalAreaType::Custom(
10617 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10618 ),
10619 )
10620 .unwrap();
10621 manager.add_cortical_area(twin_area).unwrap();
10622
10623 let repaired = manager
10624 .ensure_memory_twin_area(&mem_id, &src_id)
10625 .expect("Failed to repair twin");
10626 assert_eq!(repaired, twin_id);
10627
10628 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
10629 let twin_map = mem_area
10630 .properties
10631 .get("memory_twin_areas")
10632 .and_then(|v| v.as_object())
10633 .expect("memory_twin_areas should be set");
10634 let twin_id_str = twin_map
10635 .get(&src_id.as_base_64())
10636 .and_then(|v| v.as_str())
10637 .expect("Missing twin entry for upstream area");
10638 assert_eq!(twin_id_str, twin_id.as_base_64());
10639
10640 let replay_map = mem_area
10641 .properties
10642 .get("cortical_mapping_dst")
10643 .and_then(|v| v.as_object())
10644 .and_then(|map| map.get(&twin_id.as_base_64()))
10645 .and_then(|v| v.as_array())
10646 .expect("Missing memory replay mapping for twin area");
10647 let uses_replay = replay_map.iter().any(|rule| {
10648 rule.get("morphology_id")
10649 .and_then(|v| v.as_str())
10650 .is_some_and(|id| id == "memory_replay")
10651 });
10652 assert!(uses_replay, "Expected memory_replay mapping for twin area");
10653
10654 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10655 assert_eq!(
10656 twin_area
10657 .properties
10658 .get("memory_twin_of")
10659 .and_then(|v| v.as_str()),
10660 Some(src_id.as_base_64().as_str())
10661 );
10662 assert_eq!(
10663 twin_area
10664 .properties
10665 .get("memory_twin_for")
10666 .and_then(|v| v.as_str()),
10667 Some(mem_id.as_base_64().as_str())
10668 );
10669 }
10670
10671 #[test]
10672 fn classifier_mapping_change_updates_kernel_slot_not_field() {
10673 let mut manager = ConnectomeManager::new_for_testing();
10674 let mut classifier = feagi_structures::genomic::classifiers::Classifier {
10675 classifier_id: "clf-1".to_string(),
10676 name: "demo".to_string(),
10677 parent_region_id: "root".to_string(),
10678 coordinates_3d: [0, 0, 0],
10679 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
10680 kernel_area_id: None,
10681 class_area_id: None,
10682 mask_area_id: None,
10683 kernel_size: None,
10684 fields: Vec::new(),
10685 kernel_memory_id: "mkmem1".to_string(),
10686 class_memory_id: "mcmem1".to_string(),
10687 properties: HashMap::new(),
10688 };
10689 manager.upsert_classifier(classifier.clone());
10690 let hash_after_upsert = feagi_state_manager::StateManager::instance()
10691 .read()
10692 .get_classifiers_hash();
10693 assert_ne!(hash_after_upsert, 0, "upsert must publish classifiers_hash");
10694 let ignored = manager.apply_classifier_mapping_change(
10695 "cfield",
10696 "mkmem1",
10697 feagi_structures::genomic::classifiers::CLASSIFIER_SCAN_MORPHOLOGY,
10698 false,
10699 );
10700 assert_eq!(ignored, 0);
10701 assert!(manager.get_classifier("clf-1").unwrap().fields.is_empty());
10702 let updated = manager.apply_classifier_mapping_change(
10703 "ckern1",
10704 "mkmem1",
10705 feagi_structures::genomic::classifiers::CLASSIFIER_KERNEL_MORPHOLOGY,
10706 false,
10707 );
10708 assert_eq!(updated, 1);
10709 classifier = manager.get_classifier("clf-1").cloned().unwrap();
10710 assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
10711 let hash_after_kernel_bind = feagi_state_manager::StateManager::instance()
10712 .read()
10713 .get_classifiers_hash();
10714 assert_ne!(
10715 hash_after_kernel_bind, hash_after_upsert,
10716 "binding a classifier input must change classifiers_hash"
10717 );
10718 }
10719
10720 #[test]
10721 fn deleting_referenced_input_clears_classifier_slot() {
10722 let mut manager = ConnectomeManager::new_for_testing();
10723 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
10724 classifier_id: "clf-1".to_string(),
10725 name: "demo".to_string(),
10726 parent_region_id: "root".to_string(),
10727 coordinates_3d: [0, 0, 0],
10728 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
10729 kernel_area_id: Some("ckern1".to_string()),
10730 class_area_id: Some("ccls01".to_string()),
10731 mask_area_id: None,
10732 kernel_size: None,
10733 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
10734 field_area_id: "cfield".to_string(),
10735 scan_twin_id: "cscan1".to_string(),
10736 }],
10737 kernel_memory_id: "mkmem1".to_string(),
10738 class_memory_id: "mcmem1".to_string(),
10739 properties: HashMap::new(),
10740 });
10741 assert_eq!(manager.clear_classifier_inputs_for_area("cfield"), 1);
10742 let classifier = manager.get_classifier("clf-1").unwrap();
10743 assert!(classifier.binding_for_field("cfield").is_none());
10744 assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
10745 assert!(manager.classifiers_owning_area("cfield").is_empty());
10746 assert_eq!(manager.classifiers_owning_area("mkmem1").len(), 1);
10747 let hash_after_clear = feagi_state_manager::StateManager::instance()
10748 .read()
10749 .get_classifiers_hash();
10750 manager.remove_classifier("clf-1");
10751 let hash_after_remove = feagi_state_manager::StateManager::instance()
10752 .read()
10753 .get_classifiers_hash();
10754 assert_ne!(
10755 hash_after_remove, hash_after_clear,
10756 "removing a classifier must change classifiers_hash"
10757 );
10758 }
10759
10760 #[test]
10761 fn classifier_assembly_mappings_do_not_become_region_io() {
10762 use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
10763 use feagi_structures::genomic::cortical_area::{
10764 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10765 MemoryCorticalType,
10766 };
10767
10768 let mut manager = ConnectomeManager::new_for_testing();
10769 let root_id = RegionID::new();
10770 let child_id = RegionID::new();
10771 let root_key = root_id.to_string();
10772 let child_key = child_id.to_string();
10773 manager
10774 .add_brain_region(
10775 BrainRegion::new(root_id, "Root".to_string(), RegionType::Undefined).unwrap(),
10776 None,
10777 )
10778 .unwrap();
10779 manager
10780 .add_brain_region(
10781 BrainRegion::new(child_id, "Child".to_string(), RegionType::Undefined).unwrap(),
10782 Some(root_key.clone()),
10783 )
10784 .unwrap();
10785
10786 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
10787 let mem_id = CorticalID::try_from_bytes(b"mclfmem1").unwrap();
10788 let regular_field_id = CorticalID::try_from_bytes(b"cregfld1").unwrap();
10789 let regular_mem_id = CorticalID::try_from_bytes(b"mregmem1").unwrap();
10790
10791 let mut field = CorticalArea::new(
10792 field_id,
10793 0,
10794 "field".to_string(),
10795 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10796 (0, 0, 0).into(),
10797 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10798 )
10799 .unwrap();
10800 field.properties.insert(
10801 "parent_region_id".to_string(),
10802 serde_json::json!(child_key.clone()),
10803 );
10804 field.properties.insert(
10805 "cortical_mapping_dst".to_string(),
10806 serde_json::json!({
10807 mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
10808 }),
10809 );
10810
10811 let mut classifier_mem = CorticalArea::new(
10812 mem_id,
10813 0,
10814 "clf_kernel_mem".to_string(),
10815 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10816 (10, 0, 0).into(),
10817 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10818 )
10819 .unwrap();
10820 classifier_mem.properties.insert(
10821 "parent_region_id".to_string(),
10822 serde_json::json!(root_key.clone()),
10823 );
10824 classifier_mem
10825 .properties
10826 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10827 classifier_mem.properties.insert(
10828 "classifier_role".to_string(),
10829 serde_json::json!("kernel_memory"),
10830 );
10831
10832 let mut regular_field = CorticalArea::new(
10833 regular_field_id,
10834 0,
10835 "regular_field".to_string(),
10836 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10837 (20, 0, 0).into(),
10838 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10839 )
10840 .unwrap();
10841 regular_field.properties.insert(
10842 "parent_region_id".to_string(),
10843 serde_json::json!(child_key.clone()),
10844 );
10845 regular_field.properties.insert(
10846 "cortical_mapping_dst".to_string(),
10847 serde_json::json!({
10848 regular_mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
10849 }),
10850 );
10851
10852 let mut regular_mem = CorticalArea::new(
10853 regular_mem_id,
10854 0,
10855 "regular_mem".to_string(),
10856 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10857 (30, 0, 0).into(),
10858 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10859 )
10860 .unwrap();
10861 regular_mem.properties.insert(
10862 "parent_region_id".to_string(),
10863 serde_json::json!(root_key.clone()),
10864 );
10865
10866 manager.add_cortical_area(field).unwrap();
10867 manager.add_cortical_area(classifier_mem).unwrap();
10868 manager.add_cortical_area(regular_field).unwrap();
10869 manager.add_cortical_area(regular_mem).unwrap();
10870
10871 let io = manager.recompute_brain_region_io_registry().unwrap();
10872 let child_outputs = &io.get(&child_key).expect("child region io").1;
10873 assert!(
10874 !child_outputs.contains(&field_id.as_base_64()),
10875 "classifier input must stay inside the circuit; got outputs {child_outputs:?}"
10876 );
10877 assert!(
10878 child_outputs.contains(®ular_field_id.as_base_64()),
10879 "non-classifier cross-region mappings must still become region outputs; got {child_outputs:?}"
10880 );
10881 }
10882
10883 #[test]
10884 fn classifier_assembly_area_is_detected_from_role_or_flag() {
10885 use feagi_structures::genomic::cortical_area::{
10886 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10887 };
10888
10889 let mut flagged = CorticalArea::new(
10890 CorticalID::try_from_bytes(b"mflagged").unwrap(),
10891 0,
10892 "flagged".to_string(),
10893 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10894 (0, 0, 0).into(),
10895 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10896 )
10897 .unwrap();
10898 flagged
10899 .properties
10900 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10901 assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
10902 &flagged
10903 ));
10904
10905 let mut role = CorticalArea::new(
10906 CorticalID::try_from_bytes(b"mrole001").unwrap(),
10907 0,
10908 "role".to_string(),
10909 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10910 (0, 0, 0).into(),
10911 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10912 )
10913 .unwrap();
10914 role.properties.insert(
10915 "classifier_role".to_string(),
10916 serde_json::json!("scan_twin"),
10917 );
10918 assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
10919 &role
10920 ));
10921
10922 let plain = CorticalArea::new(
10923 CorticalID::try_from_bytes(b"mplain01").unwrap(),
10924 0,
10925 "plain".to_string(),
10926 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10927 (0, 0, 0).into(),
10928 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10929 )
10930 .unwrap();
10931 assert!(!ConnectomeManager::area_belongs_to_classifier_assembly(
10932 &plain
10933 ));
10934 }
10935
10936 #[test]
10937 fn rekey_memory_twin_source_retargets_classifier_stamp() {
10938 use feagi_structures::genomic::cortical_area::{
10939 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10940 MemoryCorticalType,
10941 };
10942
10943 let mut manager = ConnectomeManager::new_for_testing();
10944 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
10945 let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
10946 let mut mem_area = CorticalArea::new(
10947 mem_id,
10948 0,
10949 "kernel_mem".to_string(),
10950 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10951 (0, 0, 0).into(),
10952 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10953 )
10954 .unwrap();
10955 mem_area.properties.insert(
10956 "classifier_role".to_string(),
10957 serde_json::json!("kernel_memory"),
10958 );
10959 mem_area.properties.insert(
10960 "memory_twin_areas".to_string(),
10961 serde_json::json!({ "cfield01": stamp_id.as_base_64() }),
10962 );
10963 let mut stamp = CorticalArea::new(
10964 stamp_id,
10965 0,
10966 "stamp".to_string(),
10967 CorticalAreaDimensions::new(4, 4, 2).unwrap(),
10968 (0, 0, 0).into(),
10969 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10970 )
10971 .unwrap();
10972 stamp.properties.insert(
10973 "classifier_role".to_string(),
10974 serde_json::json!("scan_twin"),
10975 );
10976 stamp
10977 .properties
10978 .insert("memory_twin_of".to_string(), serde_json::json!("cfield01"));
10979 manager.add_cortical_area(mem_area).unwrap();
10980 manager.add_cortical_area(stamp).unwrap();
10981 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
10982 classifier_id: "clf-1".to_string(),
10983 name: "demo".to_string(),
10984 parent_region_id: "root".to_string(),
10985 coordinates_3d: [0, 0, 0],
10986 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
10987 kernel_area_id: Some("ckern001".to_string()),
10988 class_area_id: Some("ccls0001".to_string()),
10989 mask_area_id: None,
10990 kernel_size: None,
10991 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
10992 field_area_id: "cfield01".to_string(),
10993 scan_twin_id: stamp_id.as_base_64(),
10994 }],
10995 kernel_memory_id: mem_id.as_base_64(),
10996 class_memory_id: "mcmem001".to_string(),
10997 properties: HashMap::new(),
10998 });
10999
11000 manager
11001 .rekey_memory_twin_source(&mem_id.as_base_64(), "cfield01", "cfield02")
11002 .unwrap();
11003
11004 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
11005 let twins = memory_area
11006 .properties
11007 .get("memory_twin_areas")
11008 .and_then(|value| value.as_object())
11009 .expect("memory_twin_areas");
11010 assert_eq!(
11011 twins.get("cfield02").and_then(|value| value.as_str()),
11012 Some(stamp_id.as_base_64().as_str())
11013 );
11014 assert!(!twins.contains_key("cfield01"));
11015 let stamp = manager.get_cortical_area(&stamp_id).unwrap();
11016 assert_eq!(
11017 stamp
11018 .properties
11019 .get("memory_twin_of")
11020 .and_then(|value| value.as_str()),
11021 Some("cfield02")
11022 );
11023 }
11024
11025 #[test]
11026 fn classifier_stamp_survives_field_mapping_delete() {
11027 use feagi_npu_burst_engine::backend::CPUBackend;
11028 use feagi_npu_burst_engine::TracingMutex;
11029 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
11030 use feagi_npu_runtime::StdRuntime;
11031 use feagi_structures::genomic::cortical_area::{
11032 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11033 IOCorticalAreaConfigurationFlag, MemoryCorticalType,
11034 };
11035 use std::sync::Arc;
11036
11037 let runtime = StdRuntime;
11038 let backend = CPUBackend::new();
11039 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
11040 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
11041 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
11042 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
11043
11044 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
11045 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11046 let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
11047 let field_area = CorticalArea::new(
11048 field_id,
11049 0,
11050 "Field".to_string(),
11051 CorticalAreaDimensions::new(4, 3, 1).unwrap(),
11052 (0, 0, 0).into(),
11053 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
11054 )
11055 .unwrap();
11056 let mut mem_area = CorticalArea::new(
11057 mem_id,
11058 0,
11059 "kernel_mem".to_string(),
11060 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11061 (10, 0, 0).into(),
11062 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11063 )
11064 .unwrap();
11065 mem_area.properties.insert(
11066 "classifier_role".to_string(),
11067 serde_json::json!("kernel_memory"),
11068 );
11069 mem_area
11070 .properties
11071 .insert("classifier_assembly".to_string(), serde_json::json!(true));
11072 mem_area.properties.insert(
11073 "memory_twin_areas".to_string(),
11074 serde_json::json!({ field_id.as_base_64(): stamp_id.as_base_64() }),
11075 );
11076 let mut stamp = CorticalArea::new(
11077 stamp_id,
11078 0,
11079 "stamp".to_string(),
11080 CorticalAreaDimensions::new(4, 3, 2).unwrap(),
11081 (20, 0, 0).into(),
11082 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11083 )
11084 .unwrap();
11085 stamp.properties.insert(
11086 "classifier_role".to_string(),
11087 serde_json::json!("scan_twin"),
11088 );
11089 stamp
11090 .properties
11091 .insert("classifier_assembly".to_string(), serde_json::json!(true));
11092 stamp.properties.insert(
11093 "memory_twin_of".to_string(),
11094 serde_json::json!(field_id.as_base_64()),
11095 );
11096 manager.add_cortical_area(field_area).unwrap();
11097 manager.add_cortical_area(mem_area).unwrap();
11098 manager.add_cortical_area(stamp).unwrap();
11099 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
11100 classifier_id: "clf-1".to_string(),
11101 name: "demo".to_string(),
11102 parent_region_id: "root".to_string(),
11103 coordinates_3d: [0, 0, 0],
11104 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
11105 kernel_area_id: Some("ckern001".to_string()),
11106 class_area_id: Some("ccls0001".to_string()),
11107 mask_area_id: None,
11108 kernel_size: None,
11109 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
11110 field_area_id: field_id.as_base_64(),
11111 scan_twin_id: stamp_id.as_base_64(),
11112 }],
11113 kernel_memory_id: mem_id.as_base_64(),
11114 class_memory_id: "mcmem001".to_string(),
11115 properties: HashMap::new(),
11116 });
11117
11118 let mapping_data = vec![serde_json::json!({
11119 "morphology_id": "episodic_scan",
11120 "morphology_scalar": [1, 1, 1],
11121 "postSynapticCurrent_multiplier": 1,
11122 })];
11123 manager
11124 .update_cortical_mapping(&field_id, &mem_id, mapping_data)
11125 .unwrap();
11126 manager
11127 .update_cortical_mapping(&field_id, &mem_id, Vec::new())
11128 .unwrap();
11129
11130 assert!(
11131 manager.get_cortical_area(&stamp_id).is_some(),
11132 "classifier stamp must survive field mapping delete"
11133 );
11134 assert_eq!(
11135 manager
11136 .get_classifier("clf-1")
11137 .and_then(|classifier| {
11138 classifier
11139 .binding_for_field(&field_id.as_base_64())
11140 .map(|field| field.field_area_id.clone())
11141 }),
11142 Some(field_id.as_base_64()),
11143 "empty mapping delete must not clear the classifier field slot when morphology is omitted"
11144 );
11145 }
11146
11147 #[cfg(feature = "plasticity")]
11150 #[test]
11151 fn classifier_scan_config_targets_each_field_twin() {
11152 use feagi_structures::genomic::cortical_area::{
11153 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11154 MemoryCorticalType,
11155 };
11156
11157 let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
11158 let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
11159 let field_a = CorticalID::try_from_bytes(b"cfield01").unwrap();
11160 let field_b = CorticalID::try_from_bytes(b"cfield02").unwrap();
11161 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11162 let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
11163 let twin_a = CorticalID::try_from_bytes(b"ctwin001").unwrap();
11164 let twin_b = CorticalID::try_from_bytes(b"ctwin002").unwrap();
11165
11166 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11167 CorticalArea::new(
11168 id,
11169 0,
11170 name.to_string(),
11171 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11172 (0, 0, 0).into(),
11173 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11174 )
11175 .unwrap()
11176 };
11177 let memory = |id: CorticalID, name: &str| {
11178 let mut area = CorticalArea::new(
11179 id,
11180 0,
11181 name.to_string(),
11182 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11183 (0, 0, 0).into(),
11184 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11185 )
11186 .unwrap();
11187 area.properties
11188 .insert("is_mem_type".to_string(), serde_json::json!(true));
11189 area
11190 };
11191
11192 let mut manager = ConnectomeManager::new_for_testing();
11193 let kernel = custom(kernel_id, "kernel", (2, 2, 1));
11194 let class_area = custom(class_id, "class", (1, 1, 10));
11195 let mut field_a_area = custom(field_a, "field_a", (4, 3, 1));
11196 let mut field_b_area = custom(field_b, "field_b", (2, 2, 1));
11197 field_a_area.properties.insert(
11198 "cortical_mapping_dst".to_string(),
11199 serde_json::json!({
11200 mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
11201 }),
11202 );
11203 field_b_area.properties.insert(
11204 "cortical_mapping_dst".to_string(),
11205 serde_json::json!({
11206 mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
11207 }),
11208 );
11209 let mut kernel_mem = memory(mem_id, "kernel_mem");
11210 kernel_mem.properties.insert(
11211 "classifier_kernel_area_id".to_string(),
11212 serde_json::json!(kernel_id.as_base_64()),
11213 );
11214 kernel_mem.properties.insert(
11215 "classifier_class_area_id".to_string(),
11216 serde_json::json!(class_id.as_base_64()),
11217 );
11218 kernel_mem.properties.insert(
11219 "classifier_class_memory_id".to_string(),
11220 serde_json::json!(class_mem_id.as_base_64()),
11221 );
11222 kernel_mem.properties.insert(
11223 "cortical_mapping_dst".to_string(),
11224 serde_json::json!({
11225 class_mem_id.as_base_64(): [{ "morphology_id": "associative_memory" }]
11226 }),
11227 );
11228 kernel_mem.properties.insert(
11229 "memory_twin_areas".to_string(),
11230 serde_json::json!({
11231 field_a.as_base_64(): twin_a.as_base_64(),
11232 field_b.as_base_64(): twin_b.as_base_64(),
11233 }),
11234 );
11235 let class_mem = memory(class_mem_id, "class_mem");
11236 let twin_a_area = custom(twin_a, "twin_a", (4, 3, 10));
11237 let twin_b_area = custom(twin_b, "twin_b", (2, 2, 10));
11238
11239 manager.add_cortical_area(kernel).unwrap();
11240 manager.add_cortical_area(class_area).unwrap();
11241 manager.add_cortical_area(field_a_area).unwrap();
11242 manager.add_cortical_area(field_b_area).unwrap();
11243 manager.add_cortical_area(kernel_mem).unwrap();
11244 manager.add_cortical_area(class_mem).unwrap();
11245 manager.add_cortical_area(twin_a_area).unwrap();
11246 manager.add_cortical_area(twin_b_area).unwrap();
11247
11248 let scan = manager
11249 .build_memory_scan_config(&mem_id)
11250 .expect("a mapped field twin must produce a scan config");
11251 assert_eq!(scan.sources.len(), 2);
11252 assert_eq!(scan.class_channel_count, 10);
11253 assert_eq!(scan.kernel.width, 2);
11254 let source_a = scan
11255 .sources
11256 .iter()
11257 .find(|source| source.field_area_idx == manager.get_cortical_idx(&field_a).unwrap())
11258 .expect("field A scan source");
11259 assert_eq!(
11260 source_a.twin_area_idx,
11261 manager.get_cortical_idx(&twin_a).unwrap()
11262 );
11263 assert_eq!(source_a.field_width, 4);
11264 assert_eq!(source_a.field_height, 3);
11265 let source_b = scan
11266 .sources
11267 .iter()
11268 .find(|source| source.field_area_idx == manager.get_cortical_idx(&field_b).unwrap())
11269 .expect("field B scan source");
11270 assert_eq!(
11271 source_b.twin_area_idx,
11272 manager.get_cortical_idx(&twin_b).unwrap()
11273 );
11274
11275 manager
11276 .get_cortical_area_mut(&mem_id)
11277 .unwrap()
11278 .properties
11279 .insert("memory_twin_areas".to_string(), serde_json::json!({}));
11280 assert!(
11281 manager.build_memory_scan_config(&mem_id).is_none(),
11282 "an empty twin map must not inject, which is why a dark twin stays dark"
11283 );
11284 }
11285
11286 #[cfg(feature = "plasticity")]
11289 #[test]
11290 fn classifier_genome_round_trip_restores_scan_target() {
11291 use feagi_evolutionary::{
11292 convert_hierarchical_to_flat, load_genome_from_json, GenomeMetadata, GenomeSignatures,
11293 GenomeStats, PhysiologyConfig, RuntimeGenome,
11294 };
11295 use feagi_structures::genomic::classifiers::{Classifier, ClassifierField};
11296 use feagi_structures::genomic::cortical_area::{
11297 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11298 MemoryCorticalType,
11299 };
11300
11301 let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
11302 let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
11303 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
11304 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11305 let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
11306 let twin_id = CorticalID::try_from_bytes(b"ctwin001").unwrap();
11307
11308 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11309 CorticalArea::new(
11310 id,
11311 0,
11312 name.to_string(),
11313 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11314 (0, 0, 0).into(),
11315 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11316 )
11317 .unwrap()
11318 };
11319 let memory = |id: CorticalID, name: &str| {
11320 let mut area = CorticalArea::new(
11321 id,
11322 0,
11323 name.to_string(),
11324 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11325 (0, 0, 0).into(),
11326 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11327 )
11328 .unwrap();
11329 area.properties
11330 .insert("is_mem_type".to_string(), serde_json::json!(true));
11331 area
11332 };
11333
11334 let mut kernel = custom(kernel_id, "kernel", (2, 2, 1));
11335 kernel.properties.insert(
11336 "cortical_mapping_dst".to_string(),
11337 serde_json::json!({
11338 mem_id.as_base_64(): [{
11339 "morphology_id": "episodic_memory",
11340 "postSynapticCurrent_multiplier": 1.0,
11341 "plasticity_flag": false
11342 }]
11343 }),
11344 );
11345 let class_area = custom(class_id, "class", (1, 1, 4));
11346 let mut field = custom(field_id, "field", (3, 2, 1));
11347 field
11348 .properties
11349 .insert("burst_engine_active".to_string(), serde_json::json!(true));
11350 field.properties.insert(
11351 "cortical_mapping_dst".to_string(),
11352 serde_json::json!({
11353 mem_id.as_base_64(): [{
11354 "morphology_id": "episodic_scan",
11355 "postSynapticCurrent_multiplier": 1.0,
11356 "plasticity_flag": false
11357 }]
11358 }),
11359 );
11360 let mut kernel_mem = memory(mem_id, "kernel_mem");
11361 kernel_mem.properties.insert(
11362 "cortical_mapping_dst".to_string(),
11363 serde_json::json!({
11364 class_mem_id.as_base_64(): [{
11365 "morphology_id": "associative_memory",
11366 "postSynapticCurrent_multiplier": 1.0,
11367 "plasticity_flag": false
11368 }]
11369 }),
11370 );
11371 let class_mem = memory(class_mem_id, "class_mem");
11372 let mut twin = custom(twin_id, "twin", (3, 2, 4));
11373 twin.properties
11374 .insert("burst_engine_active".to_string(), serde_json::json!(true));
11375 twin.properties.insert(
11376 "memory_twin_of".to_string(),
11377 serde_json::json!(field_id.as_base_64()),
11378 );
11379
11380 let mut genome = RuntimeGenome {
11381 metadata: GenomeMetadata {
11382 genome_id: "clf-round-trip".to_string(),
11383 genome_title: "clf".to_string(),
11384 genome_description: "".to_string(),
11385 version: "3.0".to_string(),
11386 timestamp: 0.0,
11387 brain_regions_root: None,
11388 },
11389 cortical_areas: HashMap::new(),
11390 brain_regions: HashMap::new(),
11391 classifiers: HashMap::new(),
11392 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
11393 physiology: PhysiologyConfig::default(),
11394 signatures: GenomeSignatures {
11395 genome: "0".to_string(),
11396 blueprint: "0".to_string(),
11397 physiology: "0".to_string(),
11398 morphologies: None,
11399 },
11400 stats: GenomeStats::default(),
11401 };
11402 for area in [kernel, class_area, field, kernel_mem, class_mem, twin] {
11403 genome.cortical_areas.insert(area.cortical_id, area);
11404 }
11405 genome.classifiers.insert(
11406 "clf-1".to_string(),
11407 Classifier {
11408 classifier_id: "clf-1".to_string(),
11409 name: "asdf".to_string(),
11410 parent_region_id: "root".to_string(),
11411 coordinates_3d: [0, 0, 0],
11412 training_mode:
11413 feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
11414 kernel_area_id: Some(kernel_id.as_base_64()),
11415 class_area_id: Some(class_id.as_base_64()),
11416 mask_area_id: None,
11417 kernel_size: None,
11418 fields: vec![ClassifierField {
11419 field_area_id: field_id.as_base_64(),
11420 scan_twin_id: twin_id.as_base_64(),
11421 }],
11422 kernel_memory_id: mem_id.as_base_64(),
11423 class_memory_id: class_mem_id.as_base_64(),
11424 properties: HashMap::new(),
11425 },
11426 );
11427
11428 let flat = convert_hierarchical_to_flat(&genome).unwrap();
11429 let loaded = load_genome_from_json(&flat.to_string()).unwrap();
11430 let loaded_classifier = loaded.classifiers.get("clf-1").unwrap();
11431 assert_eq!(
11432 loaded_classifier.fields[0].scan_twin_id,
11433 twin_id.as_base_64()
11434 );
11435 assert_eq!(
11436 loaded
11437 .cortical_areas
11438 .get(&twin_id)
11439 .unwrap()
11440 .properties
11441 .get("burst_engine_active")
11442 .and_then(|value| value.as_bool()),
11443 Some(true),
11444 "twin burst must survive save and load"
11445 );
11446 assert!(
11447 !loaded.cortical_areas[&mem_id]
11448 .properties
11449 .contains_key("classifier_kernel_area_id"),
11450 "the blueprint does not store classifier area ids; load must restore them"
11451 );
11452
11453 let mut manager = ConnectomeManager::new_for_testing();
11454 for area in loaded.cortical_areas.values() {
11455 manager.add_cortical_area(area.clone()).unwrap();
11456 }
11457 manager.replace_classifiers(loaded.classifiers);
11458 manager.apply_loaded_classifier_assemblies();
11459
11460 let kernel_mem = manager.get_cortical_area(&mem_id).unwrap();
11461 assert_eq!(
11462 kernel_mem
11463 .properties
11464 .get("classifier_kernel_area_id")
11465 .and_then(|value| value.as_str()),
11466 Some(kernel_id.as_base_64().as_str())
11467 );
11468 assert_eq!(
11469 kernel_mem
11470 .properties
11471 .get("classifier_class_area_id")
11472 .and_then(|value| value.as_str()),
11473 Some(class_id.as_base_64().as_str())
11474 );
11475 assert_eq!(
11476 kernel_mem
11477 .properties
11478 .get("memory_twin_areas")
11479 .and_then(|value| value.as_object())
11480 .and_then(|map| map.get(&field_id.as_base_64()))
11481 .and_then(|value| value.as_str()),
11482 Some(twin_id.as_base_64().as_str())
11483 );
11484 assert_eq!(
11485 kernel_mem
11486 .properties
11487 .get("burst_engine_active")
11488 .and_then(|value| value.as_bool()),
11489 Some(true)
11490 );
11491 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
11492 assert_eq!(
11493 twin_area
11494 .properties
11495 .get("memory_twin_for")
11496 .and_then(|value| value.as_str()),
11497 Some(mem_id.as_base_64().as_str())
11498 );
11499 assert_eq!(
11500 twin_area
11501 .properties
11502 .get("scan_twin")
11503 .and_then(|value| value.as_bool()),
11504 Some(true)
11505 );
11506
11507 assert!(
11508 manager.mapping_from_src_to_dst_has_associative(&mem_id, &class_mem_id),
11509 "kernel memory to class memory associative mapping must survive save and load, got {:?}",
11510 manager
11511 .get_cortical_area(&mem_id)
11512 .unwrap()
11513 .properties
11514 .get("cortical_mapping_dst")
11515 );
11516 assert!(
11517 !manager
11518 .get_episodic_scan_upstream_cortical_areas(&mem_id)
11519 .is_empty(),
11520 "field episodic_scan must survive save and load, got {:?}",
11521 manager
11522 .get_cortical_area(&field_id)
11523 .unwrap()
11524 .properties
11525 .get("cortical_mapping_dst")
11526 );
11527 let scan = manager
11528 .build_memory_scan_config(&mem_id)
11529 .expect("a reloaded classifier must scan into its twin");
11530 assert_eq!(scan.sources.len(), 1);
11531 assert_eq!(
11532 scan.sources[0].twin_area_idx,
11533 manager.get_cortical_idx(&twin_id).unwrap()
11534 );
11535 assert_eq!(
11536 scan.sources[0].field_area_idx,
11537 manager.get_cortical_idx(&field_id).unwrap()
11538 );
11539 }
11540
11541 #[cfg(feature = "plasticity")]
11544 #[test]
11545 fn scanner_genome_round_trip_restores_mask_kernel_and_scan() {
11546 use feagi_evolutionary::{
11547 convert_hierarchical_to_flat, load_genome_from_json, GenomeMetadata, GenomeSignatures,
11548 GenomeStats, PhysiologyConfig, RuntimeGenome,
11549 };
11550 use feagi_structures::genomic::classifiers::{
11551 Classifier, ClassifierField, ClassifierTrainingMode,
11552 };
11553 use feagi_structures::genomic::cortical_area::{
11554 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11555 MemoryCorticalType,
11556 };
11557
11558 let field_id = CorticalID::try_from_bytes(b"cfieldsc").unwrap();
11559 let mask_id = CorticalID::try_from_bytes(b"cmaskscn").unwrap();
11560 let mem_id = CorticalID::try_from_bytes(b"mkmemscn").unwrap();
11561 let class_mem_id = CorticalID::try_from_bytes(b"mcmemscn").unwrap();
11562 let twin_id = CorticalID::try_from_bytes(b"ctwinscn").unwrap();
11563
11564 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11565 CorticalArea::new(
11566 id,
11567 0,
11568 name.to_string(),
11569 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11570 (0, 0, 0).into(),
11571 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11572 )
11573 .unwrap()
11574 };
11575 let memory = |id: CorticalID, name: &str| {
11576 let mut area = CorticalArea::new(
11577 id,
11578 0,
11579 name.to_string(),
11580 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11581 (0, 0, 0).into(),
11582 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11583 )
11584 .unwrap();
11585 area.properties
11586 .insert("is_mem_type".to_string(), serde_json::json!(true));
11587 area
11588 };
11589
11590 let mut field = custom(field_id, "field", (4, 3, 3));
11591 field.properties.insert(
11592 "cortical_mapping_dst".to_string(),
11593 serde_json::json!({
11594 mem_id.as_base_64(): [{
11595 "morphology_id": "episodic_scan",
11596 "postSynapticCurrent_multiplier": 1.0,
11597 "plasticity_flag": false
11598 }]
11599 }),
11600 );
11601 let mask = custom(mask_id, "mask", (4, 3, 10));
11602 let mut kernel_mem = memory(mem_id, "kernel_mem");
11603 kernel_mem.properties.insert(
11604 "cortical_mapping_dst".to_string(),
11605 serde_json::json!({
11606 class_mem_id.as_base_64(): [{
11607 "morphology_id": "associative_memory",
11608 "postSynapticCurrent_multiplier": 1.0,
11609 "plasticity_flag": false
11610 }]
11611 }),
11612 );
11613 let class_mem = memory(class_mem_id, "class_mem");
11614 let mut twin = custom(twin_id, "twin", (4, 3, 10));
11615 twin.properties.insert(
11616 "memory_twin_of".to_string(),
11617 serde_json::json!(field_id.as_base_64()),
11618 );
11619
11620 let mut genome = RuntimeGenome {
11621 metadata: GenomeMetadata {
11622 genome_id: "clf-scanner".to_string(),
11623 genome_title: "clf".to_string(),
11624 genome_description: "".to_string(),
11625 version: "3.0".to_string(),
11626 timestamp: 0.0,
11627 brain_regions_root: None,
11628 },
11629 cortical_areas: HashMap::new(),
11630 brain_regions: HashMap::new(),
11631 classifiers: HashMap::new(),
11632 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
11633 physiology: PhysiologyConfig::default(),
11634 signatures: GenomeSignatures {
11635 genome: "0".to_string(),
11636 blueprint: "0".to_string(),
11637 physiology: "0".to_string(),
11638 morphologies: None,
11639 },
11640 stats: GenomeStats::default(),
11641 };
11642 for area in [field, mask, kernel_mem, class_mem, twin] {
11643 genome.cortical_areas.insert(area.cortical_id, area);
11644 }
11645 genome.classifiers.insert(
11646 "clf-scan".to_string(),
11647 Classifier {
11648 classifier_id: "clf-scan".to_string(),
11649 name: "scanner".to_string(),
11650 parent_region_id: "root".to_string(),
11651 coordinates_3d: [1, 2, 3],
11652 training_mode: ClassifierTrainingMode::Scanner,
11653 kernel_area_id: None,
11654 class_area_id: None,
11655 mask_area_id: Some(mask_id.as_base_64()),
11656 kernel_size: Some([2, 2, 3]),
11657 fields: vec![ClassifierField {
11658 field_area_id: field_id.as_base_64(),
11659 scan_twin_id: twin_id.as_base_64(),
11660 }],
11661 kernel_memory_id: mem_id.as_base_64(),
11662 class_memory_id: class_mem_id.as_base_64(),
11663 properties: HashMap::new(),
11664 },
11665 );
11666
11667 let flat = convert_hierarchical_to_flat(&genome).unwrap();
11668 assert_eq!(flat["classifiers"]["clf-scan"]["training_mode"], "scanner");
11669 assert_eq!(
11670 flat["classifiers"]["clf-scan"]["kernel_size"],
11671 serde_json::json!([2, 2, 3])
11672 );
11673 assert!(flat["classifiers"]["clf-scan"]
11674 .get("kernel_area_id")
11675 .is_none());
11676 let loaded = load_genome_from_json(&flat.to_string()).unwrap();
11677 let loaded_classifier = loaded.classifiers.get("clf-scan").unwrap();
11678 assert_eq!(
11679 loaded_classifier.training_mode,
11680 ClassifierTrainingMode::Scanner
11681 );
11682 assert_eq!(
11683 loaded_classifier.mask_area_id.as_deref(),
11684 Some(mask_id.as_base_64().as_str())
11685 );
11686 assert_eq!(loaded_classifier.kernel_size, Some([2, 2, 3]));
11687 assert!(loaded_classifier.kernel_area_id.is_none());
11688 assert!(loaded_classifier.class_area_id.is_none());
11689
11690 let mut manager = ConnectomeManager::new_for_testing();
11691 for area in loaded.cortical_areas.values() {
11692 manager.add_cortical_area(area.clone()).unwrap();
11693 }
11694 manager.replace_classifiers(loaded.classifiers);
11695 manager.apply_loaded_classifier_assemblies();
11696
11697 let kernel_mem = manager.get_cortical_area(&mem_id).unwrap();
11698 assert_eq!(
11699 kernel_mem
11700 .properties
11701 .get("classifier_training_mode")
11702 .and_then(|value| value.as_str()),
11703 Some("scanner")
11704 );
11705 assert_eq!(
11706 kernel_mem
11707 .properties
11708 .get("classifier_mask_area_id")
11709 .and_then(|value| value.as_str()),
11710 Some(mask_id.as_base_64().as_str())
11711 );
11712 assert_eq!(
11713 kernel_mem
11714 .properties
11715 .get("classifier_kernel_size")
11716 .and_then(|value| value.as_array())
11717 .map(|values| values.len()),
11718 Some(3)
11719 );
11720 assert!(!kernel_mem
11721 .properties
11722 .contains_key("classifier_kernel_area_id"));
11723 assert!(!kernel_mem
11724 .properties
11725 .contains_key("classifier_class_area_id"));
11726 assert_eq!(
11727 manager
11728 .get_cortical_area(&mask_id)
11729 .unwrap()
11730 .properties
11731 .get("burst_engine_active")
11732 .and_then(|value| value.as_bool()),
11733 Some(true),
11734 "a reloaded scanner mask must stay a burst source"
11735 );
11736
11737 let scan = manager
11738 .build_memory_scan_config(&mem_id)
11739 .expect("a reloaded scanner classifier must scan from its kernel size");
11740 assert_eq!(scan.kernel.width, 2);
11741 assert_eq!(scan.kernel.height, 2);
11742 assert_eq!(scan.kernel.depth, 3);
11743 assert_eq!(scan.class_channel_count, 10);
11744 let mask_source = scan.scanner_mask.expect("scanner mask source");
11745 assert_eq!(
11746 mask_source.mask_area_idx,
11747 manager.get_cortical_idx(&mask_id).unwrap()
11748 );
11749 assert_eq!(mask_source.mask_depth, 10);
11750 assert_eq!(scan.sources.len(), 1);
11751 assert_eq!(
11752 scan.sources[0].field_area_idx,
11753 manager.get_cortical_idx(&field_id).unwrap()
11754 );
11755 }
11756}