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 match &classifier.kernel_area_id {
660 Some(area_id) => Self::hash_str(&mut hasher, area_id),
661 None => Self::hash_str(&mut hasher, "null"),
662 }
663 match &classifier.class_area_id {
664 Some(area_id) => Self::hash_str(&mut hasher, area_id),
665 None => Self::hash_str(&mut hasher, "null"),
666 }
667 for field in &classifier.fields {
668 Self::hash_str(&mut hasher, &field.field_area_id);
669 Self::hash_str(&mut hasher, &field.scan_twin_id);
670 }
671 Self::hash_str(&mut hasher, &classifier.kernel_memory_id);
672 Self::hash_str(&mut hasher, &classifier.class_memory_id);
673 Self::hash_properties_filtered(&mut hasher, &classifier.properties, &[]);
674 }
675 hasher.finish() & HASH_SAFE_MASK
676 }
677
678 fn hash_str(hasher: &mut Xxh64, value: &str) {
680 hasher.write(value.as_bytes());
681 hasher.write_u8(0);
682 }
683
684 fn hash_i32(hasher: &mut Xxh64, value: i32) {
686 hasher.write(&value.to_le_bytes());
687 }
688
689 fn hash_u32(hasher: &mut Xxh64, value: u32) {
691 hasher.write(&value.to_le_bytes());
692 }
693
694 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
696 match value {
697 serde_json::Value::Null => {
698 hasher.write_u8(0);
699 }
700 serde_json::Value::Bool(val) => {
701 hasher.write_u8(1);
702 hasher.write_u8(*val as u8);
703 }
704 serde_json::Value::Number(num) => {
705 hasher.write_u8(2);
706 Self::hash_str(hasher, &num.to_string());
707 }
708 serde_json::Value::String(val) => {
709 hasher.write_u8(3);
710 Self::hash_str(hasher, val);
711 }
712 serde_json::Value::Array(items) => {
713 hasher.write_u8(4);
714 for item in items {
715 Self::hash_json_value(hasher, item);
716 }
717 }
718 serde_json::Value::Object(map) => {
719 hasher.write_u8(5);
720 let mut keys: Vec<&String> = map.keys().collect();
721 keys.sort();
722 for key in keys {
723 Self::hash_str(hasher, key);
724 if let Some(val) = map.get(key) {
725 Self::hash_json_value(hasher, val);
726 }
727 }
728 }
729 }
730 }
731
732 fn hash_properties_filtered(
734 hasher: &mut Xxh64,
735 properties: &HashMap<String, serde_json::Value>,
736 excluded_keys: &[&str],
737 ) {
738 let mut keys: Vec<&String> = properties.keys().collect();
739 keys.sort();
740 for key in keys {
741 if excluded_keys.contains(&key.as_str()) {
742 continue;
743 }
744 Self::hash_str(hasher, key);
745 if let Some(value) = properties.get(key) {
746 Self::hash_json_value(hasher, value);
747 }
748 }
749 }
750
751 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
772 if self.cortical_areas.contains_key(&area.cortical_id) {
774 return Err(BduError::InvalidArea(format!(
775 "Cortical area {} already exists",
776 area.cortical_id
777 )));
778 }
779
780 use feagi_structures::genomic::cortical_area::CoreCorticalType;
783
784 let death_id = CoreCorticalType::Death.to_cortical_id();
785 let power_id = CoreCorticalType::Power.to_cortical_id();
786 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
787 let pain_id = CoreCorticalType::Pain.to_cortical_id();
788 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
789 let fear_id = CoreCorticalType::Fear.to_cortical_id();
790 let hope_id = CoreCorticalType::Hope.to_cortical_id();
791
792 let is_death_area = area.cortical_id == death_id;
793 let is_power_area = area.cortical_id == power_id;
794 let is_fatigue_area = area.cortical_id == fatigue_id;
795 let is_pain_area = area.cortical_id == pain_id;
796 let is_pleasure_area = area.cortical_id == pleasure_id;
797 let is_fear_area = area.cortical_id == fear_id;
798 let is_hope_area = area.cortical_id == hope_id;
799
800 if is_death_area {
801 trace!(
802 target: "feagi-bdu",
803 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
804 area.cortical_id
805 );
806 area.cortical_idx = 0;
807 } else if is_power_area {
808 trace!(
809 target: "feagi-bdu",
810 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
811 area.cortical_id
812 );
813 area.cortical_idx = 1;
814 } else if is_fatigue_area {
815 trace!(
816 target: "feagi-bdu",
817 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
818 area.cortical_id
819 );
820 area.cortical_idx = 2;
821 } else if is_pain_area {
822 trace!(
823 target: "feagi-bdu",
824 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
825 area.cortical_id
826 );
827 area.cortical_idx = 3;
828 } else if is_pleasure_area {
829 trace!(
830 target: "feagi-bdu",
831 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
832 area.cortical_id
833 );
834 area.cortical_idx = 4;
835 } else if is_fear_area {
836 trace!(
837 target: "feagi-bdu",
838 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
839 area.cortical_id
840 );
841 area.cortical_idx = 5;
842 } else if is_hope_area {
843 trace!(
844 target: "feagi-bdu",
845 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
846 area.cortical_id
847 );
848 area.cortical_idx = 6;
849 } else {
850 if area.cortical_idx == 0 {
852 area.cortical_idx = self.next_cortical_idx;
853 self.next_cortical_idx += 1;
854 trace!(
855 target: "feagi-bdu",
856 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
857 area.cortical_idx,
858 area.cortical_id.as_base_64()
859 );
860 } else {
861 if area.cortical_idx <= 6 {
863 warn!(
864 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
865 area.cortical_id, area.cortical_idx);
866 area.cortical_idx = self.next_cortical_idx;
867 self.next_cortical_idx += 1;
868 info!(
869 " Reassigned '{}' to cortical_idx={}",
870 area.cortical_id, area.cortical_idx
871 );
872 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
873 return Err(BduError::InvalidArea(format!(
874 "Cortical index {} is already in use",
875 area.cortical_idx
876 )));
877 }
878
879 if area.cortical_idx >= self.next_cortical_idx {
881 self.next_cortical_idx = area.cortical_idx + 1;
882 }
883 }
884 }
885
886 let cortical_id = area.cortical_id;
887 let cortical_idx = area.cortical_idx;
888
889 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
891 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
892
893 area.properties
895 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
896
897 let parent_region_id = area
906 .properties
907 .get("parent_region_id")
908 .and_then(|v| v.as_str())
909 .map(|s| s.to_string());
910
911 self.cortical_areas.insert(cortical_id, area);
913
914 if let Some(region_id) = parent_region_id {
916 let region = self
917 .brain_regions
918 .get_region_mut(®ion_id)
919 .ok_or_else(|| {
920 BduError::InvalidArea(format!(
921 "Unknown parent_region_id '{}' for cortical area {}",
922 region_id,
923 cortical_id.as_base_64()
924 ))
925 })?;
926 region.add_area(cortical_id);
927 }
928
929 {
932 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
933 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
934 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
935 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
936 }
937 let state_manager = StateManager::instance();
939 let state_manager = state_manager.read();
940 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
941
942 if let Some(ref npu) = self.npu {
946 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
947 if let Ok(mut npu_lock) = npu.lock() {
948 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
949 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
950 trace!(
951 target: "feagi-bdu",
952 "Registered cortical area idx={} -> '{}' in NPU",
953 cortical_idx,
954 cortical_id.as_base_64()
955 );
956 }
957 }
958
959 self.sync_cortical_area_flags_to_npu()?;
961
962 self.initialized = true;
963
964 self.refresh_cortical_area_hashes(true, true);
965 self.refresh_brain_regions_hash();
966
967 Ok(cortical_idx)
968 }
969
970 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
985 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
986 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
987 })?;
988
989 #[cfg(feature = "plasticity")]
990 if let Some(executor) = self.plasticity_executor.as_ref() {
991 let exec = match executor.lock() {
992 Ok(exec) => exec,
993 Err(_) => {
994 self.cortical_areas.insert(*cortical_id, area);
995 return Err(BduError::Internal(format!(
996 "Failed to lock PlasticityExecutor while removing cortical area {}",
997 cortical_id
998 )));
999 }
1000 };
1001 exec.unregister_memory_area(area.cortical_idx);
1002 }
1003
1004 self.cortical_id_to_idx.remove(cortical_id);
1006 self.cortical_idx_to_id.remove(&area.cortical_idx);
1007
1008 self.refresh_cortical_area_hashes(true, true);
1009 Ok(())
1010 }
1011
1012 pub fn rename_cortical_area_id(
1016 &mut self,
1017 old_id: &CorticalID,
1018 new_id: CorticalID,
1019 new_cortical_type: CorticalAreaType,
1020 ) -> BduResult<()> {
1021 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
1022 }
1023
1024 pub fn rename_cortical_area_id_with_options(
1026 &mut self,
1027 old_id: &CorticalID,
1028 new_id: CorticalID,
1029 new_cortical_type: CorticalAreaType,
1030 update_npu_registry: bool,
1031 ) -> BduResult<()> {
1032 if !self.cortical_areas.contains_key(old_id) {
1033 return Err(BduError::InvalidArea(format!(
1034 "Cortical area {} does not exist",
1035 old_id
1036 )));
1037 }
1038 if self.cortical_areas.contains_key(&new_id) {
1039 return Err(BduError::InvalidArea(format!(
1040 "Cortical area {} already exists",
1041 new_id
1042 )));
1043 }
1044
1045 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
1046 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
1047 })?;
1048 let cortical_idx = area.cortical_idx;
1049 area.cortical_id = new_id;
1050 area.cortical_type = new_cortical_type;
1051
1052 self.cortical_areas.insert(new_id, area);
1053 self.cortical_id_to_idx.remove(old_id);
1054 self.cortical_id_to_idx.insert(new_id, cortical_idx);
1055 self.cortical_idx_to_id.insert(cortical_idx, new_id);
1056
1057 {
1059 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
1060 if let Some(value) = neuron_cache.remove(old_id) {
1061 neuron_cache.insert(new_id, value);
1062 }
1063 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
1064 if let Some(value) = synapse_cache.remove(old_id) {
1065 synapse_cache.insert(new_id, value);
1066 }
1067 }
1068
1069 self.brain_regions.rename_cortical_area_id(old_id, new_id);
1071
1072 let old_id_str = old_id.as_base_64();
1074 let new_id_str = new_id.as_base_64();
1075 for area in self.cortical_areas.values_mut() {
1076 if let Some(mapping) = area
1077 .properties
1078 .get_mut("cortical_mapping_dst")
1079 .and_then(|v| v.as_object_mut())
1080 {
1081 if let Some(value) = mapping.remove(&old_id_str) {
1082 mapping.insert(new_id_str.clone(), value);
1083 }
1084 }
1085 }
1086
1087 if update_npu_registry {
1089 if let Some(ref npu) = self.npu {
1090 if let Ok(mut npu_lock) = npu.lock() {
1091 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
1092 }
1093 }
1094 }
1095
1096 self.refresh_cortical_area_hashes(true, true);
1097 self.refresh_brain_regions_hash();
1098 self.refresh_cortical_mappings_hash();
1099
1100 Ok(())
1101 }
1102
1103 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1105 self.cortical_areas.get(cortical_id)
1106 }
1107
1108 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1110 self.cortical_areas.get_mut(cortical_id)
1111 }
1112
1113 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1115 self.cortical_id_to_idx.get(cortical_id).copied()
1116 }
1117
1118 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1130 self.brain_regions.find_region_containing_area(cortical_id)
1131 }
1132
1133 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1136 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1137 return false;
1138 };
1139 let Some(src_area) = self.cortical_areas.get(area) else {
1140 return false;
1141 };
1142 let Some(dst_obj) = src_area
1143 .properties
1144 .get("cortical_mapping_dst")
1145 .and_then(|v| v.as_object())
1146 else {
1147 return false;
1148 };
1149 for dst_key in dst_obj.keys() {
1150 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1151 continue;
1152 };
1153 match self.brain_regions.find_region_containing_area(&dst_id) {
1154 None => return true,
1155 Some(rid) if rid != my_region => return true,
1156 _ => {}
1157 }
1158 }
1159 false
1160 }
1161
1162 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1164 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1165 return false;
1166 };
1167 let my_b64 = area.as_base_64();
1168 for (src_id, src_area) in &self.cortical_areas {
1169 if src_id == area {
1170 continue;
1171 }
1172 let Some(dst_map) = src_area
1173 .properties
1174 .get("cortical_mapping_dst")
1175 .and_then(|v| v.as_object())
1176 else {
1177 continue;
1178 };
1179 if !dst_map.contains_key(&my_b64) {
1180 continue;
1181 }
1182 match self.brain_regions.find_region_containing_area(src_id) {
1183 None => return true,
1184 Some(rid) if rid != my_region => return true,
1185 _ => {}
1186 }
1187 }
1188 false
1189 }
1190
1191 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1202 use std::collections::HashSet;
1203
1204 let region_ids: Vec<String> = self
1205 .brain_regions
1206 .get_all_region_ids()
1207 .into_iter()
1208 .cloned()
1209 .collect();
1210
1211 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1212 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1213
1214 for rid in ®ion_ids {
1216 inputs_by_region.insert(rid.clone(), HashSet::new());
1217 outputs_by_region.insert(rid.clone(), HashSet::new());
1218 }
1219
1220 for (src_id, src_area) in &self.cortical_areas {
1221 let Some(dstmap) = src_area
1222 .properties
1223 .get("cortical_mapping_dst")
1224 .and_then(|v| v.as_object())
1225 else {
1226 continue;
1227 };
1228
1229 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1230 debug!(
1231 target: "feagi-bdu",
1232 "Skipping region IO for source area {} (not in any region)",
1233 src_id.as_base_64()
1234 );
1235 continue;
1236 };
1237
1238 for dst_id_str in dstmap.keys() {
1239 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1240 BduError::InvalidArea(format!(
1241 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1242 dst_id_str,
1243 src_id.as_base_64(),
1244 e
1245 ))
1246 })?;
1247
1248 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1249 else {
1250 warn!(
1251 target: "feagi-bdu",
1252 "Skipping region IO for destination area {} (not in any region)",
1253 dst_id.as_base_64()
1254 );
1255 continue;
1256 };
1257
1258 if src_region_id == dst_region_id {
1259 continue;
1260 }
1261 if self.mapping_is_classifier_assembly_edge(src_id, &dst_id) {
1262 continue;
1263 }
1264
1265 outputs_by_region
1266 .entry(src_region_id.clone())
1267 .or_default()
1268 .insert(src_id.as_base_64());
1269 inputs_by_region
1270 .entry(dst_region_id.clone())
1271 .or_default()
1272 .insert(dst_id.as_base_64());
1273 }
1274 }
1275
1276 for rid in ®ion_ids {
1278 let Some(region) = self.brain_regions.get_region(rid) else {
1279 continue;
1280 };
1281 let in_ids = crate::region_io_designation::parse_designated_id_list(
1282 region
1283 .properties
1284 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1285 )?;
1286 let out_ids = crate::region_io_designation::parse_designated_id_list(
1287 region
1288 .properties
1289 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1290 )?;
1291 for id in in_ids {
1292 inputs_by_region
1293 .entry(rid.clone())
1294 .or_default()
1295 .insert(id.as_base_64());
1296 }
1297 for id in out_ids {
1298 outputs_by_region
1299 .entry(rid.clone())
1300 .or_default()
1301 .insert(id.as_base_64());
1302 }
1303 }
1304
1305 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1306 for rid in region_ids {
1307 let mut inputs: Vec<String> = inputs_by_region
1308 .remove(&rid)
1309 .unwrap_or_default()
1310 .into_iter()
1311 .collect();
1312 let mut outputs: Vec<String> = outputs_by_region
1313 .remove(&rid)
1314 .unwrap_or_default()
1315 .into_iter()
1316 .collect();
1317
1318 inputs.sort();
1319 outputs.sort();
1320
1321 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1322 BduError::InvalidArea(format!(
1323 "Unable to recompute region IO: region '{}' not found in hierarchy",
1324 rid
1325 ))
1326 })?;
1327
1328 if inputs.is_empty() {
1329 region.properties.remove("inputs");
1330 } else {
1331 region
1332 .properties
1333 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1334 }
1335
1336 if outputs.is_empty() {
1337 region.properties.remove("outputs");
1338 } else {
1339 region
1340 .properties
1341 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1342 }
1343
1344 computed.insert(rid, (inputs, outputs));
1345 }
1346
1347 self.refresh_brain_regions_hash();
1348
1349 Ok(computed)
1350 }
1351
1352 pub fn get_root_region_id(&self) -> Option<String> {
1358 self.brain_regions.get_root_region_id()
1359 }
1360
1361 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1363 self.cortical_idx_to_id.get(&cortical_idx)
1364 }
1365
1366 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1369 self.cortical_idx_to_id
1370 .iter()
1371 .map(|(idx, id)| (*idx, id.as_base_64()))
1372 .collect()
1373 }
1374
1375 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1380 let mut granularities = ahash::AHashMap::new();
1381 for (cortical_id, area) in &self.cortical_areas {
1382 let cortical_idx = self
1383 .cortical_id_to_idx
1384 .get(cortical_id)
1385 .copied()
1386 .unwrap_or(0);
1387
1388 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1391 if let Some(arr) = granularity_json.as_array() {
1392 if arr.len() == 3 {
1393 let x_opt = arr[0]
1394 .as_u64()
1395 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1396 let y_opt = arr[1]
1397 .as_u64()
1398 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1399 let z_opt = arr[2]
1400 .as_u64()
1401 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1402
1403 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1404 let granularity = (x as u32, y as u32, z as u32);
1405 if granularity != (1, 1, 1) {
1407 granularities.insert(cortical_idx, granularity);
1408 }
1409 }
1410 }
1411 }
1412 }
1413 }
1414 granularities
1415 }
1416
1417 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1419 self.cortical_areas.keys().collect()
1420 }
1421
1422 pub fn get_cortical_area_count(&self) -> usize {
1424 self.cortical_areas.len()
1425 }
1426
1427 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1441 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1442 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1443 if let Some(upstream_array) = upstream_prop.as_array() {
1444 return upstream_array
1445 .iter()
1446 .filter_map(|v| v.as_u64().map(|n| n as u32))
1447 .collect();
1448 }
1449 }
1450
1451 warn!(target: "feagi-bdu",
1453 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1454 target_cortical_id.as_base_64()
1455 );
1456 }
1457
1458 Vec::new()
1459 }
1460
1461 pub fn get_episodic_memory_upstream_cortical_areas(
1467 &self,
1468 target_cortical_id: &CorticalID,
1469 ) -> Vec<u32> {
1470 let mut episodic_upstream = Vec::new();
1471 for (src_id, src_area) in &self.cortical_areas {
1472 if src_id == target_cortical_id {
1473 continue;
1474 }
1475 let Some(mapping_dst) = src_area
1476 .properties
1477 .get("cortical_mapping_dst")
1478 .and_then(|v| v.as_object())
1479 else {
1480 continue;
1481 };
1482 let Some(rules) =
1483 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1484 else {
1485 continue;
1486 };
1487 if !Self::mapping_has_morphology_rule(rules, "episodic_memory") {
1488 continue;
1489 }
1490 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1491 continue;
1492 };
1493 episodic_upstream.push(src_idx);
1494 }
1495
1496 episodic_upstream.sort_unstable();
1497 episodic_upstream.dedup();
1498 episodic_upstream
1499 }
1500
1501 fn mapping_has_morphology_rule(rules: &[serde_json::Value], morphology: &str) -> bool {
1502 rules.iter().any(|rule| {
1503 let morphology_id = rule
1504 .as_object()
1505 .and_then(|obj| obj.get("morphology_id"))
1506 .and_then(|v| v.as_str())
1507 .or_else(|| {
1508 rule.as_array()
1509 .and_then(|arr| arr.first())
1510 .and_then(|v| v.as_str())
1511 });
1512 morphology_id == Some(morphology)
1513 })
1514 }
1515
1516 fn area_belongs_to_classifier_assembly(area: &CorticalArea) -> bool {
1518 if area
1519 .properties
1520 .get("classifier_assembly")
1521 .and_then(|value| value.as_bool())
1522 == Some(true)
1523 {
1524 return true;
1525 }
1526 matches!(
1527 area.properties
1528 .get("classifier_role")
1529 .and_then(|value| value.as_str()),
1530 Some("kernel_memory") | Some("class_memory") | Some("scan_twin")
1531 )
1532 }
1533
1534 fn mapping_is_classifier_assembly_edge(
1536 &self,
1537 src_id: &CorticalID,
1538 dst_id: &CorticalID,
1539 ) -> bool {
1540 let src_is_classifier = self
1541 .cortical_areas
1542 .get(src_id)
1543 .is_some_and(Self::area_belongs_to_classifier_assembly);
1544 let dst_is_classifier = self
1545 .cortical_areas
1546 .get(dst_id)
1547 .is_some_and(Self::area_belongs_to_classifier_assembly);
1548 if src_is_classifier || dst_is_classifier {
1549 return true;
1550 }
1551 let src_key = src_id.as_base_64();
1552 let dst_key = dst_id.as_base_64();
1553 self.classifiers.values().any(|classifier| {
1554 classifier.owns_area(&src_key)
1555 || classifier.owns_area(&dst_key)
1556 || (classifier.references_input(&src_key) && classifier.owns_area(&dst_key))
1557 })
1558 }
1559
1560 pub fn get_episodic_scan_upstream_cortical_areas(
1562 &self,
1563 target_cortical_id: &CorticalID,
1564 ) -> Vec<u32> {
1565 let mut scan_upstream = Vec::new();
1566 for (src_id, src_area) in &self.cortical_areas {
1567 if src_id == target_cortical_id {
1568 continue;
1569 }
1570 let Some(mapping_dst) = src_area
1571 .properties
1572 .get("cortical_mapping_dst")
1573 .and_then(|v| v.as_object())
1574 else {
1575 continue;
1576 };
1577 let Some(rules) =
1578 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1579 else {
1580 continue;
1581 };
1582 if !Self::mapping_has_morphology_rule(rules, "episodic_scan") {
1583 continue;
1584 }
1585 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1586 continue;
1587 };
1588 scan_upstream.push(src_idx);
1589 }
1590 scan_upstream.sort_unstable();
1591 scan_upstream.dedup();
1592 scan_upstream
1593 }
1594
1595 fn mapping_from_src_to_dst_has_scan(
1596 &self,
1597 src_area_id: &CorticalID,
1598 dst_area_id: &CorticalID,
1599 ) -> bool {
1600 let Some(src_area) = self.cortical_areas.get(src_area_id) else {
1601 return false;
1602 };
1603 let Some(mapping_dst) = src_area
1604 .properties
1605 .get("cortical_mapping_dst")
1606 .and_then(|v| v.as_object())
1607 else {
1608 return false;
1609 };
1610 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
1611 return false;
1612 };
1613 Self::mapping_has_morphology_rule(rules, "episodic_scan")
1614 }
1615
1616 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1618 upstream
1619 .iter()
1620 .filter_map(|idx| {
1621 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1622 let area = self.cortical_areas.get(cortical_id)?;
1623 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1624 None
1625 } else {
1626 Some(*idx)
1627 }
1628 })
1629 .collect()
1630 }
1631
1632 pub fn refresh_upstream_cortical_areas_from_mappings(
1636 &mut self,
1637 target_cortical_id: &CorticalID,
1638 ) -> Vec<u32> {
1639 use std::collections::HashSet;
1640 let target_id_str = target_cortical_id.as_base_64();
1641 let mut upstream_idxs = HashSet::new();
1642 for (src_id, src_area) in &self.cortical_areas {
1643 if src_id == target_cortical_id {
1644 continue;
1645 }
1646 if let Some(mapping) = src_area
1647 .properties
1648 .get("cortical_mapping_dst")
1649 .and_then(|v| v.as_object())
1650 {
1651 if mapping.contains_key(&target_id_str) {
1652 if let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) {
1653 upstream_idxs.insert(src_idx);
1654 }
1655 }
1656 }
1657 }
1658
1659 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1660 upstream_list.sort_unstable();
1661
1662 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1663 target_area.properties.insert(
1664 "upstream_cortical_areas".to_string(),
1665 serde_json::json!(upstream_list),
1666 );
1667 }
1668
1669 self.get_upstream_cortical_areas(target_cortical_id)
1670 }
1671
1672 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1682 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1683 let upstream_array = area
1684 .properties
1685 .entry("upstream_cortical_areas".to_string())
1686 .or_insert_with(|| serde_json::json!([]));
1687
1688 if let Some(arr) = upstream_array.as_array_mut() {
1689 let src_value = serde_json::json!(src_cortical_idx);
1690 if !arr.contains(&src_value) {
1691 arr.push(src_value);
1692 debug!(target: "feagi-bdu",
1693 "Added upstream area idx={} to cortical area '{}'",
1694 src_cortical_idx, target_cortical_id.as_base_64()
1695 );
1696 }
1697 }
1698 }
1699 }
1700
1701 pub fn get_memory_twin_for_upstream_idx(
1703 &self,
1704 memory_area_idx: u32,
1705 upstream_idx: u32,
1706 ) -> Option<CorticalID> {
1707 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1708 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1709 let area = self.cortical_areas.get(memory_id)?;
1710 let mapping = area
1711 .properties
1712 .get("memory_twin_areas")
1713 .and_then(|v| v.as_object())?;
1714 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1715 CorticalID::try_from_base_64(twin_b64).ok()
1716 }
1717
1718 pub fn diagnose_memory_twin_for_mapping(
1725 &self,
1726 src_area_id: &CorticalID,
1727 dst_area_id: &CorticalID,
1728 ) -> BduResult<MemoryTwinDiagnostic> {
1729 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
1730 BduError::InvalidArea(format!(
1731 "Source area not found: {}",
1732 src_area_id.as_base_64()
1733 ))
1734 })?;
1735 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
1736 BduError::InvalidArea(format!(
1737 "Destination area not found: {}",
1738 dst_area_id.as_base_64()
1739 ))
1740 })?;
1741
1742 let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
1743 let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
1744
1745 let rules_opt = src_area
1746 .properties
1747 .get("cortical_mapping_dst")
1748 .and_then(|v| v.as_object())
1749 .and_then(|mapping_dst| {
1750 Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
1751 });
1752 let mapping_exists = rules_opt.is_some();
1753 let episodic_rule_count = rules_opt
1754 .map(|rules| {
1755 rules
1756 .iter()
1757 .filter(|rule| {
1758 let morphology_id = rule
1759 .as_object()
1760 .and_then(|obj| obj.get("morphology_id"))
1761 .and_then(|v| v.as_str())
1762 .or_else(|| {
1763 rule.as_array()
1764 .and_then(|arr| arr.first())
1765 .and_then(|v| v.as_str())
1766 });
1767 morphology_id == Some("episodic_memory")
1768 })
1769 .count()
1770 })
1771 .unwrap_or(0);
1772
1773 let twin_expected =
1774 mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
1775 let twin_cortical_area = dst_area
1776 .properties
1777 .get("memory_twin_areas")
1778 .and_then(|v| v.as_object())
1779 .and_then(|map| map.get(&src_area_id.as_base_64()))
1780 .and_then(|v| v.as_str())
1781 .map(ToString::to_string);
1782 let twin_present = twin_cortical_area.is_some();
1783
1784 let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
1785 CorticalID::try_from_base_64(twin_b64)
1786 .ok()
1787 .and_then(|twin_id| {
1788 self.cortical_areas.get(&twin_id).and_then(|area| {
1789 area.properties
1790 .get("parent_region_id")
1791 .and_then(|v| v.as_str())
1792 .map(ToString::to_string)
1793 })
1794 })
1795 });
1796 let src_parent_region_id = src_area
1797 .properties
1798 .get("parent_region_id")
1799 .and_then(|v| v.as_str())
1800 .map(ToString::to_string);
1801 let dst_parent_region_id = dst_area
1802 .properties
1803 .get("parent_region_id")
1804 .and_then(|v| v.as_str())
1805 .map(ToString::to_string);
1806 let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
1807 (Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
1808 (None, None) if twin_present => Some(true),
1809 _ if twin_present => Some(false),
1810 _ => None,
1811 };
1812
1813 let reason = if !mapping_exists {
1814 Some("no_mapping_rule_between_src_and_dst".to_string())
1815 } else if episodic_rule_count == 0 {
1816 Some("mapping_exists_but_not_episodic_memory".to_string())
1817 } else if !dst_is_memory {
1818 Some("destination_is_not_memory_area".to_string())
1819 } else if src_is_memory {
1820 Some("upstream_is_memory_area_twin_not_supported".to_string())
1821 } else if !twin_present {
1822 Some("twin_expected_but_missing".to_string())
1823 } else if twin_parent_matches_src_parent == Some(false) {
1824 Some("twin_parent_region_mismatch_with_source".to_string())
1825 } else {
1826 None
1827 };
1828
1829 Ok(MemoryTwinDiagnostic {
1830 src_cortical_area: src_area_id.as_base_64(),
1831 dst_cortical_area: dst_area_id.as_base_64(),
1832 src_cortical_type: src_area.cortical_type.to_string(),
1833 dst_cortical_type: dst_area.cortical_type.to_string(),
1834 mapping_exists,
1835 episodic_rule_count,
1836 twin_expected,
1837 twin_present,
1838 twin_cortical_area,
1839 reason,
1840 src_parent_region_id,
1841 dst_parent_region_id,
1842 twin_parent_region_id,
1843 twin_parent_matches_src_parent,
1844 })
1845 }
1846
1847 pub fn ensure_memory_twin_area(
1849 &mut self,
1850 memory_area_id: &CorticalID,
1851 upstream_area_id: &CorticalID,
1852 ) -> BduResult<CorticalID> {
1853 use crate::models::CorticalAreaExt;
1854
1855 let register_replay_mapping = |manager: &mut ConnectomeManager,
1856 twin_id: &CorticalID|
1857 -> BduResult<()> {
1858 let Some(npu) = manager.npu.as_ref() else {
1859 return Ok(());
1860 };
1861 let memory_area_idx =
1862 *manager
1863 .cortical_id_to_idx
1864 .get(memory_area_id)
1865 .ok_or_else(|| {
1866 BduError::InvalidArea(format!(
1867 "Memory area idx missing for {}",
1868 memory_area_id.as_base_64()
1869 ))
1870 })?;
1871 let upstream_area_idx = *manager
1872 .cortical_id_to_idx
1873 .get(upstream_area_id)
1874 .ok_or_else(|| {
1875 BduError::InvalidArea(format!(
1876 "Upstream area idx missing for {}",
1877 upstream_area_id.as_base_64()
1878 ))
1879 })?;
1880 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1881 BduError::InvalidArea(format!(
1882 "Twin area idx missing for {}",
1883 twin_id.as_base_64()
1884 ))
1885 })?;
1886 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1887 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1888 })?;
1889 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1890 if let Ok(mut npu_lock) = npu.lock() {
1891 npu_lock.register_memory_twin_mapping(
1892 memory_area_idx,
1893 upstream_area_idx,
1894 twin_area_idx,
1895 potential,
1896 );
1897 }
1898 Ok(())
1899 };
1900
1901 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1902 BduError::InvalidArea(format!(
1903 "Memory area {} not found",
1904 memory_area_id.as_base_64()
1905 ))
1906 })?;
1907 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1908 BduError::InvalidArea(format!(
1909 "Upstream area {} not found",
1910 upstream_area_id.as_base_64()
1911 ))
1912 })?;
1913
1914 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1915 return Err(BduError::InvalidArea(format!(
1916 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1917 upstream_area_id.as_base_64()
1918 )));
1919 }
1920
1921 if let Some(existing) = memory_area
1922 .properties
1923 .get("memory_twin_areas")
1924 .and_then(|v| v.as_object())
1925 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1926 .and_then(|v| v.as_str())
1927 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1928 {
1929 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1930 register_replay_mapping(self, &existing)?;
1931 self.refresh_cortical_mappings_hash();
1932 return Ok(existing);
1933 }
1934
1935 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1936 if self.cortical_areas.contains_key(&twin_id) {
1937 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1938 let expected_source = upstream_area_id.as_base_64();
1939 let expected_target = memory_area_id.as_base_64();
1940 let existing_source = existing
1941 .properties
1942 .get("memory_twin_of")
1943 .and_then(|v| v.as_str());
1944 let existing_target = existing
1945 .properties
1946 .get("memory_twin_for")
1947 .and_then(|v| v.as_str());
1948 if existing_source != Some(expected_source.as_str())
1949 || existing_target != Some(expected_target.as_str())
1950 {
1951 warn!(
1952 target: "feagi-bdu",
1953 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1954 upstream_area_id.as_base_64(),
1955 memory_area_id.as_base_64()
1956 );
1957 existing.properties.insert(
1958 "memory_twin_of".to_string(),
1959 serde_json::json!(expected_source),
1960 );
1961 existing.properties.insert(
1962 "memory_twin_for".to_string(),
1963 serde_json::json!(expected_target),
1964 );
1965 }
1966 }
1967 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1968 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1969 register_replay_mapping(self, &twin_id)?;
1970 self.refresh_cortical_mappings_hash();
1971 return Ok(twin_id);
1972 }
1973
1974 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1975 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1976 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1977 let mut twin_area = CorticalArea::new(
1978 twin_id,
1979 0,
1980 twin_name,
1981 upstream_area.dimensions,
1982 twin_position,
1983 twin_type,
1984 )?;
1985 twin_area.properties = self.build_memory_twin_properties(
1986 memory_area,
1987 upstream_area,
1988 memory_area_id,
1989 upstream_area_id,
1990 );
1991
1992 let _twin_idx = self.add_cortical_area(twin_area)?;
1993 let _ = self.create_neurons_for_area(&twin_id);
1994
1995 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1996 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1997 register_replay_mapping(self, &twin_id)?;
1998 self.refresh_cortical_mappings_hash();
1999 Ok(twin_id)
2000 }
2001
2002 pub fn ensure_scan_twin_area(
2004 &mut self,
2005 memory_area_id: &CorticalID,
2006 field_area_id: &CorticalID,
2007 ) -> BduResult<CorticalID> {
2008 let class_channel_count = self.classifier_class_channel_count(memory_area_id)?;
2009 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
2010 BduError::InvalidArea(format!(
2011 "Memory area {} not found",
2012 memory_area_id.as_base_64()
2013 ))
2014 })?;
2015 let field_area = self.cortical_areas.get(field_area_id).ok_or_else(|| {
2016 BduError::InvalidArea(format!(
2017 "Scan field area {} not found",
2018 field_area_id.as_base_64()
2019 ))
2020 })?;
2021 if matches!(field_area.cortical_type, CorticalAreaType::Memory(_)) {
2022 return Err(BduError::InvalidArea(format!(
2023 "Scan field {} is memory type; scan twins are only for non-memory fields",
2024 field_area_id.as_base_64()
2025 )));
2026 }
2027
2028 if let Some(existing) = memory_area
2029 .properties
2030 .get("memory_twin_areas")
2031 .and_then(|v| v.as_object())
2032 .and_then(|map| map.get(&field_area_id.as_base_64()))
2033 .and_then(|v| v.as_str())
2034 .and_then(|s| CorticalID::try_from_base_64(s).ok())
2035 {
2036 self.refresh_cortical_mappings_hash();
2037 return Ok(existing);
2038 }
2039
2040 let twin_id = self.build_scan_twin_id(memory_area_id, field_area_id)?;
2041 if self.cortical_areas.contains_key(&twin_id) {
2042 self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
2043 self.refresh_cortical_mappings_hash();
2044 return Ok(twin_id);
2045 }
2046
2047 let twin_name = format!("{}_scan_twin", field_area.name.replace(' ', "_"));
2048 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
2049 let twin_position = self.build_memory_twin_position(memory_area, field_area);
2050 let twin_dims = CorticalAreaDimensions::new(
2051 field_area.dimensions.width,
2052 field_area.dimensions.height,
2053 class_channel_count,
2054 )
2055 .map_err(|e| BduError::Internal(format!("Invalid scan twin dimensions: {}", e)))?;
2056 let mut twin_area =
2057 CorticalArea::new(twin_id, 0, twin_name, twin_dims, twin_position, twin_type)?;
2058 twin_area.properties = self.build_memory_twin_properties(
2059 memory_area,
2060 field_area,
2061 memory_area_id,
2062 field_area_id,
2063 );
2064 twin_area
2065 .properties
2066 .insert("scan_twin".to_string(), serde_json::json!(true));
2067
2068 let _twin_idx = self.add_cortical_area(twin_area)?;
2069 let _ = self.create_neurons_for_area(&twin_id);
2070 self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
2071 self.refresh_cortical_mappings_hash();
2072 Ok(twin_id)
2073 }
2074
2075 fn classifier_class_channel_count(&self, memory_area_id: &CorticalID) -> BduResult<u32> {
2076 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
2077 BduError::InvalidArea(format!(
2078 "Memory area {} not found",
2079 memory_area_id.as_base_64()
2080 ))
2081 })?;
2082 let class_id = memory_area
2083 .properties
2084 .get("classifier_class_area_id")
2085 .and_then(|v| v.as_str())
2086 .ok_or_else(|| {
2087 BduError::InvalidArea(format!(
2088 "Memory area {} has no classifier_class_area_id; scan twin cannot be sized",
2089 memory_area_id.as_base_64()
2090 ))
2091 })?;
2092 let class_cortical_id = CorticalID::try_from_base_64(class_id).map_err(|e| {
2093 BduError::InvalidArea(format!(
2094 "Invalid classifier_class_area_id '{}': {}",
2095 class_id, e
2096 ))
2097 })?;
2098 let class_area = self.cortical_areas.get(&class_cortical_id).ok_or_else(|| {
2099 BduError::InvalidArea(format!(
2100 "Classifier class area {} not found",
2101 class_cortical_id.as_base_64()
2102 ))
2103 })?;
2104 let count = class_area
2105 .dimensions
2106 .width
2107 .saturating_mul(class_area.dimensions.height)
2108 .saturating_mul(class_area.dimensions.depth);
2109 if count == 0 {
2110 return Err(BduError::InvalidArea(format!(
2111 "Classifier class area {} has zero volume",
2112 class_cortical_id.as_base_64()
2113 )));
2114 }
2115 Ok(count)
2116 }
2117
2118 fn build_scan_twin_id(
2119 &self,
2120 memory_area_id: &CorticalID,
2121 field_area_id: &CorticalID,
2122 ) -> BduResult<CorticalID> {
2123 let mut hasher = Xxh64::new(DATA_HASH_SEED);
2124 hasher.write(memory_area_id.as_base_64().as_bytes());
2125 hasher.write(field_area_id.as_base_64().as_bytes());
2126 hasher.write(b"scan_twin");
2127 let hash = hasher.finish();
2128 let mut bytes = hash.to_be_bytes();
2129 bytes[0] = b'c';
2130 CorticalID::try_from_bytes(&bytes).map_err(|e| {
2131 BduError::Internal(format!("Failed to build scan twin cortical ID: {}", e))
2132 })
2133 }
2134
2135 fn build_memory_twin_position(
2136 &self,
2137 _memory_area: &CorticalArea,
2138 upstream_area: &CorticalArea,
2139 ) -> GenomeCoordinate3D {
2140 let width = upstream_area.dimensions.width as f32;
2141 let margin = (width * 0.25).ceil() as i32;
2142 let offset = upstream_area.dimensions.width as i32 + margin;
2143 GenomeCoordinate3D::new(
2144 upstream_area.position.x + offset,
2145 upstream_area.position.y,
2146 upstream_area.position.z,
2147 )
2148 }
2149
2150 fn build_memory_twin_id(
2151 &self,
2152 memory_area_id: &CorticalID,
2153 upstream_area_id: &CorticalID,
2154 ) -> BduResult<CorticalID> {
2155 let mut hasher = Xxh64::new(DATA_HASH_SEED);
2156 hasher.write(memory_area_id.as_base_64().as_bytes());
2157 hasher.write(upstream_area_id.as_base_64().as_bytes());
2158 hasher.write(b"memory_twin");
2159 let hash = hasher.finish();
2160 let mut bytes = hash.to_be_bytes();
2161 bytes[0] = b'c';
2162 CorticalID::try_from_bytes(&bytes)
2163 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
2164 }
2165
2166 fn build_memory_twin_properties(
2167 &self,
2168 _memory_area: &CorticalArea,
2169 upstream_area: &CorticalArea,
2170 memory_area_id: &CorticalID,
2171 upstream_area_id: &CorticalID,
2172 ) -> HashMap<String, serde_json::Value> {
2173 let mut props = upstream_area.properties.clone();
2174 props.remove("cortical_mapping_dst");
2175 props.remove("upstream_cortical_areas");
2176 props.remove("parent_region_id");
2177 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
2178 props.insert("is_mem_type".to_string(), serde_json::json!(false));
2179 props.insert(
2180 "memory_twin_of".to_string(),
2181 serde_json::json!(upstream_area_id.as_base_64()),
2182 );
2183 props.insert(
2184 "memory_twin_for".to_string(),
2185 serde_json::json!(memory_area_id.as_base_64()),
2186 );
2187 if let Some(parent_region_id) = upstream_area
2188 .properties
2189 .get("parent_region_id")
2190 .and_then(|v| v.as_str())
2191 {
2192 props.insert(
2193 "parent_region_id".to_string(),
2194 serde_json::json!(parent_region_id),
2195 );
2196 }
2197 props
2198 }
2199
2200 pub fn remove_memory_twin_mapping(
2201 &mut self,
2202 memory_area_id: &str,
2203 field_area_id: &str,
2204 ) -> BduResult<()> {
2205 let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
2206 BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
2207 })?;
2208 if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
2209 if let Some(twins) = memory_area
2210 .properties
2211 .get_mut("memory_twin_areas")
2212 .and_then(|value| value.as_object_mut())
2213 {
2214 twins.remove(field_area_id);
2215 }
2216 }
2217 Ok(())
2218 }
2219
2220 fn set_memory_twin_mapping(
2221 &mut self,
2222 memory_area_id: &CorticalID,
2223 upstream_area_id: &CorticalID,
2224 twin_id: &CorticalID,
2225 ) {
2226 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
2227 let mapping = memory_area
2228 .properties
2229 .entry("memory_twin_areas".to_string())
2230 .or_insert_with(|| serde_json::json!({}));
2231 if let Some(map) = mapping.as_object_mut() {
2232 map.insert(
2233 upstream_area_id.as_base_64(),
2234 serde_json::json!(twin_id.as_base_64()),
2235 );
2236 }
2237 }
2238 }
2239
2240 fn twin_is_classifier_stamp(&self, twin_id: &CorticalID) -> bool {
2242 let twin_b64 = twin_id.as_base_64();
2243 if self
2244 .classifiers
2245 .values()
2246 .any(|classifier| classifier.field_for_twin(&twin_b64).is_some())
2247 {
2248 return true;
2249 }
2250 self.cortical_areas
2251 .get(twin_id)
2252 .is_some_and(Self::area_belongs_to_classifier_assembly)
2253 }
2254
2255 pub fn rekey_memory_twin_source(
2258 &mut self,
2259 memory_area_id: &str,
2260 old_src_area_id: &str,
2261 new_src_area_id: &str,
2262 ) -> BduResult<()> {
2263 if old_src_area_id == new_src_area_id {
2264 return Ok(());
2265 }
2266 let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
2267 BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
2268 })?;
2269 let twin_entries: Vec<(String, serde_json::Value)> = {
2270 let memory_area = self.cortical_areas.get(&memory_id).ok_or_else(|| {
2271 BduError::InvalidArea(format!("Memory area {} not found", memory_area_id))
2272 })?;
2273 memory_area
2274 .properties
2275 .get("memory_twin_areas")
2276 .and_then(|value| value.as_object())
2277 .map(|twins| {
2278 twins
2279 .iter()
2280 .map(|(key, value)| (key.clone(), value.clone()))
2281 .collect()
2282 })
2283 .unwrap_or_default()
2284 };
2285 if twin_entries.is_empty() {
2286 return Ok(());
2287 }
2288 let mut used_key: Option<String> = None;
2289 let mut twin_id: Option<serde_json::Value> = None;
2290 if let Some((_, value)) = twin_entries.iter().find(|(key, _)| key == old_src_area_id) {
2291 used_key = Some(old_src_area_id.to_string());
2292 twin_id = Some(value.clone());
2293 } else {
2294 for (key, value) in &twin_entries {
2295 let Some(twin_b64) = value.as_str() else {
2296 continue;
2297 };
2298 let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) else {
2299 continue;
2300 };
2301 let owned_by_old_field = self
2302 .cortical_areas
2303 .get(&twin_cortical_id)
2304 .and_then(|twin| twin.properties.get("memory_twin_of"))
2305 .and_then(|property| property.as_str())
2306 == Some(old_src_area_id);
2307 if owned_by_old_field {
2308 used_key = Some(key.clone());
2309 twin_id = Some(value.clone());
2310 break;
2311 }
2312 }
2313 }
2314 let Some(used_key) = used_key else {
2315 return Ok(());
2316 };
2317 let Some(twin_id) = twin_id else {
2318 return Ok(());
2319 };
2320 if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
2321 if let Some(twins) = memory_area
2322 .properties
2323 .get_mut("memory_twin_areas")
2324 .and_then(|value| value.as_object_mut())
2325 {
2326 twins.remove(&used_key);
2327 twins.insert(new_src_area_id.to_string(), twin_id.clone());
2328 }
2329 }
2330 if let Some(twin_b64) = twin_id.as_str() {
2331 if let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) {
2332 if let Some(twin) = self.cortical_areas.get_mut(&twin_cortical_id) {
2333 twin.properties.insert(
2334 "memory_twin_of".to_string(),
2335 serde_json::json!(new_src_area_id),
2336 );
2337 }
2338 }
2339 }
2340 Ok(())
2341 }
2342
2343 fn ensure_memory_replay_mapping(
2344 &mut self,
2345 memory_area_id: &CorticalID,
2346 twin_id: &CorticalID,
2347 ) -> BduResult<()> {
2348 if !self.morphology_registry.contains("memory_replay") {
2349 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
2350 }
2351 self.refresh_morphologies_hash();
2352 let mapping_data = vec![serde_json::json!({
2353 "morphology_id": "memory_replay",
2354 "morphology_scalar": [1, 1, 1],
2355 "postSynapticCurrent_multiplier": 1,
2356 "plasticity_flag": false,
2357 "plasticity_constant": 0,
2358 "ltp_multiplier": 0,
2359 "ltd_multiplier": 0,
2360 "plasticity_window": 0,
2361 })];
2362 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
2363 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
2364 self.refresh_cortical_area_hashes(true, false);
2366 Ok(())
2367 }
2368
2369 pub fn teardown_owned_memory_twin_for_mapping(
2374 &mut self,
2375 memory_area_id: &CorticalID,
2376 upstream_area_id: &CorticalID,
2377 ) -> BduResult<Option<CorticalID>> {
2378 let upstream_id = upstream_area_id.as_base_64();
2379 let twin_id = self
2380 .cortical_areas
2381 .get(memory_area_id)
2382 .and_then(|memory_area| memory_area.properties.get("memory_twin_areas"))
2383 .and_then(|value| value.as_object())
2384 .and_then(|twins| twins.get(&upstream_id))
2385 .and_then(|value| value.as_str())
2386 .map(CorticalID::try_from_base_64)
2387 .transpose()
2388 .map_err(|error| {
2389 BduError::InvalidArea(format!(
2390 "Invalid owned memory twin ID for {} -> {}: {}",
2391 upstream_area_id.as_base_64(),
2392 memory_area_id.as_base_64(),
2393 error
2394 ))
2395 })?;
2396 let Some(twin_id) = twin_id else {
2397 return Ok(None);
2398 };
2399 if self.twin_is_classifier_stamp(&twin_id) {
2401 return Ok(None);
2402 }
2403
2404 let twin = self.cortical_areas.get(&twin_id).ok_or_else(|| {
2405 BduError::InvalidArea(format!(
2406 "Owned memory twin {} for {} -> {} does not exist",
2407 twin_id.as_base_64(),
2408 upstream_area_id.as_base_64(),
2409 memory_area_id.as_base_64()
2410 ))
2411 })?;
2412 let owned_by_source = twin
2413 .properties
2414 .get("memory_twin_of")
2415 .and_then(|value| value.as_str())
2416 == Some(upstream_id.as_str());
2417 let owned_by_memory = twin
2418 .properties
2419 .get("memory_twin_for")
2420 .and_then(|value| value.as_str())
2421 == Some(memory_area_id.as_base_64().as_str());
2422 if !owned_by_source || !owned_by_memory {
2423 return Err(BduError::InvalidArea(format!(
2424 "Cortical area {} is not the generated twin owned by {} -> {}",
2425 twin_id.as_base_64(),
2426 upstream_area_id.as_base_64(),
2427 memory_area_id.as_base_64()
2428 )));
2429 }
2430
2431 let memory_idx = *self.cortical_id_to_idx.get(memory_area_id).ok_or_else(|| {
2432 BduError::InvalidArea(format!(
2433 "Memory area idx missing for {}",
2434 memory_area_id.as_base_64()
2435 ))
2436 })?;
2437 let upstream_idx = *self
2438 .cortical_id_to_idx
2439 .get(upstream_area_id)
2440 .ok_or_else(|| {
2441 BduError::InvalidArea(format!(
2442 "Upstream area idx missing for {}",
2443 upstream_area_id.as_base_64()
2444 ))
2445 })?;
2446
2447 self.update_cortical_mapping(memory_area_id, &twin_id, Vec::new())?;
2448 let _ = self.regenerate_synapses_for_mapping(memory_area_id, &twin_id)?;
2449 if let Some(npu) = &self.npu {
2450 npu.lock()
2451 .unwrap()
2452 .unregister_memory_twin_mapping(memory_idx, upstream_idx);
2453 }
2454
2455 let memory_area = self.cortical_areas.get_mut(memory_area_id).ok_or_else(|| {
2456 BduError::InvalidArea(format!(
2457 "Memory area {} not found",
2458 memory_area_id.as_base_64()
2459 ))
2460 })?;
2461 let twins = memory_area
2462 .properties
2463 .get_mut("memory_twin_areas")
2464 .and_then(|value| value.as_object_mut())
2465 .ok_or_else(|| {
2466 BduError::InvalidArea(format!(
2467 "Memory area {} has invalid memory_twin_areas metadata",
2468 memory_area_id.as_base_64()
2469 ))
2470 })?;
2471 twins.remove(&upstream_id);
2472 if twins.is_empty() {
2473 memory_area.properties.remove("memory_twin_areas");
2474 }
2475
2476 for region_id in self
2477 .get_brain_region_ids()
2478 .into_iter()
2479 .cloned()
2480 .collect::<Vec<_>>()
2481 {
2482 if let Some(region) = self.get_brain_region_mut(®ion_id) {
2483 region.remove_area(&twin_id);
2484 }
2485 }
2486 self.remove_cortical_area(&twin_id)?;
2487 self.refresh_cortical_mappings_hash();
2488 Ok(Some(twin_id))
2489 }
2490
2491 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
2501 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
2502 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
2503 if let Some(arr) = upstream_prop.as_array_mut() {
2504 let src_value = serde_json::json!(src_cortical_idx);
2505 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
2506 arr.remove(pos);
2507 debug!(target: "feagi-bdu",
2508 "Removed upstream area idx={} from cortical area '{}'",
2509 src_cortical_idx, target_cortical_id.as_base_64()
2510 );
2511 }
2512 }
2513 }
2514 }
2515 }
2516
2517 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
2519 self.cortical_areas.contains_key(cortical_id)
2520 }
2521
2522 pub fn is_initialized(&self) -> bool {
2524 self.initialized && !self.cortical_areas.is_empty()
2525 }
2526
2527 pub fn add_brain_region(
2533 &mut self,
2534 region: BrainRegion,
2535 parent_id: Option<String>,
2536 ) -> BduResult<()> {
2537 self.brain_regions.add_region(region, parent_id)?;
2538 self.refresh_brain_regions_hash();
2539 Ok(())
2540 }
2541
2542 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
2544 self.brain_regions.remove_region(region_id)?;
2545 self.refresh_brain_regions_hash();
2546 Ok(())
2547 }
2548
2549 pub fn clear_brain_regions(&mut self) {
2554 self.brain_regions.clear();
2555 self.refresh_brain_regions_hash();
2556 }
2557
2558 pub fn replace_classifiers(
2560 &mut self,
2561 classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
2562 ) {
2563 self.classifiers = classifiers;
2564 self.refresh_classifiers_hash();
2565 }
2566
2567 pub fn apply_loaded_classifier_assemblies(&mut self) {
2574 let classifiers: Vec<_> = self.classifiers.values().cloned().collect();
2575 for classifier in classifiers {
2576 self.apply_loaded_classifier_assembly(&classifier);
2577 }
2578 }
2579
2580 fn apply_loaded_classifier_assembly(
2581 &mut self,
2582 classifier: &feagi_structures::genomic::classifiers::Classifier,
2583 ) {
2584 let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) else {
2585 return;
2586 };
2587 let Ok(class_mem_id) = CorticalID::try_from_base_64(&classifier.class_memory_id) else {
2588 return;
2589 };
2590
2591 let mut twin_map = serde_json::Map::new();
2592 for field in &classifier.fields {
2593 if field.field_area_id.is_empty() || field.scan_twin_id.is_empty() {
2594 continue;
2595 }
2596 let Ok(twin_id) = CorticalID::try_from_base_64(&field.scan_twin_id) else {
2597 continue;
2598 };
2599 if !self.cortical_areas.contains_key(&twin_id) {
2600 continue;
2601 }
2602 twin_map.insert(
2603 field.field_area_id.clone(),
2604 serde_json::json!(field.scan_twin_id),
2605 );
2606 if let Ok(field_id) = CorticalID::try_from_base_64(&field.field_area_id) {
2607 if let Some(field_area) = self.cortical_areas.get_mut(&field_id) {
2608 Self::ensure_assembly_burst(field_area);
2609 }
2610 }
2611 if let Some(twin_area) = self.cortical_areas.get_mut(&twin_id) {
2612 twin_area
2613 .properties
2614 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2615 twin_area.properties.insert(
2616 "classifier_role".to_string(),
2617 serde_json::json!("scan_twin"),
2618 );
2619 twin_area
2620 .properties
2621 .insert("scan_twin".to_string(), serde_json::json!(true));
2622 twin_area.properties.insert(
2623 "memory_twin_of".to_string(),
2624 serde_json::json!(field.field_area_id),
2625 );
2626 twin_area.properties.insert(
2627 "memory_twin_for".to_string(),
2628 serde_json::json!(classifier.kernel_memory_id),
2629 );
2630 Self::ensure_assembly_burst(twin_area);
2631 }
2632 }
2633
2634 if let Some(kernel_mem) = self.cortical_areas.get_mut(&kernel_mem_id) {
2635 kernel_mem
2636 .properties
2637 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2638 kernel_mem.properties.insert(
2639 "classifier_role".to_string(),
2640 serde_json::json!("kernel_memory"),
2641 );
2642 if let Some(kernel_area_id) = &classifier.kernel_area_id {
2643 kernel_mem.properties.insert(
2644 "classifier_kernel_area_id".to_string(),
2645 serde_json::json!(kernel_area_id),
2646 );
2647 }
2648 if let Some(class_area_id) = &classifier.class_area_id {
2649 kernel_mem.properties.insert(
2650 "classifier_class_area_id".to_string(),
2651 serde_json::json!(class_area_id),
2652 );
2653 }
2654 kernel_mem.properties.insert(
2655 "classifier_class_memory_id".to_string(),
2656 serde_json::json!(classifier.class_memory_id),
2657 );
2658 kernel_mem.properties.insert(
2659 "memory_twin_areas".to_string(),
2660 serde_json::Value::Object(twin_map),
2661 );
2662 Self::ensure_assembly_burst(kernel_mem);
2663 }
2664 if let Some(class_mem) = self.cortical_areas.get_mut(&class_mem_id) {
2665 class_mem
2666 .properties
2667 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2668 class_mem.properties.insert(
2669 "classifier_role".to_string(),
2670 serde_json::json!("class_memory"),
2671 );
2672 class_mem.properties.insert(
2673 "classifier_kernel_memory_id".to_string(),
2674 serde_json::json!(classifier.kernel_memory_id),
2675 );
2676 if let Some(class_area_id) = &classifier.class_area_id {
2677 class_mem.properties.insert(
2678 "classifier_class_area_id".to_string(),
2679 serde_json::json!(class_area_id),
2680 );
2681 }
2682 Self::ensure_assembly_burst(class_mem);
2683 }
2684 }
2685
2686 fn ensure_assembly_burst(area: &mut CorticalArea) {
2689 if !area.properties.contains_key("burst_engine_active") {
2690 area.properties
2691 .insert("burst_engine_active".to_string(), serde_json::json!(true));
2692 }
2693 }
2694
2695 pub fn upsert_classifier(
2696 &mut self,
2697 classifier: feagi_structures::genomic::classifiers::Classifier,
2698 ) {
2699 self.classifiers
2700 .insert(classifier.classifier_id.clone(), classifier);
2701 self.refresh_classifiers_hash();
2702 }
2703
2704 pub fn get_classifier(
2705 &self,
2706 classifier_id: &str,
2707 ) -> Option<&feagi_structures::genomic::classifiers::Classifier> {
2708 self.classifiers.get(classifier_id)
2709 }
2710
2711 pub fn list_classifiers(
2712 &self,
2713 ) -> HashMap<String, feagi_structures::genomic::classifiers::Classifier> {
2714 self.classifiers.clone()
2715 }
2716
2717 pub fn remove_classifier(
2718 &mut self,
2719 classifier_id: &str,
2720 ) -> Option<feagi_structures::genomic::classifiers::Classifier> {
2721 let removed = self.classifiers.remove(classifier_id);
2722 if removed.is_some() {
2723 self.refresh_classifiers_hash();
2724 }
2725 removed
2726 }
2727
2728 pub fn classifiers_owning_area(
2730 &self,
2731 area_id: &str,
2732 ) -> Vec<feagi_structures::genomic::classifiers::Classifier> {
2733 self.classifiers
2734 .values()
2735 .filter(|classifier| classifier.owns_area(area_id))
2736 .cloned()
2737 .collect()
2738 }
2739
2740 pub fn clear_classifier_inputs_for_area(&mut self, area_id: &str) -> usize {
2742 let mut updated = 0usize;
2743 for classifier in self.classifiers.values_mut() {
2744 if classifier.references_input(area_id) {
2745 classifier.clear_input(area_id);
2746 updated += 1;
2747 }
2748 }
2749 if updated > 0 {
2750 self.refresh_classifiers_hash();
2751 }
2752 updated
2753 }
2754
2755 pub fn apply_classifier_mapping_change(
2757 &mut self,
2758 src_area_id: &str,
2759 dst_area_id: &str,
2760 morphology_id: &str,
2761 removed: bool,
2762 ) -> usize {
2763 let mut updated = 0usize;
2764 for classifier in self.classifiers.values_mut() {
2765 if classifier.apply_mapping_change(src_area_id, dst_area_id, morphology_id, removed) {
2766 updated += 1;
2767 }
2768 }
2769 if updated > 0 {
2770 self.refresh_classifiers_hash();
2771 }
2772 updated
2773 }
2774
2775 pub fn change_brain_region_parent(
2777 &mut self,
2778 region_id: &str,
2779 new_parent_id: &str,
2780 ) -> BduResult<()> {
2781 self.brain_regions.change_parent(region_id, new_parent_id)?;
2782 self.refresh_brain_regions_hash();
2783 Ok(())
2784 }
2785
2786 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
2788 self.brain_regions.get_region(region_id)
2789 }
2790
2791 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
2793 self.brain_regions.get_region_mut(region_id)
2794 }
2795
2796 pub fn get_brain_region_ids(&self) -> Vec<&String> {
2798 self.brain_regions.get_all_region_ids()
2799 }
2800
2801 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
2803 &self.brain_regions
2804 }
2805
2806 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
2812 &self.morphology_registry
2813 }
2814
2815 pub fn get_morphology_count(&self) -> usize {
2817 self.morphology_registry.count()
2818 }
2819
2820 pub fn upsert_morphology(
2825 &mut self,
2826 morphology_id: String,
2827 morphology: feagi_evolutionary::Morphology,
2828 ) {
2829 self.morphology_registry
2830 .add_morphology(morphology_id, morphology);
2831 self.refresh_morphologies_hash();
2832 }
2833
2834 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
2840 let removed = self.morphology_registry.remove_morphology(morphology_id);
2841 if removed {
2842 self.refresh_morphologies_hash();
2843 }
2844 removed
2845 }
2846
2847 pub fn update_cortical_mapping(
2865 &mut self,
2866 src_area_id: &CorticalID,
2867 dst_area_id: &CorticalID,
2868 mapping_data: Vec<serde_json::Value>,
2869 ) -> BduResult<()> {
2870 use tracing::info;
2871
2872 self.validate_memory_outbound_mapping_contract(src_area_id, dst_area_id, &mapping_data)?;
2873 crate::region_io_designation::validate_cross_region_mapping_proposal(
2874 self,
2875 src_area_id,
2876 dst_area_id,
2877 &mapping_data,
2878 )?;
2879
2880 debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
2881
2882 {
2883 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
2885 crate::types::BduError::InvalidArea(format!(
2886 "Source area not found: {}",
2887 src_area_id
2888 ))
2889 })?;
2890
2891 let cortical_mapping_dst =
2893 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
2894 existing.as_object_mut().ok_or_else(|| {
2895 crate::types::BduError::InvalidMorphology(
2896 "cortical_mapping_dst is not an object".to_string(),
2897 )
2898 })?
2899 } else {
2900 src_area
2902 .properties
2903 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
2904 src_area
2905 .properties
2906 .get_mut("cortical_mapping_dst")
2907 .unwrap()
2908 .as_object_mut()
2909 .unwrap()
2910 };
2911
2912 if mapping_data.is_empty() {
2914 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
2916 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
2917 } else {
2918 cortical_mapping_dst.insert(
2919 dst_area_id.as_base_64(),
2920 serde_json::Value::Array(mapping_data.clone()),
2921 );
2922 debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
2923 src_area_id, dst_area_id, mapping_data.len());
2924 }
2925 }
2926
2927 self.refresh_cortical_mappings_hash();
2928
2929 Ok(())
2930 }
2931
2932 pub fn validate_memory_outbound_mapping_contract(
2939 &self,
2940 src_area_id: &CorticalID,
2941 dst_area_id: &CorticalID,
2942 mapping_data: &[serde_json::Value],
2943 ) -> BduResult<()> {
2944 if mapping_data.is_empty() {
2945 return Ok(());
2946 }
2947 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2948 BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2949 })?;
2950 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2951 BduError::InvalidArea(format!("Destination area not found: {}", dst_area_id))
2952 })?;
2953 if !matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2954 || matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2955 {
2956 return Ok(());
2957 }
2958
2959 for rule in mapping_data {
2960 let morphology_id = rule
2961 .as_object()
2962 .and_then(|rule_obj| rule_obj.get("morphology_id"))
2963 .and_then(|value| value.as_str())
2964 .or_else(|| {
2965 rule.as_array()
2966 .and_then(|rule_array| rule_array.first())
2967 .and_then(|value| value.as_str())
2968 });
2969 let is_replay_edge_to_own_twin = morphology_id == Some("memory_replay")
2970 && dst_area
2971 .properties
2972 .get("memory_twin_for")
2973 .and_then(|value| value.as_str())
2974 == Some(src_area_id.as_base_64().as_str());
2975 if morphology_id != Some("associative_memory") && !is_replay_edge_to_own_twin {
2976 return Err(BduError::InvalidMorphology(format!(
2977 "Memory-to-non-memory mapping {} -> {} only supports associative_memory \
2978 (or memory_replay to its own twin)",
2979 src_area_id, dst_area_id
2980 )));
2981 }
2982 }
2983 Ok(())
2984 }
2985
2986 pub fn regenerate_synapses_for_mapping(
2999 &mut self,
3000 src_area_id: &CorticalID,
3001 dst_area_id: &CorticalID,
3002 ) -> BduResult<usize> {
3003 use tracing::info;
3004
3005 debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
3006
3007 let mapping_rules_len = self
3008 .cortical_areas
3009 .get(src_area_id)
3010 .and_then(|area| area.properties.get("cortical_mapping_dst"))
3011 .and_then(|v| v.as_object())
3012 .and_then(|map| map.get(&dst_area_id.as_base_64()))
3013 .and_then(|v| v.as_array())
3014 .map(|arr| arr.len())
3015 .unwrap_or(0);
3016 tracing::debug!(
3017 target: "feagi-bdu",
3018 "Mapping rules for {} -> {}: {}",
3019 src_area_id,
3020 dst_area_id,
3021 mapping_rules_len
3022 );
3023
3024 let Some(npu_arc) = self.npu.clone() else {
3026 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
3027 return Ok(0);
3028 };
3029
3030 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3040 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
3041 })?;
3042 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3043 BduError::InvalidArea(format!(
3044 "No cortical idx for destination area {}",
3045 dst_area_id
3046 ))
3047 })?;
3048
3049 let mut pruned_synapse_count: usize = 0;
3052 use std::time::Instant;
3053 let start = Instant::now();
3054
3055 let (sources, targets) = {
3061 let lock_start = std::time::Instant::now();
3062 let npu = npu_arc.lock().unwrap();
3063 let lock_wait = lock_start.elapsed();
3064 tracing::debug!(
3065 target: "feagi-bdu",
3066 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
3067 lock_wait.as_secs_f64() * 1000.0,
3068 src_area_id,
3069 dst_area_id
3070 );
3071 let sources: Vec<NeuronId> = npu
3072 .get_neurons_in_cortical_area(src_idx)
3073 .into_iter()
3074 .map(NeuronId)
3075 .collect();
3076 let targets: Vec<NeuronId> = npu
3077 .get_neurons_in_cortical_area(dst_idx)
3078 .into_iter()
3079 .map(NeuronId)
3080 .collect();
3081 (sources, targets)
3082 };
3083
3084 tracing::debug!(
3085 target: "feagi-bdu",
3086 "Prune synapses: {} sources, {} targets",
3087 sources.len(),
3088 targets.len()
3089 );
3090
3091 if !sources.is_empty() && !targets.is_empty() {
3092 let remove_start = Instant::now();
3093 pruned_synapse_count = {
3094 let lock_start = std::time::Instant::now();
3095 let mut npu = npu_arc.lock().unwrap();
3096 let lock_wait = lock_start.elapsed();
3097 tracing::debug!(
3098 target: "feagi-bdu",
3099 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
3100 lock_wait.as_secs_f64() * 1000.0,
3101 src_area_id,
3102 dst_area_id
3103 );
3104 npu.remove_synapses_between(sources, targets)
3108 };
3109 let remove_time = remove_start.elapsed();
3110 let total_time = start.elapsed();
3111
3112 info!(
3113 target: "feagi-bdu",
3114 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
3115 pruned_synapse_count,
3116 src_area_id,
3117 dst_area_id,
3118 total_time.as_millis(),
3119 remove_time.as_millis()
3120 );
3121
3122 if pruned_synapse_count > 0 {
3124 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
3125 BduError::Internal(format!(
3126 "Pruned synapse count overflow (usize -> u32): {}",
3127 pruned_synapse_count
3128 ))
3129 })?;
3130 let state_manager = StateManager::instance();
3131 let state_manager = state_manager.read();
3132 let core_state = state_manager.get_core_state();
3133 core_state.subtract_synapse_count(pruned_u32);
3134 state_manager.subtract_cortical_area_outgoing_synapses(
3135 &src_area_id.as_base_64(),
3136 pruned_synapse_count,
3137 );
3138 state_manager.subtract_cortical_area_incoming_synapses(
3139 &dst_area_id.as_base_64(),
3140 pruned_synapse_count,
3141 );
3142
3143 {
3147 let mut cache = self.cached_synapse_counts_per_area.write();
3148 let entry = cache
3149 .entry(*src_area_id)
3150 .or_insert_with(|| AtomicUsize::new(0));
3151 let mut current = entry.load(Ordering::Relaxed);
3152 loop {
3153 let next = current.saturating_sub(pruned_synapse_count);
3154 match entry.compare_exchange(
3155 current,
3156 next,
3157 Ordering::Relaxed,
3158 Ordering::Relaxed,
3159 ) {
3160 Ok(_) => break,
3161 Err(v) => current = v,
3162 }
3163 }
3164 }
3165 }
3166 }
3167
3168 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
3175 tracing::debug!(
3176 target: "feagi-bdu",
3177 "Synaptogenesis created {} synapses for {} -> {}",
3178 synapse_count,
3179 src_area_id,
3180 dst_area_id
3181 );
3182
3183 if synapse_count > 0 {
3186 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
3187 BduError::Internal(format!(
3188 "Created synapse count overflow (usize -> u32): {}",
3189 synapse_count
3190 ))
3191 })?;
3192
3193 {
3195 let mut cache = self.cached_synapse_counts_per_area.write();
3196 cache
3197 .entry(*src_area_id)
3198 .or_insert_with(|| AtomicUsize::new(0))
3199 .fetch_add(synapse_count, Ordering::Relaxed);
3200 }
3201
3202 let state_manager = StateManager::instance();
3204 let state_manager = state_manager.read();
3205 let core_state = state_manager.get_core_state();
3206 core_state.add_synapse_count(created_u32);
3207 state_manager
3208 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
3209 state_manager
3210 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
3211 }
3212
3213 let src_idx_for_upstream = src_idx;
3216
3217 let has_mapping = self
3219 .cortical_areas
3220 .get(src_area_id)
3221 .and_then(|area| area.properties.get("cortical_mapping_dst"))
3222 .and_then(|v| v.as_object())
3223 .and_then(|map| map.get(&dst_area_id.as_base_64()))
3224 .is_some();
3225
3226 debug!(target: "feagi-bdu",
3227 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
3228 synapse_count,
3229 src_area_id.as_base_64(),
3230 dst_area_id.as_base_64(),
3231 has_mapping,
3232 if has_mapping { "" } else { "NOT " }
3233 );
3234
3235 if has_mapping {
3236 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
3238
3239 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
3240 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3241 && !Self::area_belongs_to_classifier_assembly(dst_area)
3242 {
3243 let is_scan = self.mapping_from_src_to_dst_has_scan(src_area_id, dst_area_id);
3244 let twin_result = if is_scan {
3245 self.ensure_scan_twin_area(dst_area_id, src_area_id)
3246 } else {
3247 self.ensure_memory_twin_area(dst_area_id, src_area_id)
3248 };
3249 if let Err(e) = twin_result {
3250 warn!(
3251 target: "feagi-bdu",
3252 "Failed to ensure {} twin for {} -> {}: {}",
3253 if is_scan { "scan" } else { "memory" },
3254 src_area_id.as_base_64(),
3255 dst_area_id.as_base_64(),
3256 e
3257 );
3258 }
3259 }
3260 }
3261
3262 #[cfg(feature = "plasticity")]
3264 if let Some(ref executor) = self.plasticity_executor {
3265 use feagi_evolutionary::extract_memory_properties;
3266
3267 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
3268 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
3269 let upstream_areas =
3270 self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
3271 debug!(
3272 target: "feagi-bdu",
3273 "Registering memory area idx={} id={} upstream={} depth={}",
3274 dst_area.cortical_idx,
3275 dst_area_id.as_base_64(),
3276 upstream_areas.len(),
3277 mem_props.temporal_depth
3278 );
3279
3280 if let Some(ref npu_arc) = self.npu {
3283 if let Ok(mut npu) = npu_arc.lock() {
3284 let existing_configs = npu.get_all_fire_ledger_configs();
3285 for &upstream_idx in &upstream_areas {
3286 let existing = existing_configs
3287 .iter()
3288 .find(|(idx, _)| *idx == upstream_idx)
3289 .map(|(_, w)| *w)
3290 .unwrap_or(0);
3291
3292 let desired = mem_props.temporal_depth as usize;
3293 let resolved = existing.max(desired);
3294 if resolved != existing {
3295 if let Err(e) =
3296 npu.configure_fire_ledger_window(upstream_idx, resolved)
3297 {
3298 warn!(
3299 target: "feagi-bdu",
3300 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
3301 upstream_idx,
3302 resolved,
3303 e
3304 );
3305 }
3306 }
3307 }
3308 } else {
3309 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
3310 }
3311 }
3312
3313 if let Ok(exec) = executor.lock() {
3314 use feagi_npu_plasticity::{
3315 MemoryNeuronLifecycleConfig, PlasticityExecutor,
3316 };
3317
3318 let lifecycle_config = MemoryNeuronLifecycleConfig {
3319 initial_lifespan: mem_props.init_lifespan,
3320 lifespan_growth_rate: mem_props.lifespan_growth_rate,
3321 longterm_threshold: mem_props.longterm_threshold,
3322 max_reactivations: 1000,
3323 };
3324
3325 exec.register_memory_area(
3326 dst_area.cortical_idx,
3327 dst_area_id.as_base_64(),
3328 mem_props.temporal_depth,
3329 upstream_areas.clone(),
3330 Some(lifecycle_config),
3331 mem_props.mp_learning_enabled,
3332 );
3333 self.configure_memory_scan_on_executor(&*exec, dst_area_id);
3334 } else {
3335 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
3336 }
3337 } else {
3338 debug!(
3339 target: "feagi-bdu",
3340 "Skipping plasticity registration: no memory properties for area {}",
3341 dst_area_id.as_base_64()
3342 );
3343 }
3344 } else {
3345 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
3346 }
3347 } else {
3348 warn!(
3349 target: "feagi-bdu",
3350 "PlasticityExecutor not available; memory area {} not registered",
3351 dst_area_id.as_base_64()
3352 );
3353 }
3354
3355 #[cfg(not(feature = "plasticity"))]
3356 {
3357 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
3358 }
3359 } else {
3360 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
3362
3363 let destination_is_memory = self
3364 .cortical_areas
3365 .get(dst_area_id)
3366 .is_some_and(|area| matches!(area.cortical_type, CorticalAreaType::Memory(_)));
3367 if destination_is_memory {
3368 self.teardown_owned_memory_twin_for_mapping(dst_area_id, src_area_id)?;
3369 }
3370
3371 let mut npu = npu_arc.lock().unwrap();
3373 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
3374 }
3375
3376 debug!(
3377 target: "feagi-bdu",
3378 "Created {} new synapses: {} -> {}",
3379 synapse_count,
3380 src_area_id,
3381 dst_area_id
3382 );
3383
3384 if pruned_synapse_count > 0 || synapse_count == 0 {
3387 let mut npu = npu_arc.lock().unwrap();
3388 npu.rebuild_synapse_index();
3389 info!(
3390 target: "feagi-bdu",
3391 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
3392 src_area_id,
3393 dst_area_id,
3394 pruned_synapse_count,
3395 synapse_count
3396 );
3397 } else {
3398 debug!(
3399 target: "feagi-bdu",
3400 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
3401 src_area_id,
3402 dst_area_id,
3403 synapse_count
3404 );
3405 }
3406
3407 {
3409 let npu = npu_arc.lock().unwrap();
3410 let fresh_count = npu.get_synapse_count();
3411 self.cached_synapse_count
3412 .store(fresh_count, Ordering::Relaxed);
3413 }
3414
3415 Ok(synapse_count)
3416 }
3417
3418 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
3420 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
3421 }
3422
3423 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
3424 v.as_u64()
3425 .map(|n| n as usize)
3426 .or_else(|| v.as_f64().map(|f| f as usize))
3427 }
3428
3429 #[allow(clippy::too_many_arguments)]
3431 fn register_stdp_mapping_for_rule(
3432 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3433 src_area_id: &CorticalID,
3434 dst_area_id: &CorticalID,
3435 src_cortical_idx: u32,
3436 dst_cortical_idx: u32,
3437 rule_obj: &serde_json::Map<String, serde_json::Value>,
3438 bidirectional_stdp: bool,
3439 synapse_psp: f32,
3440 synapse_type: feagi_npu_neural::SynapseType,
3441 ) -> BduResult<()> {
3442 let plasticity_window = rule_obj
3443 .get("plasticity_window")
3444 .and_then(Self::json_number_as_usize_for_stdp)
3445 .ok_or_else(|| {
3446 BduError::Internal(format!(
3447 "Missing plasticity_window in plastic mapping rule {} -> {}",
3448 src_area_id, dst_area_id
3449 ))
3450 })?;
3451 let plasticity_constant = rule_obj
3452 .get("plasticity_constant")
3453 .and_then(Self::json_number_as_i64_for_stdp)
3454 .ok_or_else(|| {
3455 BduError::Internal(format!(
3456 "Missing plasticity_constant in plastic mapping rule {} -> {}",
3457 src_area_id, dst_area_id
3458 ))
3459 })?;
3460 let ltp_i64 = rule_obj
3461 .get("ltp_multiplier")
3462 .and_then(Self::json_number_as_i64_for_stdp)
3463 .ok_or_else(|| {
3464 BduError::Internal(format!(
3465 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
3466 src_area_id, dst_area_id
3467 ))
3468 })?;
3469 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
3470 BduError::Internal(format!(
3471 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
3472 i8::MIN,
3473 i8::MAX,
3474 ltp_i64,
3475 src_area_id,
3476 dst_area_id
3477 ))
3478 })?;
3479 let ltd_i64 = rule_obj
3480 .get("ltd_multiplier")
3481 .and_then(Self::json_number_as_i64_for_stdp)
3482 .ok_or_else(|| {
3483 BduError::Internal(format!(
3484 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
3485 src_area_id, dst_area_id
3486 ))
3487 })?;
3488 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
3489 BduError::Internal(format!(
3490 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
3491 i8::MIN,
3492 i8::MAX,
3493 ltd_i64,
3494 src_area_id,
3495 dst_area_id
3496 ))
3497 })?;
3498
3499 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
3503 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
3504 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
3505 }
3506 Some(s) if s.eq_ignore_ascii_case("stdp") => {
3507 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
3508 }
3509 Some(s) if s.eq_ignore_ascii_case("off") => {
3510 feagi_npu_burst_engine::npu::PlasticityMode::Off
3511 }
3512 Some(other) => {
3513 return Err(BduError::Internal(format!(
3514 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
3515 other, src_area_id, dst_area_id
3516 )));
3517 }
3518 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
3519 };
3520
3521 let eligibility_decay_bursts = rule_obj
3523 .get("eligibility_decay_bursts")
3524 .and_then(|v| v.as_u64())
3525 .map(|n| n as u32)
3526 .unwrap_or(0);
3527 let reward_source_area_id = rule_obj
3528 .get("reward_source_area")
3529 .and_then(|v| v.as_str())
3530 .map(str::to_string);
3531 let punishment_source_area_id = rule_obj
3532 .get("punishment_source_area")
3533 .and_then(|v| v.as_str())
3534 .map(str::to_string);
3535
3536 let max_weight_provided = rule_obj.get("max_weight").is_some()
3541 && !rule_obj
3542 .get("max_weight")
3543 .map(|v| v.is_null())
3544 .unwrap_or(true);
3545 let max_weight: f32 = if max_weight_provided {
3546 let raw = rule_obj
3547 .get("max_weight")
3548 .and_then(|v| v.as_f64())
3549 .ok_or_else(|| {
3550 BduError::Internal(format!(
3551 "max_weight must be a number on mapping {} -> {}",
3552 src_area_id, dst_area_id
3553 ))
3554 })?;
3555 if raw.is_nan() || raw <= 0.0 {
3556 return Err(BduError::Internal(format!(
3557 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
3558 raw, src_area_id, dst_area_id
3559 )));
3560 }
3561 raw as f32
3562 } else {
3563 f32::INFINITY
3564 };
3565
3566 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
3569 && !rule_obj
3570 .get("plasticity_eta")
3571 .map(|v| v.is_null())
3572 .unwrap_or(true);
3573 let plasticity_eta: f32 = if plasticity_eta_provided {
3574 let raw = rule_obj
3575 .get("plasticity_eta")
3576 .and_then(|v| v.as_f64())
3577 .ok_or_else(|| {
3578 BduError::Internal(format!(
3579 "plasticity_eta must be a number on mapping {} -> {}",
3580 src_area_id, dst_area_id
3581 ))
3582 })?;
3583 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
3584 return Err(BduError::Internal(format!(
3585 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
3586 raw, src_area_id, dst_area_id
3587 )));
3588 }
3589 raw as f32
3590 } else {
3591 1.0
3592 };
3593
3594 if !matches!(
3596 plasticity_mode,
3597 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
3598 ) && (reward_source_area_id.is_some()
3599 || punishment_source_area_id.is_some()
3600 || eligibility_decay_bursts != 0)
3601 {
3602 return Err(BduError::Internal(format!(
3603 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
3604 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
3605 src_area_id, dst_area_id
3606 )));
3607 }
3608
3609 if matches!(
3613 plasticity_mode,
3614 feagi_npu_burst_engine::npu::PlasticityMode::Off
3615 ) && max_weight_provided
3616 {
3617 return Err(BduError::Internal(format!(
3618 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
3619 src_area_id, dst_area_id
3620 )));
3621 }
3622
3623 if matches!(
3624 plasticity_mode,
3625 feagi_npu_burst_engine::npu::PlasticityMode::Off
3626 ) && plasticity_eta_provided
3627 {
3628 return Err(BduError::Internal(format!(
3629 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
3630 src_area_id, dst_area_id
3631 )));
3632 }
3633
3634 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
3635 let mut npu_lock = npu
3636 .lock()
3637 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3638 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
3639
3640 let resolve_optional_area =
3645 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
3646 let Some(name) = name_opt else {
3647 return Ok(None);
3648 };
3649 match npu_lock.get_cortical_area_id(name.as_str()) {
3650 Some(idx) => Ok(Some(idx)),
3651 None => Err(BduError::Internal(format!(
3652 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
3653 label, name, src_area_id, dst_area_id
3654 ))),
3655 }
3656 };
3657 let reward_source_area =
3658 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
3659 let punishment_source_area =
3660 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
3661
3662 if matches!(
3664 plasticity_mode,
3665 feagi_npu_burst_engine::npu::PlasticityMode::Off
3666 ) {
3667 return Ok(());
3668 }
3669
3670 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
3671 plasticity_window,
3672 plasticity_constant,
3673 ltp_multiplier,
3674 ltd_multiplier,
3675 bidirectional_stdp,
3676 associative_memory_mapping: rule_obj
3677 .get("morphology_id")
3678 .and_then(|value| value.as_str())
3679 == Some("associative_memory"),
3680 synapse_psp,
3681 synapse_type,
3682 plasticity_mode,
3683 eligibility_decay_bursts,
3684 reward_source_area,
3685 punishment_source_area,
3686 max_weight,
3687 plasticity_eta,
3688 };
3689
3690 npu_lock
3691 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
3692 .map_err(|e| {
3693 BduError::Internal(format!(
3694 "Failed to register STDP mapping {} -> {}: {}",
3695 src_area_id, dst_area_id, e
3696 ))
3697 })?;
3698
3699 let mut areas_to_track: Vec<(u32, usize)> = vec![
3703 (src_cortical_idx, plasticity_window),
3704 (dst_cortical_idx, plasticity_window),
3705 ];
3706 if let Some(area) = reward_source_area {
3707 areas_to_track.push((area, 1));
3708 }
3709 if let Some(area) = punishment_source_area {
3710 areas_to_track.push((area, 1));
3711 }
3712
3713 let existing_configs = npu_lock.get_all_fire_ledger_configs();
3714 for (area_idx, required_depth) in areas_to_track {
3715 let existing = existing_configs
3716 .iter()
3717 .find(|(idx, _)| *idx == area_idx)
3718 .map(|(_, w)| *w)
3719 .unwrap_or(0);
3720 let resolved = existing.max(required_depth);
3721 if resolved != existing {
3722 npu_lock
3723 .configure_fire_ledger_window(area_idx, resolved)
3724 .map_err(|e| {
3725 BduError::Internal(format!(
3726 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
3727 area_idx, resolved, e
3728 ))
3729 })?;
3730 }
3731 }
3732
3733 Ok(())
3734 }
3735
3736 fn resolve_synapse_params_for_rule(
3738 &self,
3739 src_area_id: &CorticalID,
3740 rule: &serde_json::Value,
3741 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
3742 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3744 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3745 })?;
3746
3747 let (weight, synapse_type) = {
3749 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
3750 if let Some(i) = v.as_i64() {
3751 return Some(i as f64);
3752 }
3753 v.as_f64()
3754 };
3755
3756 let mult: f64 = if let Some(obj) = rule.as_object() {
3757 obj.get("postSynapticCurrent_multiplier")
3758 .and_then(parse_f64)
3759 .unwrap_or(1.0)
3760 } else if let Some(arr) = rule.as_array() {
3761 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
3762 } else {
3763 128.0
3764 };
3765
3766 if mult < 0.0 {
3767 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
3768 } else {
3769 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
3770 }
3771 };
3772
3773 use crate::models::cortical_area::CorticalAreaExt;
3775 let psp_f32 = src_area.postsynaptic_current();
3776
3777 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
3778 obj.get("synaptic_delay_bursts")
3779 .and_then(|v| v.as_u64())
3780 .map(|d| u8::try_from(d).unwrap_or(1))
3781 .unwrap_or(1)
3782 } else if let Some(arr) = rule.as_array() {
3783 arr.get(8)
3784 .and_then(|v| v.as_u64())
3785 .map(|d| u8::try_from(d).unwrap_or(1))
3786 .unwrap_or(1)
3787 } else {
3788 1
3789 };
3790 if delay_bursts < 1 {
3791 return Err(crate::types::BduError::Internal(format!(
3792 "synaptic_delay_bursts must be >= 1 (src area {})",
3793 src_area_id.as_base_64()
3794 )));
3795 }
3796
3797 tracing::debug!(
3798 target: "feagi-bdu",
3799 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
3800 src_area_id.as_base_64(),
3801 weight,
3802 psp_f32,
3803 synapse_type,
3804 delay_bursts
3805 );
3806
3807 Ok((weight, psp_f32, synapse_type, delay_bursts))
3808 }
3809
3810 fn apply_cortical_mapping_for_pair(
3812 &mut self,
3813 src_area_id: &CorticalID,
3814 dst_area_id: &CorticalID,
3815 ) -> BduResult<usize> {
3816 let rules = {
3822 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3823 crate::types::BduError::InvalidArea(format!(
3824 "Source area not found: {}",
3825 src_area_id
3826 ))
3827 })?;
3828
3829 let Some(mapping_dst) = src_area
3830 .properties
3831 .get("cortical_mapping_dst")
3832 .and_then(|v| v.as_object())
3833 else {
3834 return Ok(0);
3835 };
3836
3837 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
3838 else {
3839 return Ok(0);
3840 };
3841
3842 rules.clone()
3843 }; if rules.is_empty() {
3846 return Ok(0);
3847 }
3848
3849 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3851 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
3852 })?;
3853 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3854 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
3855 })?;
3856
3857 let npu_arc = self
3859 .npu
3860 .as_ref()
3861 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
3862 .clone();
3863
3864 tracing::debug!(
3865 target: "feagi-bdu",
3866 "Applying {} mapping rule(s) for {} -> {}",
3867 rules.len(),
3868 src_area_id,
3869 dst_area_id
3870 );
3871 let mut total_synapses = 0;
3873 for rule in &rules {
3874 let morphology_id = if let Some(rule_obj) = rule.as_object() {
3875 rule_obj
3876 .get("morphology_id")
3877 .and_then(|v| v.as_str())
3878 .unwrap_or("unknown")
3879 .to_string()
3880 } else if let Some(rule_arr) = rule.as_array() {
3881 rule_arr
3882 .first()
3883 .and_then(|v| v.as_str())
3884 .unwrap_or("unknown")
3885 .to_string()
3886 } else {
3887 "unknown".to_string()
3888 };
3889
3890 let rule_keys: Vec<String> = rule
3891 .as_object()
3892 .map(|obj| obj.keys().cloned().collect())
3893 .unwrap_or_default();
3894
3895 let mut plasticity_flag = rule
3897 .as_object()
3898 .and_then(|obj| obj.get("plasticity_flag"))
3899 .and_then(|v| v.as_bool())
3900 .unwrap_or(false);
3901 if morphology_id == "associative_memory" {
3902 plasticity_flag = true;
3903 }
3904 if plasticity_flag {
3905 let Some(rule_obj) = rule.as_object() else {
3906 return Err(crate::types::BduError::InvalidMorphology(
3907 "Plasticity mapping rule must be an object format".to_string(),
3908 ));
3909 };
3910 let (_weight, psp, synapse_type, _delay_bursts) =
3911 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3912 let bidirectional_stdp = false;
3915 if let Err(e) = Self::register_stdp_mapping_for_rule(
3916 &npu_arc,
3917 src_area_id,
3918 dst_area_id,
3919 src_cortical_idx,
3920 dst_cortical_idx,
3921 rule_obj,
3922 bidirectional_stdp,
3923 psp,
3924 synapse_type,
3925 ) {
3926 tracing::error!(
3927 target: "feagi-bdu",
3928 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
3929 src_area_id,
3930 dst_area_id,
3931 morphology_id,
3932 rule_keys,
3933 e
3934 );
3935 return Err(e);
3936 }
3937 }
3938
3939 if let Some(gate_area_str) = rule
3943 .as_object()
3944 .and_then(|obj| obj.get("gate_source_area"))
3945 .and_then(|v| v.as_str())
3946 {
3947 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
3948 crate::types::BduError::Internal(format!(
3949 "Invalid gate_source_area '{}' on mapping {} -> {}",
3950 gate_area_str, src_area_id, dst_area_id
3951 ))
3952 })?;
3953 let gate_cortical_idx =
3954 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
3955 crate::types::BduError::Internal(format!(
3956 "Unknown gate_source_area '{}' on mapping {} -> {}",
3957 gate_area_str, src_area_id, dst_area_id
3958 ))
3959 })?;
3960
3961 let mut npu_lock = npu_arc.lock().map_err(|_| {
3962 crate::types::BduError::Internal(
3963 "Failed to acquire NPU lock for gate registration".to_string(),
3964 )
3965 })?;
3966 if let Err(e) = npu_lock.register_gate_mapping(
3967 src_cortical_idx,
3968 dst_cortical_idx,
3969 *gate_cortical_idx,
3970 ) {
3971 tracing::error!(
3972 target: "feagi-bdu",
3973 "Gate mapping registration failed for {} -> {} (gate={}): {}",
3974 src_area_id,
3975 dst_area_id,
3976 gate_area_str,
3977 e
3978 );
3979 return Err(crate::types::BduError::Internal(format!(
3980 "Gate registration failed: {}",
3981 e
3982 )));
3983 }
3984 }
3985
3986 let synapse_count = match self.apply_single_morphology_rule(
3988 src_area_id,
3989 dst_area_id,
3990 rule,
3991 ) {
3992 Ok(count) => count,
3993 Err(e) => {
3994 tracing::error!(
3995 target: "feagi-bdu",
3996 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
3997 src_area_id,
3998 dst_area_id,
3999 morphology_id,
4000 rule_keys,
4001 e
4002 );
4003 return Err(e);
4004 }
4005 };
4006 total_synapses += synapse_count;
4007 tracing::debug!(
4008 target: "feagi-bdu",
4009 "Rule {} created {} synapses for {} -> {}",
4010 morphology_id,
4011 synapse_count,
4012 src_area_id,
4013 dst_area_id
4014 );
4015 }
4016
4017 Ok(total_synapses)
4018 }
4019
4020 #[allow(clippy::too_many_arguments)]
4034 fn apply_function_morphology(
4035 &self,
4036 morphology_id: &str,
4037 rule: &serde_json::Value,
4038 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4039 npu: &mut feagi_npu_burst_engine::DynamicNPU,
4040 src_area_id: &CorticalID,
4041 dst_area_id: &CorticalID,
4042 src_idx: u32,
4043 dst_idx: u32,
4044 weight: f32,
4045 psp: f32,
4046 synapse_attractivity: u8,
4047 synapse_type: feagi_npu_neural::SynapseType,
4048 delay_bursts: u8,
4049 ) -> BduResult<usize> {
4050 match morphology_id {
4051 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
4052 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4054 crate::types::BduError::InvalidArea(format!(
4055 "Source area not found: {}",
4056 src_area_id
4057 ))
4058 })?;
4059 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4060 crate::types::BduError::InvalidArea(format!(
4061 "Destination area not found: {}",
4062 dst_area_id
4063 ))
4064 })?;
4065
4066 let src_dimensions = (
4067 src_area.dimensions.width as usize,
4068 src_area.dimensions.height as usize,
4069 src_area.dimensions.depth as usize,
4070 );
4071 let dst_dimensions = (
4072 dst_area.dimensions.width as usize,
4073 dst_area.dimensions.height as usize,
4074 dst_area.dimensions.depth as usize,
4075 );
4076
4077 let transpose = match morphology_id {
4080 "transpose_xy" => Some((1, 0, 2)),
4081 "transpose_yz" => Some((0, 2, 1)),
4082 "transpose_xz" => Some((2, 1, 0)),
4083 _ => None,
4084 };
4085
4086 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
4087 let count = apply_projector_morphology_with_dimensions(
4088 npu,
4089 src_idx,
4090 dst_idx,
4091 src_dimensions,
4092 dst_dimensions,
4093 transpose,
4094 None, weight,
4096 psp,
4097 synapse_attractivity,
4098 synapse_type,
4099 0,
4100 delay_bursts,
4101 )?;
4102 npu.rebuild_synapse_index();
4104 Ok(count as usize)
4105 }
4106 "centered_projector" => {
4107 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4108 crate::types::BduError::InvalidArea(format!(
4109 "Source area not found: {}",
4110 src_area_id
4111 ))
4112 })?;
4113 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4114 crate::types::BduError::InvalidArea(format!(
4115 "Destination area not found: {}",
4116 dst_area_id
4117 ))
4118 })?;
4119
4120 let src_dimensions = (
4121 src_area.dimensions.width as usize,
4122 src_area.dimensions.height as usize,
4123 src_area.dimensions.depth as usize,
4124 );
4125 let dst_dimensions = (
4126 dst_area.dimensions.width as usize,
4127 dst_area.dimensions.height as usize,
4128 dst_area.dimensions.depth as usize,
4129 );
4130
4131 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
4132 npu,
4133 src_idx,
4134 dst_idx,
4135 src_dimensions,
4136 dst_dimensions,
4137 weight,
4138 psp,
4139 synapse_attractivity,
4140 synapse_type,
4141 delay_bursts,
4142 )?;
4143 if count > 0 {
4144 npu.rebuild_synapse_index();
4145 }
4146 Ok(count as usize)
4147 }
4148 "episodic_memory" => {
4149 use tracing::trace;
4152 trace!(
4153 target: "feagi-bdu",
4154 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
4155 src_idx, dst_idx
4156 );
4157 Ok(0)
4158 }
4159 "episodic_scan" => {
4160 use tracing::trace;
4161 trace!(
4162 target: "feagi-bdu",
4163 "Episodic scan morphology: {} -> {} (no physical synapses, plasticity-driven)",
4164 src_idx, dst_idx
4165 );
4166 Ok(0)
4167 }
4168 "memory_replay" => {
4169 use tracing::trace;
4171 trace!(
4172 target: "feagi-bdu",
4173 "Memory replay morphology: {} -> {} (no physical synapses)",
4174 src_idx, dst_idx
4175 );
4176 Ok(0)
4177 }
4178 "associative_memory" => {
4179 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4183 crate::types::BduError::InvalidArea(format!(
4184 "Source area not found: {}",
4185 src_area_id
4186 ))
4187 })?;
4188 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4189 crate::types::BduError::InvalidArea(format!(
4190 "Destination area not found: {}",
4191 dst_area_id
4192 ))
4193 })?;
4194
4195 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
4196 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
4197 {
4198 let src_dimensions = (
4199 src_area.dimensions.width as usize,
4200 src_area.dimensions.height as usize,
4201 src_area.dimensions.depth as usize,
4202 );
4203 let dst_dimensions = (
4204 dst_area.dimensions.width as usize,
4205 dst_area.dimensions.height as usize,
4206 dst_area.dimensions.depth as usize,
4207 );
4208 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
4209 let count = apply_projector_morphology_with_dimensions(
4210 npu,
4211 src_idx,
4212 dst_idx,
4213 src_dimensions,
4214 dst_dimensions,
4215 None,
4216 None,
4217 weight,
4218 psp,
4219 synapse_attractivity,
4220 synapse_type,
4221 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
4222 delay_bursts,
4223 )?;
4224 npu.rebuild_synapse_index();
4225 Ok(count as usize)
4226 } else {
4227 Ok(0)
4228 }
4229 }
4230 "block_to_block" => {
4231 tracing::warn!(
4232 target: "feagi-bdu",
4233 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
4234 src_idx, dst_idx
4235 );
4236 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4238 crate::types::BduError::InvalidArea(format!(
4239 "Source area not found: {}",
4240 src_area_id
4241 ))
4242 })?;
4243 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4244 crate::types::BduError::InvalidArea(format!(
4245 "Destination area not found: {}",
4246 dst_area_id
4247 ))
4248 })?;
4249
4250 let src_dimensions = (
4251 src_area.dimensions.width as usize,
4252 src_area.dimensions.height as usize,
4253 src_area.dimensions.depth as usize,
4254 );
4255 let dst_dimensions = (
4256 dst_area.dimensions.width as usize,
4257 dst_area.dimensions.height as usize,
4258 dst_area.dimensions.depth as usize,
4259 );
4260
4261 let scalar = if let Some(obj) = rule.as_object() {
4263 if let Some(scalar_arr) =
4265 obj.get("morphology_scalar").and_then(|v| v.as_array())
4266 {
4267 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
4269 } else {
4270 1 }
4272 } else if let Some(arr) = rule.as_array() {
4273 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
4275 } else {
4276 1 };
4278
4279 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
4282 let count = if estimated_neurons > 100_000 {
4283 let _ = npu;
4285
4286 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
4287 npu_arc,
4288 src_idx,
4289 dst_idx,
4290 src_dimensions,
4291 dst_dimensions,
4292 scalar, weight,
4294 psp,
4295 synapse_attractivity,
4296 synapse_type,
4297 delay_bursts,
4298 )? as usize
4299 } else {
4300 tracing::warn!(
4302 target: "feagi-bdu",
4303 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
4304 src_idx, dst_idx, src_dimensions, dst_dimensions
4305 );
4306 let count =
4307 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
4308 npu,
4309 src_idx,
4310 dst_idx,
4311 src_dimensions,
4312 dst_dimensions,
4313 scalar, weight,
4315 psp,
4316 synapse_attractivity,
4317 synapse_type,
4318 delay_bursts,
4319 )? as usize;
4320 tracing::warn!(
4321 target: "feagi-bdu",
4322 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
4323 count
4324 );
4325 if count > 0 {
4327 npu.rebuild_synapse_index();
4328 }
4329 count
4330 };
4331
4332 if count > 0 && estimated_neurons > 100_000 {
4334 let mut npu_lock = npu_arc.lock().unwrap();
4335 npu_lock.rebuild_synapse_index();
4336 }
4337
4338 Ok(count)
4339 }
4340 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
4341 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
4342 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4343 crate::types::BduError::InvalidArea(format!(
4344 "Source area not found: {}",
4345 src_area_id
4346 ))
4347 })?;
4348 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4349 crate::types::BduError::InvalidArea(format!(
4350 "Destination area not found: {}",
4351 dst_area_id
4352 ))
4353 })?;
4354
4355 let src_dimensions = (
4356 src_area.dimensions.width as usize,
4357 src_area.dimensions.height as usize,
4358 src_area.dimensions.depth as usize,
4359 );
4360 let dst_dimensions = (
4361 dst_area.dimensions.width as usize,
4362 dst_area.dimensions.height as usize,
4363 dst_area.dimensions.depth as usize,
4364 );
4365
4366 let (axis, mode) = match morphology_id {
4367 "bitmask_encoder_x" => (
4368 crate::connectivity::core_morphologies::BitmaskAxis::X,
4369 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4370 ),
4371 "bitmask_encoder_y" => (
4372 crate::connectivity::core_morphologies::BitmaskAxis::Y,
4373 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4374 ),
4375 "bitmask_encoder_z" => (
4376 crate::connectivity::core_morphologies::BitmaskAxis::Z,
4377 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4378 ),
4379 "bitmask_decoder_x" => (
4380 crate::connectivity::core_morphologies::BitmaskAxis::X,
4381 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4382 ),
4383 "bitmask_decoder_y" => (
4384 crate::connectivity::core_morphologies::BitmaskAxis::Y,
4385 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4386 ),
4387 "bitmask_decoder_z" => (
4388 crate::connectivity::core_morphologies::BitmaskAxis::Z,
4389 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4390 ),
4391 _ => unreachable!("matched bitmask morphology above"),
4392 };
4393
4394 let count =
4395 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
4396 npu,
4397 src_idx,
4398 dst_idx,
4399 src_dimensions,
4400 dst_dimensions,
4401 axis,
4402 mode,
4403 weight,
4404 psp,
4405 synapse_attractivity,
4406 synapse_type,
4407 delay_bursts,
4408 )?;
4409 if count > 0 {
4410 npu.rebuild_synapse_index();
4411 }
4412 Ok(count as usize)
4413 }
4414 "sweeper" => {
4415 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4416 crate::types::BduError::InvalidArea(format!(
4417 "Destination area not found: {}",
4418 dst_area_id
4419 ))
4420 })?;
4421 let dst_dimensions = (
4422 dst_area.dimensions.width as usize,
4423 dst_area.dimensions.height as usize,
4424 dst_area.dimensions.depth as usize,
4425 );
4426
4427 let count =
4428 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
4429 npu,
4430 src_idx,
4431 dst_idx,
4432 dst_dimensions,
4433 weight,
4434 psp,
4435 synapse_attractivity,
4436 synapse_type,
4437 delay_bursts,
4438 )?;
4439 if count > 0 {
4440 npu.rebuild_synapse_index();
4441 }
4442 Ok(count as usize)
4443 }
4444 "last_to_first" => {
4445 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4446 crate::types::BduError::InvalidArea(format!(
4447 "Source area not found: {}",
4448 src_area_id
4449 ))
4450 })?;
4451 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4452 crate::types::BduError::InvalidArea(format!(
4453 "Destination area not found: {}",
4454 dst_area_id
4455 ))
4456 })?;
4457 let src_dimensions = (
4458 src_area.dimensions.width as usize,
4459 src_area.dimensions.height as usize,
4460 src_area.dimensions.depth as usize,
4461 );
4462 let dst_dimensions = (
4463 dst_area.dimensions.width as usize,
4464 dst_area.dimensions.height as usize,
4465 dst_area.dimensions.depth as usize,
4466 );
4467
4468 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
4469 npu,
4470 src_idx,
4471 dst_idx,
4472 src_dimensions,
4473 dst_dimensions,
4474 weight,
4475 psp,
4476 synapse_attractivity,
4477 synapse_type,
4478 delay_bursts,
4479 )?;
4480 if count > 0 {
4481 npu.rebuild_synapse_index();
4482 }
4483 Ok(count as usize)
4484 }
4485 "first_to_last" => {
4486 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4487 crate::types::BduError::InvalidArea(format!(
4488 "Source area not found: {}",
4489 src_area_id
4490 ))
4491 })?;
4492 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4493 crate::types::BduError::InvalidArea(format!(
4494 "Destination area not found: {}",
4495 dst_area_id
4496 ))
4497 })?;
4498 let src_dimensions = (
4499 src_area.dimensions.width as usize,
4500 src_area.dimensions.height as usize,
4501 src_area.dimensions.depth as usize,
4502 );
4503 let dst_dimensions = (
4504 dst_area.dimensions.width as usize,
4505 dst_area.dimensions.height as usize,
4506 dst_area.dimensions.depth as usize,
4507 );
4508
4509 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
4510 npu,
4511 src_idx,
4512 dst_idx,
4513 src_dimensions,
4514 dst_dimensions,
4515 weight,
4516 psp,
4517 synapse_attractivity,
4518 synapse_type,
4519 delay_bursts,
4520 )?;
4521 if count > 0 {
4522 npu.rebuild_synapse_index();
4523 }
4524 Ok(count as usize)
4525 }
4526 "rotator_z" => {
4527 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4528 crate::types::BduError::InvalidArea(format!(
4529 "Source area not found: {}",
4530 src_area_id
4531 ))
4532 })?;
4533 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4534 crate::types::BduError::InvalidArea(format!(
4535 "Destination area not found: {}",
4536 dst_area_id
4537 ))
4538 })?;
4539 let src_dimensions = (
4540 src_area.dimensions.width as usize,
4541 src_area.dimensions.height as usize,
4542 src_area.dimensions.depth as usize,
4543 );
4544 let dst_dimensions = (
4545 dst_area.dimensions.width as usize,
4546 dst_area.dimensions.height as usize,
4547 dst_area.dimensions.depth as usize,
4548 );
4549
4550 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
4551 npu,
4552 src_idx,
4553 dst_idx,
4554 src_dimensions,
4555 dst_dimensions,
4556 weight,
4557 psp,
4558 synapse_attractivity,
4559 synapse_type,
4560 delay_bursts,
4561 )?;
4562 if count > 0 {
4563 npu.rebuild_synapse_index();
4564 }
4565 Ok(count as usize)
4566 }
4567 _ => {
4568 use tracing::debug;
4571 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
4572 Ok(0)
4573 }
4574 }
4575 }
4576
4577 fn apply_single_morphology_rule(
4579 &mut self,
4580 src_area_id: &CorticalID,
4581 dst_area_id: &CorticalID,
4582 rule: &serde_json::Value,
4583 ) -> BduResult<usize> {
4584 let morphology_id = if let Some(arr) = rule.as_array() {
4586 arr.first().and_then(|v| v.as_str()).unwrap_or("")
4587 } else if let Some(obj) = rule.as_object() {
4588 obj.get("morphology_id")
4589 .and_then(|v| v.as_str())
4590 .unwrap_or("")
4591 } else {
4592 return Ok(0);
4593 };
4594
4595 if morphology_id.is_empty() {
4596 return Ok(0);
4597 }
4598
4599 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
4601 crate::types::BduError::InvalidMorphology(format!(
4602 "Morphology not found: {}",
4603 morphology_id
4604 ))
4605 })?;
4606
4607 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
4609 crate::types::BduError::InvalidArea(format!(
4610 "Source area ID not found: {}",
4611 src_area_id
4612 ))
4613 })?;
4614 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
4615 crate::types::BduError::InvalidArea(format!(
4616 "Destination area ID not found: {}",
4617 dst_area_id
4618 ))
4619 })?;
4620
4621 if let Some(ref npu_arc) = self.npu {
4623 let lock_start = std::time::Instant::now();
4624 let mut npu = npu_arc.lock().unwrap();
4625 let lock_wait = lock_start.elapsed();
4626 tracing::debug!(
4627 target: "feagi-bdu",
4628 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
4629 lock_wait.as_secs_f64() * 1000.0,
4630 src_area_id,
4631 dst_area_id,
4632 morphology_id
4633 );
4634
4635 let (weight, psp, synapse_type, delay_bursts) =
4636 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
4637
4638 let synapse_attractivity = if let Some(obj) = rule.as_object() {
4640 obj.get("synapse_attractivity")
4641 .and_then(|v| v.as_u64())
4642 .unwrap_or(100) as u8
4643 } else {
4644 100 };
4646
4647 match morphology.morphology_type {
4648 feagi_evolutionary::MorphologyType::Functions => {
4649 tracing::debug!(
4650 target: "feagi-bdu",
4651 "apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
4652 morphology_id
4653 );
4654 self.apply_function_morphology(
4657 morphology_id,
4658 rule,
4659 npu_arc,
4660 &mut npu,
4661 src_area_id,
4662 dst_area_id,
4663 *src_idx,
4664 *dst_idx,
4665 weight,
4666 psp,
4667 synapse_attractivity,
4668 synapse_type,
4669 delay_bursts,
4670 )
4671 }
4672 feagi_evolutionary::MorphologyType::Vectors => {
4673 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
4674
4675 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4677 crate::types::BduError::InvalidArea(format!(
4678 "Destination area not found: {}",
4679 dst_area_id
4680 ))
4681 })?;
4682
4683 let dst_dimensions = (
4684 dst_area.dimensions.width as usize,
4685 dst_area.dimensions.height as usize,
4686 dst_area.dimensions.depth as usize,
4687 );
4688
4689 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
4690 morphology.parameters
4691 {
4692 let vectors_tuples: Vec<(i32, i32, i32)> =
4694 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
4695
4696 let count = apply_vectors_morphology_with_dimensions(
4697 &mut npu,
4698 *src_idx,
4699 *dst_idx,
4700 vectors_tuples,
4701 dst_dimensions,
4702 weight, psp, synapse_attractivity, synapse_type,
4706 delay_bursts,
4707 )?;
4708 npu.rebuild_synapse_index();
4711 Ok(count as usize)
4712 } else {
4713 Ok(0)
4714 }
4715 }
4716 feagi_evolutionary::MorphologyType::Patterns => {
4717 use crate::connectivity::core_morphologies::apply_patterns_morphology;
4718 use crate::connectivity::rules::patterns::{
4719 Pattern3D, PatternElement as RulePatternElement,
4720 };
4721 use feagi_evolutionary::PatternElement as EvoPatternElement;
4722
4723 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
4724 morphology.parameters
4725 else {
4726 return Ok(0);
4727 };
4728
4729 let convert_element =
4730 |element: &EvoPatternElement|
4731 -> crate::types::BduResult<RulePatternElement> {
4732 match element {
4733 EvoPatternElement::Value(value) => {
4734 if *value < 0 {
4735 return Err(crate::types::BduError::InvalidMorphology(
4736 format!(
4737 "Pattern morphology {} contains negative voxel coordinate {}",
4738 morphology_id, value
4739 ),
4740 ));
4741 }
4742 Ok(RulePatternElement::Exact(*value))
4743 }
4744 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
4745 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
4746 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
4747 EvoPatternElement::DirectionPositive => {
4748 Ok(RulePatternElement::DirectionExclusive(
4749 crate::connectivity::rules::patterns::Direction::Positive,
4750 ))
4751 }
4752 EvoPatternElement::DirectionNegative => {
4753 Ok(RulePatternElement::DirectionExclusive(
4754 crate::connectivity::rules::patterns::Direction::Negative,
4755 ))
4756 }
4757 EvoPatternElement::DirectionPositiveInclusive => {
4758 Ok(RulePatternElement::DirectionInclusive(
4759 crate::connectivity::rules::patterns::Direction::Positive,
4760 ))
4761 }
4762 EvoPatternElement::DirectionNegativeInclusive => {
4763 Ok(RulePatternElement::DirectionInclusive(
4764 crate::connectivity::rules::patterns::Direction::Negative,
4765 ))
4766 }
4767 EvoPatternElement::Offset(off) => {
4768 Ok(RulePatternElement::Offset(*off))
4769 }
4770 EvoPatternElement::Range(lo, hi) => {
4771 Ok(RulePatternElement::Range(*lo, *hi))
4772 }
4773 EvoPatternElement::AbsoluteRange(lo, hi) => {
4774 Ok(RulePatternElement::AbsoluteRange(*lo, *hi))
4775 }
4776 }
4777 };
4778
4779 let mut converted_patterns = Vec::with_capacity(patterns.len());
4780 for pattern_pair in patterns {
4781 if pattern_pair.len() != 2 {
4782 return Err(crate::types::BduError::InvalidMorphology(format!(
4783 "Pattern morphology {} must contain [src, dst] pairs",
4784 morphology_id
4785 )));
4786 }
4787
4788 let src_pattern = &pattern_pair[0];
4789 let dst_pattern = &pattern_pair[1];
4790
4791 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
4792 return Err(crate::types::BduError::InvalidMorphology(format!(
4793 "Pattern morphology {} requires 3-axis patterns",
4794 morphology_id
4795 )));
4796 }
4797
4798 let src: Pattern3D = (
4799 convert_element(&src_pattern[0])?,
4800 convert_element(&src_pattern[1])?,
4801 convert_element(&src_pattern[2])?,
4802 );
4803 let dst: Pattern3D = (
4804 convert_element(&dst_pattern[0])?,
4805 convert_element(&dst_pattern[1])?,
4806 convert_element(&dst_pattern[2])?,
4807 );
4808
4809 converted_patterns.push((src, dst));
4810 }
4811
4812 let count = apply_patterns_morphology(
4813 &mut npu,
4814 *src_idx,
4815 *dst_idx,
4816 converted_patterns,
4817 weight,
4818 psp,
4819 synapse_attractivity,
4820 synapse_type,
4821 delay_bursts,
4822 )?;
4823 if count > 0 {
4824 npu.rebuild_synapse_index();
4825 }
4826 Ok(count as usize)
4827 }
4828 feagi_evolutionary::MorphologyType::Composite => {
4829 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
4830 morphology.parameters
4831 else {
4832 return Ok(0);
4833 };
4834
4835 if morphology_id != "tile" {
4836 use tracing::debug;
4837 debug!(
4838 target: "feagi-bdu",
4839 "Composite morphology {} not yet implemented",
4840 morphology_id
4841 );
4842 return Ok(0);
4843 }
4844
4845 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4846 crate::types::BduError::InvalidArea(format!(
4847 "Source area not found: {}",
4848 src_area_id
4849 ))
4850 })?;
4851 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4852 crate::types::BduError::InvalidArea(format!(
4853 "Destination area not found: {}",
4854 dst_area_id
4855 ))
4856 })?;
4857 let src_dimensions = (
4858 src_area.dimensions.width as usize,
4859 src_area.dimensions.height as usize,
4860 src_area.dimensions.depth as usize,
4861 );
4862 let dst_dimensions = (
4863 dst_area.dimensions.width as usize,
4864 dst_area.dimensions.height as usize,
4865 dst_area.dimensions.depth as usize,
4866 );
4867
4868 let count =
4869 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
4870 &mut npu,
4871 *src_idx,
4872 *dst_idx,
4873 src_dimensions,
4874 dst_dimensions,
4875 weight,
4876 psp,
4877 synapse_attractivity,
4878 synapse_type,
4879 delay_bursts,
4880 )?;
4881 if count > 0 {
4882 npu.rebuild_synapse_index();
4883 }
4884 Ok(count as usize)
4885 }
4886 }
4887 } else {
4888 Ok(0) }
4890 }
4891
4892 pub fn set_npu(
4905 &mut self,
4906 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4907 ) {
4908 self.npu = Some(Arc::clone(&npu));
4909 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
4910
4911 #[cfg(not(feature = "wasm"))]
4914 {
4915 use feagi_state_manager::StateManager;
4916 let state_manager = StateManager::instance();
4917 let state_manager = state_manager.read();
4918 let core_state = state_manager.get_core_state();
4919 core_state.set_neuron_capacity(self.config.max_neurons as u32);
4921 core_state.set_synapse_capacity(self.config.max_synapses as u32);
4922 info!(
4923 target: "feagi-bdu",
4924 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
4925 self.config.max_neurons, self.config.max_synapses
4926 );
4927 }
4928
4929 let existing_area_count = self.cortical_id_to_idx.len();
4936 if existing_area_count > 0 {
4937 match npu.lock() {
4938 Ok(mut npu_lock) => {
4939 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
4940 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
4941 }
4942 info!(
4943 target: "feagi-bdu",
4944 "🔁 Backfilled {} cortical area registrations into NPU",
4945 existing_area_count
4946 );
4947 }
4948 Err(e) => {
4949 warn!(
4950 target: "feagi-bdu",
4951 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
4952 e
4953 );
4954 }
4955 }
4956 }
4957
4958 self.update_all_cached_stats();
4960 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
4961 self.get_neuron_count(), self.get_synapse_count());
4962 }
4963
4964 pub fn has_npu(&self) -> bool {
4966 self.npu.is_some()
4967 }
4968
4969 pub fn get_npu(
4976 &self,
4977 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
4978 {
4979 self.npu.as_ref()
4980 }
4981
4982 pub fn rebind_npu_runtime_after_connectome_apply(&mut self) -> BduResult<()> {
4988 #[cfg(feature = "plasticity")]
4989 if let Some(executor) = self.plasticity_executor.as_ref() {
4990 executor
4991 .lock()
4992 .map_err(|_| {
4993 BduError::Internal(
4994 "Failed to lock PlasticityExecutor for registration reset".to_string(),
4995 )
4996 })?
4997 .clear_memory_area_registrations()
4998 .map_err(BduError::Internal)?;
4999 }
5000 self.refresh_all_upstream_cortical_areas_from_mappings();
5001 self.rebuild_memory_twin_mappings()?;
5002 self.rebind_memory_twin_mappings_to_npu()?;
5003 self.rebind_stdp_mappings_to_npu()?;
5004 #[cfg(feature = "plasticity")]
5005 self.reregister_memory_areas_with_plasticity()?;
5006 Ok(())
5007 }
5008
5009 pub fn refresh_all_upstream_cortical_areas_from_mappings(&mut self) {
5015 let area_ids: Vec<CorticalID> = self.cortical_areas.keys().copied().collect();
5016 for area_id in area_ids {
5017 self.refresh_upstream_cortical_areas_from_mappings(&area_id);
5018 }
5019 }
5020
5021 pub fn rebuild_memory_twin_mappings(&mut self) -> BduResult<usize> {
5031 let jobs = self.collect_memory_twin_rebuild_jobs()?;
5032 let mut restored = 0usize;
5033 for (memory_id, upstream_id, known_twin) in jobs {
5034 match known_twin {
5035 Some(twin_id) => {
5036 self.restore_memory_twin_index(&memory_id, &upstream_id, &twin_id)?;
5037 restored += 1;
5038 }
5039 None => {
5040 self.ensure_memory_twin_area(&memory_id, &upstream_id)?;
5041 restored += 1;
5042 }
5043 }
5044 }
5045 if restored > 0 {
5046 info!(
5047 target: "feagi-bdu",
5048 "Rebuilt {} memory twin mapping(s) after connectome apply",
5049 restored
5050 );
5051 self.refresh_cortical_mappings_hash();
5052 }
5053 self.rebuild_scan_twin_mappings()?;
5054 Ok(restored)
5055 }
5056
5057 fn rebuild_scan_twin_mappings(&mut self) -> BduResult<usize> {
5058 let mut pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
5059 for (src_id, src_area) in &self.cortical_areas {
5060 if Self::area_is_memory(src_area) {
5061 continue;
5062 }
5063 let Some(dstmap) = src_area
5064 .properties
5065 .get("cortical_mapping_dst")
5066 .and_then(|value| value.as_object())
5067 else {
5068 continue;
5069 };
5070 for (dst_b64, rules) in dstmap {
5071 if !Self::mapping_rules_use_morphology(rules, "episodic_scan") {
5072 continue;
5073 }
5074 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
5075 continue;
5076 };
5077 pairs.push((memory_id, *src_id));
5078 }
5079 }
5080 let mut restored = 0usize;
5081 for (memory_id, field_id) in pairs {
5082 if self.ensure_scan_twin_area(&memory_id, &field_id).is_ok() {
5083 restored += 1;
5084 }
5085 }
5086 Ok(restored)
5087 }
5088
5089 fn collect_memory_twin_rebuild_jobs(
5090 &self,
5091 ) -> BduResult<Vec<(CorticalID, CorticalID, Option<CorticalID>)>> {
5092 let mut jobs: Vec<(CorticalID, CorticalID, Option<CorticalID>)> = Vec::new();
5093 let mut seen: HashSet<(CorticalID, CorticalID)> = HashSet::new();
5094
5095 for (memory_id, memory_area) in &self.cortical_areas {
5096 if !Self::area_is_memory(memory_area) {
5097 continue;
5098 }
5099 let Some(dstmap) = memory_area
5100 .properties
5101 .get("cortical_mapping_dst")
5102 .and_then(|value| value.as_object())
5103 else {
5104 continue;
5105 };
5106 for (dst_b64, rules) in dstmap {
5107 if !Self::mapping_rules_use_morphology(rules, "memory_replay") {
5108 continue;
5109 }
5110 let twin_id = match CorticalID::try_from_base_64(dst_b64) {
5111 Ok(id) => id,
5112 Err(_) => continue,
5113 };
5114 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
5115 continue;
5116 };
5117 let Some(upstream_b64) = twin_area
5118 .properties
5119 .get("memory_twin_of")
5120 .and_then(|value| value.as_str())
5121 else {
5122 continue;
5123 };
5124 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5125 BduError::InvalidArea(format!(
5126 "Invalid memory_twin_of '{}' on {}: {}",
5127 upstream_b64,
5128 twin_id.as_base_64(),
5129 error
5130 ))
5131 })?;
5132 if seen.insert((*memory_id, upstream_id)) {
5133 jobs.push((*memory_id, upstream_id, Some(twin_id)));
5134 }
5135 }
5136 }
5137
5138 for (twin_id, twin_area) in &self.cortical_areas {
5139 let Some(upstream_b64) = twin_area
5140 .properties
5141 .get("memory_twin_of")
5142 .and_then(|value| value.as_str())
5143 else {
5144 continue;
5145 };
5146 let Some(memory_b64) = twin_area
5147 .properties
5148 .get("memory_twin_for")
5149 .and_then(|value| value.as_str())
5150 else {
5151 continue;
5152 };
5153 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5154 BduError::InvalidArea(format!(
5155 "Invalid memory_twin_of '{}' on {}: {}",
5156 upstream_b64,
5157 twin_id.as_base_64(),
5158 error
5159 ))
5160 })?;
5161 let memory_id = CorticalID::try_from_base_64(memory_b64).map_err(|error| {
5162 BduError::InvalidArea(format!(
5163 "Invalid memory_twin_for '{}' on {}: {}",
5164 memory_b64,
5165 twin_id.as_base_64(),
5166 error
5167 ))
5168 })?;
5169 if !self.cortical_areas.contains_key(&memory_id)
5170 || !self.cortical_areas.contains_key(&upstream_id)
5171 {
5172 continue;
5173 }
5174 if seen.insert((memory_id, upstream_id)) {
5175 jobs.push((memory_id, upstream_id, Some(*twin_id)));
5176 }
5177 }
5178
5179 let mut episodic_pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
5180 for (src_id, src_area) in &self.cortical_areas {
5181 if Self::area_is_memory(src_area) {
5182 continue;
5183 }
5184 let Some(dstmap) = src_area
5185 .properties
5186 .get("cortical_mapping_dst")
5187 .and_then(|value| value.as_object())
5188 else {
5189 continue;
5190 };
5191 for (dst_b64, rules) in dstmap {
5192 if !Self::mapping_rules_use_morphology(rules, "episodic_memory") {
5193 continue;
5194 }
5195 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
5196 continue;
5197 };
5198 let Some(memory_area) = self.cortical_areas.get(&memory_id) else {
5199 continue;
5200 };
5201 if !Self::area_is_memory(memory_area) {
5202 continue;
5203 }
5204 episodic_pairs.push((memory_id, *src_id));
5205 }
5206 }
5207
5208 for (memory_id, upstream_id) in episodic_pairs {
5209 if !seen.insert((memory_id, upstream_id)) {
5210 continue;
5211 }
5212 let indexed_twin = self
5213 .cortical_areas
5214 .get(&memory_id)
5215 .and_then(|area| area.properties.get("memory_twin_areas"))
5216 .and_then(|value| value.as_object())
5217 .and_then(|map| map.get(&upstream_id.as_base_64()))
5218 .and_then(|value| value.as_str())
5219 .and_then(|twin_b64| CorticalID::try_from_base_64(twin_b64).ok());
5220 if let Some(twin_id) = indexed_twin {
5221 jobs.push((memory_id, upstream_id, Some(twin_id)));
5222 continue;
5223 }
5224 if let Ok(hash_twin_id) = self.build_memory_twin_id(&memory_id, &upstream_id) {
5225 if self.cortical_areas.contains_key(&hash_twin_id) {
5226 jobs.push((memory_id, upstream_id, Some(hash_twin_id)));
5227 continue;
5228 }
5229 }
5230 jobs.push((memory_id, upstream_id, None));
5231 }
5232
5233 Ok(jobs)
5234 }
5235
5236 fn restore_memory_twin_index(
5237 &mut self,
5238 memory_area_id: &CorticalID,
5239 upstream_area_id: &CorticalID,
5240 twin_id: &CorticalID,
5241 ) -> BduResult<()> {
5242 let expected_source = upstream_area_id.as_base_64();
5243 let expected_target = memory_area_id.as_base_64();
5244 let Some(existing) = self.cortical_areas.get_mut(twin_id) else {
5245 return Err(BduError::InvalidArea(format!(
5246 "Twin area {} not found while restoring memory twin index",
5247 twin_id.as_base_64()
5248 )));
5249 };
5250 let existing_source = existing
5251 .properties
5252 .get("memory_twin_of")
5253 .and_then(|value| value.as_str())
5254 .map(ToString::to_string);
5255 let existing_target = existing
5256 .properties
5257 .get("memory_twin_for")
5258 .and_then(|value| value.as_str())
5259 .map(ToString::to_string);
5260 if existing_source.as_deref() != Some(expected_source.as_str())
5261 || existing_target.as_deref() != Some(expected_target.as_str())
5262 {
5263 existing.properties.insert(
5264 "memory_twin_of".to_string(),
5265 serde_json::json!(expected_source),
5266 );
5267 existing.properties.insert(
5268 "memory_twin_for".to_string(),
5269 serde_json::json!(expected_target),
5270 );
5271 }
5272 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, twin_id);
5273
5274 let has_replay = self
5275 .cortical_areas
5276 .get(memory_area_id)
5277 .and_then(|area| area.properties.get("cortical_mapping_dst"))
5278 .and_then(|value| value.as_object())
5279 .and_then(|map| map.get(&twin_id.as_base_64()))
5280 .is_some_and(|rules| Self::mapping_rules_use_morphology(rules, "memory_replay"));
5281 if !has_replay {
5282 self.ensure_memory_replay_mapping(memory_area_id, twin_id)?;
5283 }
5284 Ok(())
5285 }
5286
5287 fn area_is_memory(area: &CorticalArea) -> bool {
5288 matches!(area.cortical_type, CorticalAreaType::Memory(_))
5289 || area
5290 .properties
5291 .get("is_mem_type")
5292 .and_then(|value| value.as_bool())
5293 == Some(true)
5294 }
5295
5296 fn mapping_rules_use_morphology(rules: &serde_json::Value, morphology_id: &str) -> bool {
5297 rules.as_array().is_some_and(|arr| {
5298 arr.iter().any(|rule| {
5299 rule.as_object()
5300 .and_then(|obj| obj.get("morphology_id"))
5301 .and_then(|value| value.as_str())
5302 == Some(morphology_id)
5303 })
5304 })
5305 }
5306
5307 fn rebind_memory_twin_mappings_to_npu(&mut self) -> BduResult<()> {
5308 use crate::models::CorticalAreaExt;
5309
5310 let Some(npu_arc) = self.npu.clone() else {
5311 return Ok(());
5312 };
5313 let mut bindings: Vec<(u32, u32, u32, f32)> = Vec::new();
5314 for (memory_id, area) in &self.cortical_areas {
5315 if !matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
5316 continue;
5317 }
5318 let Some(twins) = area
5319 .properties
5320 .get("memory_twin_areas")
5321 .and_then(|value| value.as_object())
5322 else {
5323 continue;
5324 };
5325 let Some(&memory_idx) = self.cortical_id_to_idx.get(memory_id) else {
5326 continue;
5327 };
5328 for (upstream_b64, twin_val) in twins {
5329 let Some(twin_b64) = twin_val.as_str() else {
5330 return Err(BduError::InvalidArea(format!(
5331 "memory_twin_areas entry for {} is not a string",
5332 upstream_b64
5333 )));
5334 };
5335 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5336 BduError::InvalidArea(format!(
5337 "Invalid upstream cortical ID {}: {}",
5338 upstream_b64, error
5339 ))
5340 })?;
5341 let twin_id = CorticalID::try_from_base_64(twin_b64).map_err(|error| {
5342 BduError::InvalidArea(format!(
5343 "Invalid twin cortical ID {}: {}",
5344 twin_b64, error
5345 ))
5346 })?;
5347 let Some(&upstream_idx) = self.cortical_id_to_idx.get(&upstream_id) else {
5348 continue;
5349 };
5350 let Some(&twin_idx) = self.cortical_id_to_idx.get(&twin_id) else {
5351 continue;
5352 };
5353 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
5354 continue;
5355 };
5356 let potential =
5357 twin_area.firing_threshold() + twin_area.firing_threshold_increment();
5358 bindings.push((memory_idx, upstream_idx, twin_idx, potential));
5359 }
5360 }
5361 let mut npu = npu_arc.lock().unwrap();
5362 for (memory_idx, upstream_idx, twin_idx, potential) in bindings {
5363 npu.register_memory_twin_mapping(memory_idx, upstream_idx, twin_idx, potential);
5364 }
5365 Ok(())
5366 }
5367
5368 fn rebind_stdp_mappings_to_npu(&mut self) -> BduResult<()> {
5369 let Some(npu_arc) = self.npu.clone() else {
5370 return Ok(());
5371 };
5372 let mut jobs: Vec<(CorticalID, CorticalID, u32, u32, Vec<serde_json::Value>)> = Vec::new();
5373 for (src_id, src_area) in &self.cortical_areas {
5374 let Some(dst_map) = src_area
5375 .properties
5376 .get("cortical_mapping_dst")
5377 .and_then(|value| value.as_object())
5378 else {
5379 continue;
5380 };
5381 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
5382 continue;
5383 };
5384 for (dst_b64, rules) in dst_map {
5385 let dst_id = CorticalID::try_from_base_64(dst_b64).map_err(|error| {
5386 BduError::InvalidArea(format!(
5387 "Invalid destination cortical ID {}: {}",
5388 dst_b64, error
5389 ))
5390 })?;
5391 let Some(&dst_idx) = self.cortical_id_to_idx.get(&dst_id) else {
5392 continue;
5393 };
5394 let Some(rules_arr) = rules.as_array() else {
5395 continue;
5396 };
5397 jobs.push((*src_id, dst_id, src_idx, dst_idx, rules_arr.clone()));
5398 }
5399 }
5400
5401 for (src_id, dst_id, src_idx, dst_idx, rules) in jobs {
5402 for rule in &rules {
5403 let morphology_id = rule
5404 .as_object()
5405 .and_then(|obj| obj.get("morphology_id"))
5406 .and_then(|value| value.as_str())
5407 .unwrap_or("");
5408 let mut plasticity_flag = rule
5409 .as_object()
5410 .and_then(|obj| obj.get("plasticity_flag"))
5411 .and_then(|value| value.as_bool())
5412 .unwrap_or(false);
5413 if morphology_id == "associative_memory" {
5414 plasticity_flag = true;
5415 }
5416 if !plasticity_flag {
5417 continue;
5418 }
5419 let Some(rule_obj) = rule.as_object() else {
5420 return Err(BduError::InvalidMorphology(
5421 "Plasticity mapping rule must be an object format".to_string(),
5422 ));
5423 };
5424 let (_weight, psp, synapse_type, _delay) =
5425 self.resolve_synapse_params_for_rule(&src_id, rule)?;
5426 Self::register_stdp_mapping_for_rule(
5427 &npu_arc,
5428 &src_id,
5429 &dst_id,
5430 src_idx,
5431 dst_idx,
5432 rule_obj,
5433 false,
5434 psp,
5435 synapse_type,
5436 )?;
5437 }
5438 }
5439 Ok(())
5440 }
5441
5442 #[cfg(feature = "plasticity")]
5443 fn reregister_memory_areas_with_plasticity(&mut self) -> BduResult<()> {
5444 use feagi_evolutionary::extract_memory_properties;
5445 use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
5446
5447 let Some(executor) = self.plasticity_executor.clone() else {
5448 return Ok(());
5449 };
5450 let memory_ids: Vec<CorticalID> = self
5451 .cortical_areas
5452 .iter()
5453 .filter(|(_, area)| matches!(area.cortical_type, CorticalAreaType::Memory(_)))
5454 .map(|(id, _)| *id)
5455 .collect();
5456
5457 for memory_id in memory_ids {
5458 let Some(area) = self.cortical_areas.get(&memory_id) else {
5459 continue;
5460 };
5461 let Some(mem_props) = extract_memory_properties(&area.properties) else {
5462 continue;
5463 };
5464 let Some(&area_idx) = self.cortical_id_to_idx.get(&memory_id) else {
5465 continue;
5466 };
5467 let area_name = memory_id.as_base_64();
5468 let upstream_areas = self.get_episodic_memory_upstream_cortical_areas(&memory_id);
5469 let lifecycle_config = MemoryNeuronLifecycleConfig {
5470 initial_lifespan: mem_props.init_lifespan,
5471 lifespan_growth_rate: mem_props.lifespan_growth_rate,
5472 longterm_threshold: mem_props.longterm_threshold,
5473 max_reactivations: 1000,
5474 };
5475 let exec = executor.lock().map_err(|_| {
5476 BduError::Internal(
5477 "Failed to lock PlasticityExecutor for memory-area rebind".to_string(),
5478 )
5479 })?;
5480 exec.register_memory_area(
5481 area_idx,
5482 area_name,
5483 mem_props.temporal_depth,
5484 upstream_areas,
5485 Some(lifecycle_config),
5486 mem_props.mp_learning_enabled,
5487 );
5488 self.configure_memory_scan_on_executor(&*exec, &memory_id);
5489 }
5490 Ok(())
5491 }
5492
5493 #[cfg(feature = "plasticity")]
5494 pub(crate) fn configure_memory_scan_on_executor(
5495 &self,
5496 exec: &dyn feagi_npu_plasticity::PlasticityExecutor,
5497 memory_id: &CorticalID,
5498 ) {
5499 let Some(scan) = self.build_memory_scan_config(memory_id) else {
5500 if let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) {
5501 exec.configure_memory_scan(area_idx, None);
5502 }
5503 return;
5504 };
5505 let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) else {
5506 return;
5507 };
5508 exec.configure_memory_scan(area_idx, Some(scan));
5509 }
5510
5511 #[cfg(feature = "plasticity")]
5512 fn build_memory_scan_config(
5513 &self,
5514 memory_id: &CorticalID,
5515 ) -> Option<feagi_npu_plasticity::MemoryScanConfig> {
5516 use feagi_evolutionary::extract_memory_properties;
5517 use feagi_npu_plasticity::{MemoryScanConfig, MemoryScanSource, ScanKernel};
5518
5519 let memory_area = self.cortical_areas.get(memory_id)?;
5520 let mem_props = extract_memory_properties(&memory_area.properties)?;
5521 let kernel_b64 = memory_area
5522 .properties
5523 .get("classifier_kernel_area_id")
5524 .and_then(|v| v.as_str())?;
5525 let class_b64 = memory_area
5526 .properties
5527 .get("classifier_class_area_id")
5528 .and_then(|v| v.as_str())?;
5529 let class_mem_b64 = memory_area
5530 .properties
5531 .get("classifier_class_memory_id")
5532 .and_then(|v| v.as_str())?;
5533 let kernel_id = CorticalID::try_from_base_64(kernel_b64).ok()?;
5534 let class_id = CorticalID::try_from_base_64(class_b64).ok()?;
5535 let class_mem_id = CorticalID::try_from_base_64(class_mem_b64).ok()?;
5536 if !self.mapping_from_src_to_dst_has_associative(memory_id, &class_mem_id) {
5537 return None;
5538 }
5539 let kernel_area = self.cortical_areas.get(&kernel_id)?;
5540 let class_area = self.cortical_areas.get(&class_id)?;
5541 let class_memory_area_idx = *self.cortical_id_to_idx.get(&class_mem_id)?;
5542 let class_channel_count = class_area
5543 .dimensions
5544 .width
5545 .saturating_mul(class_area.dimensions.height)
5546 .saturating_mul(class_area.dimensions.depth);
5547 if class_channel_count == 0 {
5548 return None;
5549 }
5550 let scan_fields = self.get_episodic_scan_upstream_cortical_areas(memory_id);
5551 if scan_fields.is_empty() {
5552 return None;
5553 }
5554 let twin_map = memory_area
5555 .properties
5556 .get("memory_twin_areas")
5557 .and_then(|v| v.as_object())?;
5558 let mut sources = Vec::new();
5559 for field_idx in scan_fields {
5560 let field_id = *self.cortical_idx_to_id.get(&field_idx)?;
5561 let field_area = self.cortical_areas.get(&field_id)?;
5562 let twin_b64 = twin_map.get(&field_id.as_base_64())?.as_str()?;
5563 let twin_id = CorticalID::try_from_base_64(twin_b64).ok()?;
5564 let twin_idx = *self.cortical_id_to_idx.get(&twin_id)?;
5565 sources.push(MemoryScanSource {
5566 field_area_idx: field_idx,
5567 twin_area_idx: twin_idx,
5568 field_width: field_area.dimensions.width,
5569 field_height: field_area.dimensions.height,
5570 field_depth: field_area.dimensions.depth,
5571 });
5572 }
5573 if sources.is_empty() {
5574 return None;
5575 }
5576 Some(MemoryScanConfig {
5577 kernel: ScanKernel {
5578 width: kernel_area.dimensions.width,
5579 height: kernel_area.dimensions.height,
5580 depth: kernel_area.dimensions.depth,
5581 },
5582 min_window_activity: mem_props.min_window_activity,
5583 scan_skip_density: mem_props.scan_skip_density,
5584 class_channel_count,
5585 class_area_width: class_area.dimensions.width,
5586 class_area_height: class_area.dimensions.height,
5587 class_memory_area_idx,
5588 sources,
5589 })
5590 }
5591
5592 #[cfg(feature = "plasticity")]
5593 fn mapping_from_src_to_dst_has_associative(
5594 &self,
5595 src_area_id: &CorticalID,
5596 dst_area_id: &CorticalID,
5597 ) -> bool {
5598 let Some(src_area) = self.cortical_areas.get(src_area_id) else {
5599 return false;
5600 };
5601 let Some(mapping_dst) = src_area
5602 .properties
5603 .get("cortical_mapping_dst")
5604 .and_then(|v| v.as_object())
5605 else {
5606 return false;
5607 };
5608 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
5609 return false;
5610 };
5611 Self::mapping_has_morphology_rule(rules, "associative_memory")
5612 }
5613
5614 #[cfg(feature = "plasticity")]
5617 pub fn set_plasticity_executor(
5618 &mut self,
5619 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
5620 ) {
5621 if let Ok(mut exec) = executor.lock() {
5622 use feagi_npu_plasticity::executor::PlasticityExecutor;
5623 if !exec.is_running() {
5624 exec.start();
5625 }
5626 } else {
5627 warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
5628 }
5629 self.plasticity_executor = Some(executor);
5630 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
5631 }
5632
5633 #[cfg(feature = "plasticity")]
5635 pub fn get_plasticity_executor(
5636 &self,
5637 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
5638 self.plasticity_executor.as_ref()
5639 }
5640
5641 pub fn get_neuron_capacity(&self) -> usize {
5653 self.config.max_neurons
5655 }
5656
5657 pub fn get_synapse_capacity(&self) -> usize {
5669 self.config.max_synapses
5671 }
5672
5673 pub fn update_fatigue_index(&self) -> Option<u8> {
5688 let mut last_calc = match self.last_fatigue_calculation.lock() {
5690 Ok(guard) => guard,
5691 Err(_) => return None, };
5693
5694 let now = std::time::Instant::now();
5695 if now.duration_since(*last_calc).as_secs() < 2 {
5696 return None; }
5698 *last_calc = now;
5699 drop(last_calc);
5700
5701 let regular_neuron_count = self.get_neuron_count();
5703 let regular_neuron_capacity = self.get_neuron_capacity();
5704 let regular_neuron_util = if regular_neuron_capacity > 0 {
5705 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
5706 } else {
5707 0
5708 };
5709
5710 let memory_neuron_util = match StateManager::instance().try_read() {
5714 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
5715 None => {
5716 return None;
5718 }
5719 };
5720
5721 let synapse_count = self.get_synapse_count();
5723 let synapse_capacity = self.get_synapse_capacity();
5724 let synapse_util = if synapse_capacity > 0 {
5725 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
5726 } else {
5727 0
5728 };
5729
5730 let fatigue_index = regular_neuron_util
5732 .max(memory_neuron_util)
5733 .max(synapse_util);
5734
5735 let current_fatigue_active = {
5737 StateManager::instance()
5739 .try_read()
5740 .map(|m| m.get_core_state().is_fatigue_active())
5741 .unwrap_or(false)
5742 };
5743
5744 let new_fatigue_active = if fatigue_index >= 85 {
5745 true
5746 } else if fatigue_index < 80 {
5747 false
5748 } else {
5749 current_fatigue_active };
5751
5752 if let Some(state_manager) = StateManager::instance().try_write() {
5756 let core_state = state_manager.get_core_state();
5757 core_state.set_fatigue_index(fatigue_index);
5758 core_state.set_fatigue_active(new_fatigue_active);
5759 core_state.set_regular_neuron_util(regular_neuron_util);
5760 core_state.set_memory_neuron_util(memory_neuron_util);
5761 core_state.set_synapse_util(synapse_util);
5762 } else {
5763 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
5765 }
5766
5767 if let Some(ref npu) = self.npu {
5769 if let Ok(mut npu_lock) = npu.lock() {
5770 npu_lock.set_fatigue_active(new_fatigue_active);
5771 }
5772 }
5773
5774 trace!(
5775 target: "feagi-bdu",
5776 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
5777 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
5778 );
5779
5780 Some(fatigue_index)
5781 }
5782
5783 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
5800 let area = self
5802 .cortical_areas
5803 .get(cortical_id)
5804 .ok_or_else(|| {
5805 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5806 })?
5807 .clone();
5808
5809 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
5811 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
5812 })?;
5813
5814 let npu = self
5816 .npu
5817 .as_ref()
5818 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5819
5820 use crate::models::CorticalAreaExt;
5823 let per_voxel_cnt = area.neurons_per_voxel();
5824 let firing_threshold = area.firing_threshold();
5825 let firing_threshold_increment_x = area.firing_threshold_increment_x();
5826 let firing_threshold_increment_y = area.firing_threshold_increment_y();
5827 let firing_threshold_increment_z = area.firing_threshold_increment_z();
5828 let firing_threshold_limit_raw = area.firing_threshold_limit();
5830 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
5831 f32::MAX } else {
5833 firing_threshold_limit_raw
5834 };
5835
5836 if firing_threshold_increment_x != 0.0
5838 || firing_threshold_increment_y != 0.0
5839 || firing_threshold_increment_z != 0.0
5840 {
5841 info!(
5842 target: "feagi-bdu",
5843 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
5844 cortical_id.as_base_64(),
5845 firing_threshold_increment_x,
5846 firing_threshold_increment_y,
5847 firing_threshold_increment_z
5848 );
5849 } else {
5850 if area.properties.contains_key("firing_threshold_increment_x")
5852 || area.properties.contains_key("firing_threshold_increment_y")
5853 || area.properties.contains_key("firing_threshold_increment_z")
5854 {
5855 info!(
5856 target: "feagi-bdu",
5857 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
5858 cortical_id.as_base_64(),
5859 area.properties.get("firing_threshold_increment_x"),
5860 area.properties.get("firing_threshold_increment_y"),
5861 area.properties.get("firing_threshold_increment_z")
5862 );
5863 }
5864 }
5865
5866 let leak_coefficient = area.leak_coefficient();
5867 let excitability = area.neuron_excitability();
5868 let refractory_period = area.refractory_period();
5869 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
5871 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
5872 u16::MAX } else {
5874 consecutive_fire_limit_raw
5875 };
5876 let snooze_length = area.snooze_period();
5877 let mp_charge_accumulation = area.mp_charge_accumulation();
5878
5879 let voxels = area.dimensions.width as usize
5881 * area.dimensions.height as usize
5882 * area.dimensions.depth as usize;
5883 let expected_neurons = voxels * per_voxel_cnt as usize;
5884
5885 trace!(
5886 target: "feagi-bdu",
5887 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
5888 cortical_id.as_base_64(),
5889 area.dimensions.width,
5890 area.dimensions.height,
5891 area.dimensions.depth,
5892 per_voxel_cnt,
5893 expected_neurons
5894 );
5895
5896 let neuron_count: u32 = {
5901 let mut npu_lock = npu
5902 .lock()
5903 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5904 npu_lock
5905 .create_cortical_area_neurons(
5906 *cortical_idx,
5907 area.dimensions.width,
5908 area.dimensions.height,
5909 area.dimensions.depth,
5910 per_voxel_cnt,
5911 firing_threshold,
5912 firing_threshold_increment_x,
5913 firing_threshold_increment_y,
5914 firing_threshold_increment_z,
5915 firing_threshold_limit,
5916 leak_coefficient,
5917 0.0, 0, refractory_period,
5920 excitability,
5921 consecutive_fire_limit,
5922 snooze_length,
5923 mp_charge_accumulation,
5924 )
5925 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
5926 };
5927
5928 trace!(
5929 target: "feagi-bdu",
5930 "Created {} neurons for area {} via NPU",
5931 neuron_count,
5932 cortical_id.as_base_64()
5933 );
5934
5935 {
5938 let mut cache = self.cached_neuron_counts_per_area.write();
5939 cache
5940 .entry(*cortical_id)
5941 .or_insert_with(|| AtomicUsize::new(0))
5942 .store(neuron_count as usize, Ordering::Relaxed);
5943 }
5944
5945 let state_manager = StateManager::instance();
5947 let state_manager = state_manager.read();
5948 state_manager
5949 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
5950
5951 self.cached_neuron_count
5953 .fetch_add(neuron_count as usize, Ordering::Relaxed);
5954
5955 let state_manager = StateManager::instance();
5957 let state_manager = state_manager.read();
5958 let core_state = state_manager.get_core_state();
5959 core_state.add_neuron_count(neuron_count);
5960 core_state.add_regular_neuron_count(neuron_count);
5961
5962 if let Some(npu) = &self.npu {
5964 if let Ok(mut npl) = npu.lock() {
5965 if let Some(v) = area.properties.get("rate_modulated_leak") {
5966 use crate::models::CorticalAreaExt;
5967 let idxs: Vec<usize> = npl
5968 .get_neurons_in_cortical_area(*cortical_idx)
5969 .into_iter()
5970 .map(|id| id as usize)
5971 .collect();
5972 npl.sync_rate_modulated_leak_from_cortical_property(
5973 *cortical_idx,
5974 v,
5975 area.leak_coefficient(),
5976 idxs,
5977 );
5978 } else {
5979 npl.remove_rate_modulated_leak(*cortical_idx);
5980 }
5981 }
5982 }
5983
5984 Ok(neuron_count)
5992 }
5993
5994 #[allow(clippy::too_many_arguments)]
6018 pub fn add_neuron(
6019 &mut self,
6020 cortical_id: &CorticalID,
6021 x: u32,
6022 y: u32,
6023 z: u32,
6024 firing_threshold: f32,
6025 firing_threshold_limit: f32,
6026 leak_coefficient: f32,
6027 resting_potential: f32,
6028 neuron_type: u8,
6029 refractory_period: u16,
6030 excitability: f32,
6031 consecutive_fire_limit: u16,
6032 snooze_length: u16,
6033 mp_charge_accumulation: bool,
6034 ) -> BduResult<u64> {
6035 self.add_neuron_with_area_stats(
6036 cortical_id,
6037 x,
6038 y,
6039 z,
6040 firing_threshold,
6041 firing_threshold_limit,
6042 leak_coefficient,
6043 resting_potential,
6044 neuron_type,
6045 refractory_period,
6046 excitability,
6047 consecutive_fire_limit,
6048 snooze_length,
6049 mp_charge_accumulation,
6050 true,
6051 )
6052 }
6053
6054 #[allow(clippy::too_many_arguments)]
6060 pub fn add_auxiliary_neuron(
6061 &mut self,
6062 cortical_id: &CorticalID,
6063 x: u32,
6064 y: u32,
6065 z: u32,
6066 firing_threshold: f32,
6067 firing_threshold_limit: f32,
6068 leak_coefficient: f32,
6069 resting_potential: f32,
6070 neuron_type: u8,
6071 refractory_period: u16,
6072 excitability: f32,
6073 consecutive_fire_limit: u16,
6074 snooze_length: u16,
6075 mp_charge_accumulation: bool,
6076 ) -> BduResult<u64> {
6077 self.add_neuron_with_area_stats(
6078 cortical_id,
6079 x,
6080 y,
6081 z,
6082 firing_threshold,
6083 firing_threshold_limit,
6084 leak_coefficient,
6085 resting_potential,
6086 neuron_type,
6087 refractory_period,
6088 excitability,
6089 consecutive_fire_limit,
6090 snooze_length,
6091 mp_charge_accumulation,
6092 false,
6093 )
6094 }
6095
6096 #[allow(clippy::too_many_arguments)]
6097 fn add_neuron_with_area_stats(
6098 &mut self,
6099 cortical_id: &CorticalID,
6100 x: u32,
6101 y: u32,
6102 z: u32,
6103 firing_threshold: f32,
6104 firing_threshold_limit: f32,
6105 leak_coefficient: f32,
6106 resting_potential: f32,
6107 neuron_type: u8,
6108 refractory_period: u16,
6109 excitability: f32,
6110 consecutive_fire_limit: u16,
6111 snooze_length: u16,
6112 mp_charge_accumulation: bool,
6113 update_area_stats: bool,
6114 ) -> BduResult<u64> {
6115 if !self.cortical_areas.contains_key(cortical_id) {
6117 return Err(BduError::InvalidArea(format!(
6118 "Cortical area {} not found",
6119 cortical_id
6120 )));
6121 }
6122
6123 let cortical_idx = *self
6124 .cortical_id_to_idx
6125 .get(cortical_id)
6126 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
6127
6128 let npu = self
6130 .npu
6131 .as_ref()
6132 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6133
6134 let mut npu_lock = npu
6135 .lock()
6136 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6137
6138 let neuron_id = npu_lock
6140 .add_neuron(
6141 firing_threshold,
6142 firing_threshold_limit,
6143 leak_coefficient,
6144 resting_potential,
6145 neuron_type as i32,
6146 refractory_period,
6147 excitability,
6148 consecutive_fire_limit,
6149 snooze_length,
6150 mp_charge_accumulation,
6151 cortical_idx,
6152 x,
6153 y,
6154 z,
6155 )
6156 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
6157
6158 trace!(
6159 target: "feagi-bdu",
6160 "Created neuron {} in area {} at ({}, {}, {})",
6161 neuron_id.0,
6162 cortical_id,
6163 x,
6164 y,
6165 z
6166 );
6167
6168 let state_manager = StateManager::instance();
6170 let state_manager = state_manager.read();
6171 let core_state = state_manager.get_core_state();
6172 core_state.add_neuron_count(1);
6173 core_state.add_regular_neuron_count(1);
6174 if update_area_stats {
6175 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
6176 }
6177
6178 Ok(neuron_id.0 as u64)
6179 }
6180
6181 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
6192 let npu = self
6194 .npu
6195 .as_ref()
6196 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6197
6198 let mut npu_lock = npu
6199 .lock()
6200 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6201
6202 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
6203 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6204
6205 let deleted = npu_lock.delete_neuron(neuron_id as u32);
6206
6207 if deleted {
6208 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
6209
6210 let state_manager = StateManager::instance();
6212 let state_manager = state_manager.read();
6213 let core_state = state_manager.get_core_state();
6214 core_state.subtract_neuron_count(1);
6215 core_state.subtract_regular_neuron_count(1);
6216 if let Some(cortical_id) = cortical_id {
6217 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
6218 }
6219
6220 }
6224
6225 Ok(deleted)
6226 }
6227
6228 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
6242 let src_area = self
6244 .cortical_areas
6245 .get(src_cortical_id)
6246 .ok_or_else(|| {
6247 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
6248 })?
6249 .clone();
6250
6251 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
6253 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
6254 _ => return Ok(0), };
6256
6257 let src_cortical_idx = *self
6258 .cortical_id_to_idx
6259 .get(src_cortical_id)
6260 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
6261
6262 let mut total_synapses = 0u32;
6263 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
6267 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
6269 Ok(id) => id,
6270 Err(_) => {
6271 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
6272 continue;
6273 }
6274 };
6275
6276 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
6278 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
6279 continue;
6280 }
6281
6282 let synapse_count =
6284 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
6285 total_synapses += synapse_count as u32;
6286
6287 upstream_updates.push((dst_cortical_id, src_cortical_idx));
6290 }
6291
6292 for (dst_id, src_idx) in upstream_updates {
6294 self.add_upstream_area(&dst_id, src_idx);
6295 }
6296
6297 trace!(
6298 target: "feagi-bdu",
6299 "Created {} synapses for area {} via NPU",
6300 total_synapses,
6301 src_cortical_id
6302 );
6303
6304 if total_synapses > 0 {
6307 let mut cache = self.cached_synapse_counts_per_area.write();
6308 cache
6309 .entry(*src_cortical_id)
6310 .or_insert_with(|| AtomicUsize::new(0))
6311 .fetch_add(total_synapses as usize, Ordering::Relaxed);
6312 }
6313
6314 self.cached_synapse_count
6316 .fetch_add(total_synapses as usize, Ordering::Relaxed);
6317
6318 if total_synapses > 0 {
6320 let state_manager = StateManager::instance();
6321 let state_manager = state_manager.read();
6322 let core_state = state_manager.get_core_state();
6323 core_state.add_synapse_count(total_synapses);
6324 }
6325
6326 Ok(total_synapses)
6327 }
6328
6329 pub fn has_neuron(&self, neuron_id: u64) -> bool {
6348 if let Some(ref npu) = self.npu {
6349 if let Ok(npu_lock) = npu.lock() {
6350 npu_lock.is_neuron_valid(neuron_id as u32)
6352 } else {
6353 false
6354 }
6355 } else {
6356 false
6357 }
6358 }
6359
6360 pub fn get_neuron_count(&self) -> usize {
6372 if let Some(ref npu) = self.npu {
6374 if let Ok(npu_lock) = npu.try_lock() {
6375 let fresh_count = npu_lock.get_neuron_count();
6376 self.cached_neuron_count
6377 .store(fresh_count, Ordering::Relaxed);
6378 }
6379 }
6381
6382 self.cached_neuron_count.load(Ordering::Relaxed)
6384 }
6385
6386 pub fn update_cached_neuron_count(&self) {
6392 if let Some(ref npu) = self.npu {
6393 if let Ok(npu_lock) = npu.try_lock() {
6394 let count = npu_lock.get_neuron_count();
6395 self.cached_neuron_count.store(count, Ordering::Relaxed);
6396 }
6397 }
6398 }
6399
6400 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
6404 let npu = self.npu.as_ref()?;
6405 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
6406 let npu_lock = npu.lock().ok()?;
6407 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
6408 drop(npu_lock);
6409
6410 let mut cache = self.cached_neuron_counts_per_area.write();
6411 cache
6412 .entry(*cortical_id)
6413 .or_insert_with(|| AtomicUsize::new(0))
6414 .store(count, Ordering::Relaxed);
6415
6416 let state_manager = StateManager::instance();
6418 let state_manager = state_manager.read();
6419 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
6420
6421 self.update_cached_neuron_count();
6422
6423 Some(count)
6424 }
6425
6426 pub fn get_synapse_count(&self) -> usize {
6438 if let Some(ref npu) = self.npu {
6440 if let Ok(npu_lock) = npu.try_lock() {
6441 let fresh_count = npu_lock.get_synapse_count();
6442 self.cached_synapse_count
6443 .store(fresh_count, Ordering::Relaxed);
6444 }
6445 }
6447
6448 self.cached_synapse_count.load(Ordering::Relaxed)
6450 }
6451
6452 pub fn update_cached_synapse_count(&self) {
6458 if let Some(ref npu) = self.npu {
6459 if let Ok(npu_lock) = npu.try_lock() {
6460 let count = npu_lock.get_synapse_count();
6461 self.cached_synapse_count.store(count, Ordering::Relaxed);
6462 }
6463 }
6464 }
6465
6466 pub fn update_all_cached_stats(&self) {
6472 self.update_cached_neuron_count();
6473 self.update_cached_synapse_count();
6474 }
6475
6476 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
6487 #[cfg(feature = "plasticity")]
6492 {
6493 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
6494 return (0, 0, 0);
6495 }
6496 }
6497 if let Some(ref npu) = self.npu {
6498 if let Ok(npu_lock) = npu.lock() {
6499 npu_lock
6500 .get_neuron_coordinates(neuron_id as u32)
6501 .unwrap_or((0, 0, 0))
6502 } else {
6503 (0, 0, 0)
6504 }
6505 } else {
6506 (0, 0, 0)
6507 }
6508 }
6509
6510 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
6521 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
6522 }
6523
6524 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
6530 #[cfg(feature = "plasticity")]
6531 {
6532 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
6533 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
6534 }
6535 }
6536 if let Some(ref npu) = self.npu {
6537 if let Ok(npu_lock) = npu.lock() {
6538 npu_lock.get_neuron_cortical_area(neuron_id as u32)
6539 } else {
6540 None
6541 }
6542 } else {
6543 None
6544 }
6545 }
6546
6547 #[cfg(feature = "plasticity")]
6549 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
6550 let exec = self.get_plasticity_executor()?;
6551 let guard = exec.lock().ok()?;
6552 guard
6553 .memory_neuron_detail(neuron_id)
6554 .map(|d| d.cortical_area_idx)
6555 }
6556
6557 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6568 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
6570 Some(idx) => *idx,
6571 None => return Vec::new(),
6572 };
6573
6574 if let Some(ref npu) = self.npu {
6575 if let Ok(npu_lock) = npu.lock() {
6576 npu_lock
6578 .get_neurons_in_cortical_area(cortical_idx)
6579 .into_iter()
6580 .map(|id| id as u64)
6581 .collect()
6582 } else {
6583 Vec::new()
6584 }
6585 } else {
6586 Vec::new()
6587 }
6588 }
6589
6590 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
6601 if let Some(ref npu) = self.npu {
6602 if let Ok(npu_lock) = npu.lock() {
6603 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
6604 } else {
6605 Vec::new()
6606 }
6607 } else {
6608 Vec::new()
6609 }
6610 }
6611
6612 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
6623 if let Some(ref npu) = self.npu {
6624 if let Ok(npu_lock) = npu.lock() {
6625 npu_lock.get_incoming_synapses(target_neuron_id as u32)
6626 } else {
6627 Vec::new()
6628 }
6629 } else {
6630 Vec::new()
6631 }
6632 }
6633
6634 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6660 let is_memory_area = self
6661 .cortical_areas
6662 .get(cortical_id)
6663 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
6664 .is_some();
6665
6666 if is_memory_area {
6667 #[cfg(feature = "plasticity")]
6671 if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
6672 return count;
6673 }
6674
6675 return StateManager::instance()
6677 .try_read()
6678 .and_then(|state_manager| {
6679 state_manager
6680 .get_cortical_area_stats(&cortical_id.as_base_64())
6681 .map(|stats| stats.neuron_count)
6682 })
6683 .unwrap_or(0);
6684 }
6685
6686 let cache = self.cached_neuron_counts_per_area.read();
6688 cache
6689 .get(cortical_id)
6690 .map(|count| count.load(Ordering::Relaxed))
6691 .unwrap_or(0)
6692 }
6693
6694 #[cfg(feature = "plasticity")]
6696 fn active_memory_neuron_count_from_plasticity(
6697 &self,
6698 cortical_id: &CorticalID,
6699 ) -> Option<usize> {
6700 let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
6701 let exec = self.get_plasticity_executor()?;
6702 let guard = exec.lock().ok()?;
6703 let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
6704 Some(runtime.active_memory_neuron_count())
6705 }
6706
6707 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
6714 let mut result = Vec::new();
6715
6716 for cortical_id in self.cortical_areas.keys() {
6717 let count = self.get_neuron_count_in_area(cortical_id);
6718 if count > 0 {
6719 result.push((cortical_id.to_string(), count));
6720 }
6721 }
6722
6723 result
6724 }
6725
6726 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
6737 self.get_neuron_count_in_area(cortical_id) > 0
6738 }
6739
6740 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6756 let cache = self.cached_synapse_counts_per_area.read();
6758 cache
6759 .get(cortical_id)
6760 .map(|count| count.load(Ordering::Relaxed))
6761 .unwrap_or(0)
6762 }
6763
6764 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6774 if !self.cortical_id_to_idx.contains_key(cortical_id) {
6775 return 0;
6776 }
6777
6778 if let Some(state_manager) = StateManager::instance().try_read() {
6779 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
6780 return stats.incoming_synapse_count;
6781 }
6782 }
6783
6784 0
6785 }
6786
6787 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6797 if !self.cortical_id_to_idx.contains_key(cortical_id) {
6798 return 0;
6799 }
6800
6801 if let Some(state_manager) = StateManager::instance().try_read() {
6802 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
6803 return stats.outgoing_synapse_count;
6804 }
6805 }
6806
6807 0
6808 }
6809
6810 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
6822 let synapses = self.get_outgoing_synapses(source_neuron_id);
6823 synapses
6824 .iter()
6825 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
6826 }
6827
6828 pub fn get_connection_weight(
6840 &self,
6841 source_neuron_id: u64,
6842 target_neuron_id: u64,
6843 ) -> Option<f32> {
6844 let synapses = self.get_outgoing_synapses(source_neuron_id);
6845 synapses
6846 .iter()
6847 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
6848 .map(|(_, weight, _, _)| *weight)
6849 }
6850
6851 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
6862 let neurons = self.get_neurons_in_area(cortical_id);
6863 let neuron_count = neurons.len();
6864
6865 if neuron_count == 0 {
6866 return (0, 0, 0.0);
6867 }
6868
6869 let mut total_synapses = 0;
6870 for neuron_id in neurons {
6871 total_synapses += self.get_outgoing_synapses(neuron_id).len();
6872 }
6873
6874 let avg_synapses = total_synapses as f32 / neuron_count as f32;
6875
6876 (neuron_count, total_synapses, avg_synapses)
6877 }
6878
6879 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
6890 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
6891 self.cortical_idx_to_id.get(&cortical_idx).copied()
6892 }
6893
6894 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
6905 let area = match self.cortical_areas.get(cortical_id) {
6906 Some(a) => a,
6907 None => return 0.0,
6908 };
6909
6910 let neuron_count = self.get_neuron_count_in_area(cortical_id);
6911 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
6912
6913 if volume == 0 {
6914 return 0.0;
6915 }
6916
6917 neuron_count as f32 / volume as f32
6918 }
6919
6920 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
6927 let mut stats = Vec::new();
6928
6929 for cortical_id in self.cortical_areas.keys() {
6930 let neuron_count = self.get_neuron_count_in_area(cortical_id);
6931 let synapse_count = self.get_synapse_count_in_area(cortical_id);
6932 let density = self.get_neuron_density(cortical_id);
6933
6934 stats.push((
6935 cortical_id.to_string(),
6936 neuron_count,
6937 synapse_count,
6938 density,
6939 ));
6940 }
6941
6942 stats
6943 }
6944
6945 pub fn get_config(&self) -> &ConnectomeConfig {
6951 &self.config
6952 }
6953
6954 pub fn set_config(&mut self, config: ConnectomeConfig) {
6956 self.config = config;
6957 }
6958
6959 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
6982 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
6983
6984 use feagi_structures::genomic::cortical_area::{
6985 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
6986 };
6987
6988 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
6990 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
6991 })?;
6992
6993 let core_position = (0, 0, 0).into();
6995
6996 let death_id = CoreCorticalType::Death.to_cortical_id();
6998 if !self.cortical_areas.contains_key(&death_id) {
6999 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
7000 let death_area = CorticalArea::new(
7001 death_id,
7002 0, "_death".to_string(),
7004 core_dimensions,
7005 core_position,
7006 CorticalAreaType::Core(CoreCorticalType::Death),
7007 )
7008 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
7009 match self.add_cortical_area(death_area) {
7010 Ok(idx) => {
7011 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
7012 }
7013 Err(e) => {
7014 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
7015 return Err(e);
7016 }
7017 }
7018 } else {
7019 info!(target: "feagi-bdu", " ✓ _death area already exists");
7020 }
7021
7022 let power_id = CoreCorticalType::Power.to_cortical_id();
7024 if !self.cortical_areas.contains_key(&power_id) {
7025 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
7026 let power_area = CorticalArea::new(
7027 power_id,
7028 1, "_power".to_string(),
7030 core_dimensions,
7031 core_position,
7032 CorticalAreaType::Core(CoreCorticalType::Power),
7033 )
7034 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
7035 match self.add_cortical_area(power_area) {
7036 Ok(idx) => {
7037 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
7038 }
7039 Err(e) => {
7040 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
7041 return Err(e);
7042 }
7043 }
7044 } else {
7045 info!(target: "feagi-bdu", " ✓ _power area already exists");
7046 }
7047
7048 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
7050 let pain_id = CoreCorticalType::Pain.to_cortical_id();
7051 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
7052 let fear_id = CoreCorticalType::Fear.to_cortical_id();
7053 let hope_id = CoreCorticalType::Hope.to_cortical_id();
7054 if !self.cortical_areas.contains_key(&fatigue_id) {
7055 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
7056 let fatigue_area = CorticalArea::new(
7057 fatigue_id,
7058 2, "_fatigue".to_string(),
7060 core_dimensions,
7061 core_position,
7062 CorticalAreaType::Core(CoreCorticalType::Fatigue),
7063 )
7064 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
7065 match self.add_cortical_area(fatigue_area) {
7066 Ok(idx) => {
7067 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
7068 }
7069 Err(e) => {
7070 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
7071 return Err(e);
7072 }
7073 }
7074 } else {
7075 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
7076 }
7077
7078 if !self.cortical_areas.contains_key(&pain_id) {
7080 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
7081 let pain_area = CorticalArea::new(
7082 pain_id,
7083 3, "_pain".to_string(),
7085 core_dimensions,
7086 core_position,
7087 CorticalAreaType::Core(CoreCorticalType::Pain),
7088 )
7089 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
7090 match self.add_cortical_area(pain_area) {
7091 Ok(idx) => {
7092 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
7093 }
7094 Err(e) => {
7095 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
7096 return Err(e);
7097 }
7098 }
7099 } else {
7100 info!(target: "feagi-bdu", " ✓ _pain area already exists");
7101 }
7102
7103 if !self.cortical_areas.contains_key(&pleasure_id) {
7105 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
7106 let pleasure_area = CorticalArea::new(
7107 pleasure_id,
7108 4, "_pleasure".to_string(),
7110 core_dimensions,
7111 core_position,
7112 CorticalAreaType::Core(CoreCorticalType::Pleasure),
7113 )
7114 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
7115 match self.add_cortical_area(pleasure_area) {
7116 Ok(idx) => {
7117 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
7118 }
7119 Err(e) => {
7120 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
7121 return Err(e);
7122 }
7123 }
7124 } else {
7125 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
7126 }
7127
7128 if !self.cortical_areas.contains_key(&fear_id) {
7130 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
7131 let fear_area = CorticalArea::new(
7132 fear_id,
7133 5, "_fear".to_string(),
7135 core_dimensions,
7136 core_position,
7137 CorticalAreaType::Core(CoreCorticalType::Fear),
7138 )
7139 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
7140 match self.add_cortical_area(fear_area) {
7141 Ok(idx) => {
7142 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
7143 }
7144 Err(e) => {
7145 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
7146 return Err(e);
7147 }
7148 }
7149 } else {
7150 info!(target: "feagi-bdu", " ✓ _fear area already exists");
7151 }
7152
7153 if !self.cortical_areas.contains_key(&hope_id) {
7155 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
7156 let hope_area = CorticalArea::new(
7157 hope_id,
7158 6, "_hope".to_string(),
7160 core_dimensions,
7161 core_position,
7162 CorticalAreaType::Core(CoreCorticalType::Hope),
7163 )
7164 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
7165 match self.add_cortical_area(hope_area) {
7166 Ok(idx) => {
7167 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
7168 }
7169 Err(e) => {
7170 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
7171 return Err(e);
7172 }
7173 }
7174 } else {
7175 info!(target: "feagi-bdu", " ✓ _hope area already exists");
7176 }
7177
7178 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
7179 Ok(())
7180 }
7181
7182 #[deprecated(
7199 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
7200 )]
7201 #[allow(deprecated)]
7202 pub fn save_genome_to_json(
7203 &self,
7204 genome_id: Option<String>,
7205 genome_title: Option<String>,
7206 ) -> BduResult<String> {
7207 let mut brain_regions_with_parents = std::collections::HashMap::new();
7209
7210 for region_id in self.brain_regions.get_all_region_ids() {
7211 if let Some(region) = self.brain_regions.get_region(region_id) {
7212 let parent_id = self
7213 .brain_regions
7214 .get_parent(region_id)
7215 .map(|s| s.to_string());
7216 brain_regions_with_parents
7217 .insert(region_id.to_string(), (region.clone(), parent_id));
7218 }
7219 }
7220
7221 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
7223 &self.cortical_areas,
7224 &brain_regions_with_parents,
7225 genome_id,
7226 genome_title,
7227 )?)
7228 }
7229
7230 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
7261 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
7262
7263 self.cortical_areas.clear();
7265 self.cortical_id_to_idx.clear();
7266 self.cortical_idx_to_id.clear();
7267 self.next_cortical_idx = 7;
7269 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)");
7270
7271 self.brain_regions = BrainRegionHierarchy::new();
7273
7274 #[cfg(feature = "plasticity")]
7277 if let Some(executor) = self.plasticity_executor.as_ref() {
7278 executor
7279 .lock()
7280 .map_err(|_| {
7281 BduError::Internal(
7282 "Failed to lock PlasticityExecutor for memory state reset".to_string(),
7283 )
7284 })?
7285 .reset_all_memory_state()
7286 .map_err(BduError::Internal)?;
7287 }
7288
7289 if let Some(ref npu) = self.npu {
7291 let mut npu_lock = npu
7292 .lock()
7293 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7294 npu_lock
7295 .reset_for_new_genome()
7296 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
7297 }
7298
7299 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
7300 Ok(())
7301 }
7302
7303 pub fn resize_for_genome(
7313 &mut self,
7314 genome: &feagi_evolutionary::RuntimeGenome,
7315 ) -> BduResult<()> {
7316 self.morphology_registry = genome.morphologies.clone();
7318 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
7319
7320 let required_neurons = genome.stats.innate_neuron_count;
7322 let required_synapses = genome.stats.innate_synapse_count;
7323
7324 info!(target: "feagi-bdu",
7325 "Genome requires: {} neurons, {} synapses",
7326 required_neurons,
7327 required_synapses
7328 );
7329
7330 let mut total_voxels = 0;
7332 for area in genome.cortical_areas.values() {
7333 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
7334 }
7335
7336 info!(target: "feagi-bdu",
7337 "Genome has {} cortical areas with {} total voxels",
7338 genome.cortical_areas.len(),
7339 total_voxels
7340 );
7341
7342 Ok(())
7347 }
7348
7349 pub fn create_synapse(
7368 &mut self,
7369 source_neuron_id: u64,
7370 target_neuron_id: u64,
7371 weight: f32,
7372 psp: f32,
7373 synapse_type: u8,
7374 ) -> BduResult<()> {
7375 let npu = self
7377 .npu
7378 .as_ref()
7379 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7380
7381 let mut npu_lock = npu
7382 .lock()
7383 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7384
7385 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
7387 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
7388
7389 if !source_exists {
7390 return Err(BduError::InvalidNeuron(format!(
7391 "Source neuron {} not found",
7392 source_neuron_id
7393 )));
7394 }
7395 if !target_exists {
7396 return Err(BduError::InvalidNeuron(format!(
7397 "Target neuron {} not found",
7398 target_neuron_id
7399 )));
7400 }
7401
7402 let syn_type = if synapse_type == 0 {
7404 feagi_npu_neural::synapse::SynapseType::Excitatory
7405 } else {
7406 feagi_npu_neural::synapse::SynapseType::Inhibitory
7407 };
7408
7409 let synapse_idx = npu_lock
7410 .add_synapse(
7411 NeuronId(source_neuron_id as u32),
7412 NeuronId(target_neuron_id as u32),
7413 feagi_npu_neural::types::SynapticWeight(weight),
7414 feagi_npu_neural::types::SynapticPsp(psp),
7415 syn_type,
7416 0,
7417 1,
7418 )
7419 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
7420
7421 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
7422 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
7423
7424 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
7425 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
7426 let source_cortical_id =
7427 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7428 let target_cortical_id =
7429 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7430
7431 let state_manager = StateManager::instance();
7432 let state_manager = state_manager.read();
7433 let core_state = state_manager.get_core_state();
7434 core_state.add_synapse_count(1);
7435 if let Some(cortical_id) = source_cortical_id {
7436 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
7437 }
7438 if let Some(cortical_id) = target_cortical_id {
7439 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
7440 }
7441
7442 Ok(())
7447 }
7448
7449 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
7452 if let Some(ref npu) = self.npu {
7453 if let Ok(mut npu_lock) = npu.lock() {
7454 let mut psp_uniform_flags = ahash::AHashMap::new();
7456 let mut mp_driven_psp_flags = ahash::AHashMap::new();
7457 let mut postsynaptic_current_flags = ahash::AHashMap::new();
7458 let mut degeneration_flags = ahash::AHashMap::new();
7459
7460 for (cortical_id, area) in &self.cortical_areas {
7461 let default_psp_uniform = *cortical_id
7464 == CoreCorticalType::Power.to_cortical_id()
7465 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
7466 let psp_uniform = area
7467 .get_property("psp_uniform_distribution")
7468 .and_then(|v| v.as_bool())
7469 .unwrap_or(default_psp_uniform);
7470 psp_uniform_flags.insert(*cortical_id, psp_uniform);
7471
7472 let mp_driven_psp = area
7474 .get_property("mp_driven_psp")
7475 .and_then(|v| v.as_bool())
7476 .unwrap_or(false);
7477 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
7478
7479 let postsynaptic_current = area
7481 .get_property("postsynaptic_current")
7482 .and_then(|v| v.as_f64())
7483 .unwrap_or(1.0) as f32;
7484 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
7485
7486 let degeneration = area
7488 .get_property("degeneration")
7489 .and_then(|v| v.as_f64())
7490 .unwrap_or(0.0) as f32;
7491 if degeneration > 0.0 {
7492 degeneration_flags.insert(*cortical_id, degeneration);
7493 }
7494 }
7495
7496 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
7498 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
7499 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
7500 npu_lock.set_degeneration_flags(degeneration_flags);
7501
7502 trace!(
7503 target: "feagi-bdu",
7504 "Synchronized cortical area flags to NPU ({} areas)",
7505 self.cortical_areas.len()
7506 );
7507 }
7508 }
7509
7510 Ok(())
7511 }
7512
7513 pub fn get_synapse(
7525 &self,
7526 source_neuron_id: u64,
7527 target_neuron_id: u64,
7528 ) -> Option<(f32, f32, u8)> {
7529 let npu = self.npu.as_ref()?;
7531 let npu_lock = npu.lock().ok()?;
7532
7533 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
7536
7537 for (source_id, weight, psp, synapse_type) in incoming {
7539 if source_id == source_neuron_id as u32 {
7540 return Some((weight, psp, synapse_type));
7541 }
7542 }
7543
7544 None
7545 }
7546
7547 pub fn update_synapse_weight(
7560 &mut self,
7561 source_neuron_id: u64,
7562 target_neuron_id: u64,
7563 new_weight: f32,
7564 ) -> BduResult<()> {
7565 let npu = self
7567 .npu
7568 .as_ref()
7569 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7570
7571 let mut npu_lock = npu
7572 .lock()
7573 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7574
7575 let updated = npu_lock.update_synapse_weight(
7577 NeuronId(source_neuron_id as u32),
7578 NeuronId(target_neuron_id as u32),
7579 feagi_npu_neural::types::SynapticWeight(new_weight),
7580 );
7581
7582 if updated {
7583 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
7584 Ok(())
7585 } else {
7586 Err(BduError::InvalidSynapse(format!(
7587 "Synapse {} -> {} not found",
7588 source_neuron_id, target_neuron_id
7589 )))
7590 }
7591 }
7592
7593 pub fn remove_synapse(
7605 &mut self,
7606 source_neuron_id: u64,
7607 target_neuron_id: u64,
7608 ) -> BduResult<bool> {
7609 let npu = self
7611 .npu
7612 .as_ref()
7613 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7614
7615 let mut npu_lock = npu
7616 .lock()
7617 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7618
7619 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
7620 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
7621 let source_cortical_id =
7622 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7623 let target_cortical_id =
7624 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7625
7626 let removed = npu_lock.remove_synapse(
7628 NeuronId(source_neuron_id as u32),
7629 NeuronId(target_neuron_id as u32),
7630 );
7631
7632 if removed {
7633 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
7634
7635 let state_manager = StateManager::instance();
7637 let state_manager = state_manager.read();
7638 let core_state = state_manager.get_core_state();
7639 core_state.subtract_synapse_count(1);
7640 if let Some(cortical_id) = source_cortical_id {
7641 state_manager
7642 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
7643 }
7644 if let Some(cortical_id) = target_cortical_id {
7645 state_manager
7646 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
7647 }
7648 }
7649
7650 Ok(removed)
7651 }
7652
7653 pub fn batch_create_neurons(
7671 &mut self,
7672 cortical_id: &CorticalID,
7673 neurons: Vec<NeuronData>,
7674 ) -> BduResult<Vec<u64>> {
7675 let npu = self
7677 .npu
7678 .as_ref()
7679 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7680
7681 let mut npu_lock = npu
7682 .lock()
7683 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7684
7685 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
7687 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
7688 })?;
7689 let cortical_idx = area.cortical_idx;
7690
7691 let count = neurons.len();
7692
7693 let mut x_coords = Vec::with_capacity(count);
7695 let mut y_coords = Vec::with_capacity(count);
7696 let mut z_coords = Vec::with_capacity(count);
7697 let mut firing_thresholds = Vec::with_capacity(count);
7698 let mut threshold_limits = Vec::with_capacity(count);
7699 let mut leak_coeffs = Vec::with_capacity(count);
7700 let mut resting_potentials = Vec::with_capacity(count);
7701 let mut neuron_types = Vec::with_capacity(count);
7702 let mut refractory_periods = Vec::with_capacity(count);
7703 let mut excitabilities = Vec::with_capacity(count);
7704 let mut consec_fire_limits = Vec::with_capacity(count);
7705 let mut snooze_lengths = Vec::with_capacity(count);
7706 let mut mp_accums = Vec::with_capacity(count);
7707 let mut cortical_areas = Vec::with_capacity(count);
7708
7709 for (
7710 x,
7711 y,
7712 z,
7713 threshold,
7714 threshold_limit,
7715 leak,
7716 resting,
7717 ntype,
7718 refract,
7719 excit,
7720 consec_limit,
7721 snooze,
7722 mp_accum,
7723 ) in neurons
7724 {
7725 x_coords.push(x);
7726 y_coords.push(y);
7727 z_coords.push(z);
7728 firing_thresholds.push(threshold);
7729 threshold_limits.push(threshold_limit);
7730 leak_coeffs.push(leak);
7731 resting_potentials.push(resting);
7732 neuron_types.push(ntype);
7733 refractory_periods.push(refract);
7734 excitabilities.push(excit);
7735 consec_fire_limits.push(consec_limit);
7736 snooze_lengths.push(snooze);
7737 mp_accums.push(mp_accum);
7738 cortical_areas.push(cortical_idx);
7739 }
7740
7741 let first_neuron_id = npu_lock.get_neuron_count() as u32;
7743
7744 let firing_thresholds_t = firing_thresholds;
7749 let threshold_limits_t = threshold_limits;
7750 let resting_potentials_t = resting_potentials;
7751 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
7752 firing_thresholds_t,
7753 threshold_limits_t,
7754 leak_coeffs,
7755 resting_potentials_t,
7756 neuron_types,
7757 refractory_periods,
7758 excitabilities,
7759 consec_fire_limits,
7760 snooze_lengths,
7761 mp_accums,
7762 cortical_areas,
7763 x_coords,
7764 y_coords,
7765 z_coords,
7766 );
7767
7768 let mut neuron_ids = Vec::with_capacity(count);
7770 for i in 0..neurons_created {
7771 neuron_ids.push((first_neuron_id + i) as u64);
7772 }
7773
7774 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
7775
7776 let state_manager = StateManager::instance();
7778 let state_manager = state_manager.read();
7779 let core_state = state_manager.get_core_state();
7780 core_state.add_neuron_count(neurons_created);
7781 core_state.add_regular_neuron_count(neurons_created);
7782 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
7783
7784 {
7786 let mut cache = self.cached_neuron_counts_per_area.write();
7787 cache
7788 .entry(*cortical_id)
7789 .or_insert_with(|| AtomicUsize::new(0))
7790 .fetch_add(count, Ordering::Relaxed);
7791 }
7792
7793 Ok(neuron_ids)
7794 }
7795
7796 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
7807 let npu = self
7809 .npu
7810 .as_ref()
7811 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7812
7813 let mut npu_lock = npu
7814 .lock()
7815 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7816
7817 let mut deleted_count = 0;
7818 let mut per_area_deleted: std::collections::HashMap<String, usize> =
7819 std::collections::HashMap::new();
7820
7821 for neuron_id in neuron_ids {
7824 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
7825 let cortical_id =
7826 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7827
7828 if npu_lock.delete_neuron(neuron_id as u32) {
7829 deleted_count += 1;
7830 if let Some(cortical_id) = cortical_id {
7831 let key = cortical_id.as_base_64();
7832 *per_area_deleted.entry(key).or_insert(0) += 1;
7833 }
7834 }
7835 }
7836
7837 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
7838
7839 if deleted_count > 0 {
7841 let state_manager = StateManager::instance();
7842 let state_manager = state_manager.read();
7843 let core_state = state_manager.get_core_state();
7844 core_state.subtract_neuron_count(deleted_count as u32);
7845 core_state.subtract_regular_neuron_count(deleted_count as u32);
7846 for (cortical_id, count) in per_area_deleted {
7847 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
7848 }
7849 }
7850
7851 Ok(deleted_count)
7858 }
7859
7860 pub fn update_neuron_properties(
7879 &mut self,
7880 neuron_id: u64,
7881 firing_threshold: Option<f32>,
7882 leak_coefficient: Option<f32>,
7883 resting_potential: Option<f32>,
7884 excitability: Option<f32>,
7885 ) -> BduResult<()> {
7886 let npu = self
7888 .npu
7889 .as_ref()
7890 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7891
7892 let mut npu_lock = npu
7893 .lock()
7894 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7895
7896 let neuron_id_u32 = neuron_id as u32;
7897
7898 let mut updated = false;
7900
7901 if let Some(threshold) = firing_threshold {
7903 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
7904 updated = true;
7905 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
7906 } else if !updated {
7907 return Err(BduError::InvalidNeuron(format!(
7908 "Neuron {} not found",
7909 neuron_id
7910 )));
7911 }
7912 }
7913
7914 if let Some(leak) = leak_coefficient {
7915 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
7916 updated = true;
7917 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
7918 } else if !updated {
7919 return Err(BduError::InvalidNeuron(format!(
7920 "Neuron {} not found",
7921 neuron_id
7922 )));
7923 }
7924 }
7925
7926 if let Some(resting) = resting_potential {
7927 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
7928 updated = true;
7929 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
7930 } else if !updated {
7931 return Err(BduError::InvalidNeuron(format!(
7932 "Neuron {} not found",
7933 neuron_id
7934 )));
7935 }
7936 }
7937
7938 if let Some(excit) = excitability {
7939 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
7940 updated = true;
7941 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
7942 } else if !updated {
7943 return Err(BduError::InvalidNeuron(format!(
7944 "Neuron {} not found",
7945 neuron_id
7946 )));
7947 }
7948 }
7949
7950 if !updated {
7951 return Err(BduError::Internal(
7952 "No properties provided for update".to_string(),
7953 ));
7954 }
7955
7956 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
7957
7958 Ok(())
7959 }
7960
7961 pub fn set_neuron_firing_threshold(
7973 &mut self,
7974 neuron_id: u64,
7975 new_threshold: f32,
7976 ) -> BduResult<()> {
7977 let npu = self
7979 .npu
7980 .as_ref()
7981 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7982
7983 let mut npu_lock = npu
7984 .lock()
7985 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7986
7987 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
7989 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
7990 Ok(())
7991 } else {
7992 Err(BduError::InvalidNeuron(format!(
7993 "Neuron {} not found",
7994 neuron_id
7995 )))
7996 }
7997 }
7998
7999 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
8014 self.cortical_areas
8015 .values()
8016 .find(|area| area.name == name)
8017 .cloned()
8018 }
8019
8020 pub fn resize_cortical_area(
8037 &mut self,
8038 cortical_id: &CorticalID,
8039 new_dimensions: CorticalAreaDimensions,
8040 ) -> BduResult<()> {
8041 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
8043 return Err(BduError::InvalidArea(format!(
8044 "Invalid dimensions: {:?} (all must be > 0)",
8045 new_dimensions
8046 )));
8047 }
8048
8049 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
8051 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
8052 })?;
8053
8054 let old_dimensions = area.dimensions;
8055 area.dimensions = new_dimensions;
8056
8057 info!(target: "feagi-bdu",
8061 "Resized cortical area {} from {:?} to {:?}",
8062 cortical_id,
8063 old_dimensions,
8064 new_dimensions
8065 );
8066
8067 self.refresh_cortical_area_hashes(false, true);
8068
8069 Ok(())
8070 }
8071
8072 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
8083 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
8084 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8085 })?;
8086
8087 Ok(region
8089 .cortical_areas
8090 .iter()
8091 .map(|id| id.as_base_64())
8092 .collect())
8093 }
8094
8095 pub fn update_brain_region(
8108 &mut self,
8109 region_id: &str,
8110 new_name: Option<String>,
8111 new_description: Option<String>,
8112 ) -> BduResult<()> {
8113 let region = self
8114 .brain_regions
8115 .get_region_mut(region_id)
8116 .ok_or_else(|| {
8117 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8118 })?;
8119
8120 if let Some(name) = new_name {
8121 region.name = name;
8122 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
8123 }
8124
8125 if let Some(desc) = new_description {
8126 region
8128 .properties
8129 .insert("description".to_string(), serde_json::json!(desc));
8130 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
8131 }
8132
8133 info!(target: "feagi-bdu","Updated brain region {}", region_id);
8134
8135 self.refresh_brain_regions_hash();
8136
8137 Ok(())
8138 }
8139
8140 pub fn update_brain_region_properties(
8154 &mut self,
8155 region_id: &str,
8156 properties: std::collections::HashMap<String, serde_json::Value>,
8157 ) -> BduResult<Option<BrainRegionIoRegistry>> {
8158 use tracing::{debug, info};
8159
8160 let should_recompute_io = properties
8161 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
8162 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
8163
8164 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
8165 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
8166 {
8167 let region_snapshot = self
8168 .brain_regions
8169 .get_region(region_id)
8170 .ok_or_else(|| {
8171 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8172 })?
8173 .clone();
8174 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
8175 ®ion_snapshot,
8176 &properties,
8177 )?;
8178 crate::region_io_designation::validate_merged_designations_against_connectivity(
8179 self,
8180 ®ion_snapshot,
8181 &merged_in,
8182 &merged_out,
8183 )?;
8184 }
8185
8186 let region = self
8187 .brain_regions
8188 .get_region_mut(region_id)
8189 .ok_or_else(|| {
8190 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8191 })?;
8192
8193 for (key, value) in properties {
8194 match key.as_str() {
8195 "title" | "name" | "region_title" => {
8197 if let Some(name) = value.as_str() {
8198 region.name = name.to_string();
8199 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
8200 }
8201 }
8202 "coordinate_3d" | "coordinates_3d" => {
8203 region
8204 .properties
8205 .insert("coordinate_3d".to_string(), value.clone());
8206 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
8207 }
8208 "coordinate_2d" | "coordinates_2d" => {
8209 region
8210 .properties
8211 .insert("coordinate_2d".to_string(), value.clone());
8212 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
8213 }
8214 "description" => {
8215 region
8216 .properties
8217 .insert("description".to_string(), value.clone());
8218 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
8219 }
8220 "region_type" => {
8221 if let Some(type_str) = value.as_str() {
8222 region.region_type = feagi_structures::genomic::RegionType::Undefined;
8225 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
8226 }
8227 }
8228 _ => {
8230 region.properties.insert(key.clone(), value.clone());
8231 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
8232 }
8233 }
8234 }
8235
8236 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
8237
8238 if should_recompute_io {
8241 let registry = self.recompute_brain_region_io_registry()?;
8242 return Ok(Some(registry));
8243 }
8244
8245 self.refresh_brain_regions_hash();
8249
8250 Ok(None)
8251 }
8252
8253 pub fn get_neuron_by_coordinates(
8271 &self,
8272 cortical_id: &CorticalID,
8273 x: u32,
8274 y: u32,
8275 z: u32,
8276 ) -> Option<u64> {
8277 let area = self.get_cortical_area(cortical_id)?;
8279 let cortical_idx = area.cortical_idx;
8280
8281 let npu = self.npu.as_ref()?;
8283 let npu_lock = npu.lock().ok()?;
8284
8285 npu_lock
8286 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
8287 .map(|id| id as u64)
8288 }
8289
8290 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
8301 let npu = self.npu.as_ref()?;
8302 let npu_lock = npu.lock().ok()?;
8303
8304 let neuron_count = npu_lock.get_neuron_count();
8306 if (neuron_id as usize) >= neuron_count {
8307 return None;
8308 }
8309
8310 Some(
8311 npu_lock
8312 .get_neuron_coordinates(neuron_id as u32)
8313 .unwrap_or((0, 0, 0)),
8314 )
8315 }
8316
8317 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
8328 let npu = self.npu.as_ref()?;
8329 let npu_lock = npu.lock().ok()?;
8330
8331 let neuron_count = npu_lock.get_neuron_count();
8333 if (neuron_id as usize) >= neuron_count {
8334 return None;
8335 }
8336
8337 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
8338
8339 self.cortical_areas
8341 .values()
8342 .find(|area| area.cortical_idx == cortical_idx)
8343 .map(|area| area.cortical_id)
8344 }
8345
8346 pub fn get_neuron_properties(
8357 &self,
8358 neuron_id: u64,
8359 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8360 let npu = self.npu.as_ref()?;
8361 let npu_lock = npu.lock().ok()?;
8362
8363 let neuron_id_u32 = neuron_id as u32;
8364 let idx = neuron_id as usize;
8365
8366 let neuron_count = npu_lock.get_neuron_count();
8368 if idx >= neuron_count {
8369 return None;
8370 }
8371
8372 let mut properties = std::collections::HashMap::new();
8373
8374 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
8376
8377 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
8379 properties.insert("x".to_string(), serde_json::json!(x));
8380 properties.insert("y".to_string(), serde_json::json!(y));
8381 properties.insert("z".to_string(), serde_json::json!(z));
8382
8383 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
8385 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
8386
8387 properties.insert(
8389 "mp_charge_accumulation".to_string(),
8390 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
8391 );
8392 properties.insert(
8393 "neuron_type".to_string(),
8394 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
8395 );
8396 let (mp_drv, psp_uni) = self
8397 .cortical_idx_to_id
8398 .get(&cortical_idx)
8399 .map(|cid| {
8400 (
8401 npu_lock.get_mp_driven_psp_for_cortical(cid),
8402 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
8403 )
8404 })
8405 .unwrap_or((false, false));
8406 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
8407 properties.insert(
8408 "psp_uniform_distribution".to_string(),
8409 serde_json::json!(psp_uni),
8410 );
8411
8412 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
8415 .get_neuron_state(NeuronId(neuron_id_u32))
8416 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
8417 properties.insert(
8418 "consecutive_fire_count".to_string(),
8419 serde_json::json!(consec_count),
8420 );
8421 properties.insert(
8422 "consecutive_fire_limit".to_string(),
8423 serde_json::json!(consec_limit),
8424 );
8425 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
8426 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
8427 properties.insert("threshold".to_string(), serde_json::json!(threshold));
8428 properties.insert(
8429 "refractory_countdown".to_string(),
8430 serde_json::json!(refract_countdown),
8431 );
8432
8433 properties.insert(
8435 "leak_coefficient".to_string(),
8436 serde_json::json!(npu_lock
8437 .get_neuron_property_by_index(idx, "leak_coefficient")
8438 .unwrap_or(0.0)),
8439 );
8440 properties.insert(
8441 "resting_potential".to_string(),
8442 serde_json::json!(npu_lock
8443 .get_neuron_property_by_index(idx, "resting_potential")
8444 .unwrap_or(0.0)),
8445 );
8446 properties.insert(
8447 "excitability".to_string(),
8448 serde_json::json!(npu_lock
8449 .get_neuron_property_by_index(idx, "excitability")
8450 .unwrap_or(0.0)),
8451 );
8452 properties.insert(
8453 "threshold_limit".to_string(),
8454 serde_json::json!(npu_lock
8455 .get_neuron_property_by_index(idx, "threshold_limit")
8456 .unwrap_or(0.0)),
8457 );
8458 properties.insert(
8459 "refractory_period".to_string(),
8460 serde_json::json!(npu_lock
8461 .get_neuron_property_u16_by_index(idx, "refractory_period")
8462 .unwrap_or(0)),
8463 );
8464
8465 Some(properties)
8466 }
8467
8468 pub fn get_neuron_property(
8480 &self,
8481 neuron_id: u64,
8482 property_name: &str,
8483 ) -> Option<serde_json::Value> {
8484 self.get_neuron_properties(neuron_id)?
8485 .get(property_name)
8486 .cloned()
8487 }
8488
8489 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
8500 self.cortical_areas.keys().copied().collect()
8501 }
8502
8503 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
8510 self.cortical_areas
8511 .values()
8512 .map(|area| area.cortical_idx)
8513 .collect()
8514 }
8515
8516 pub fn get_cortical_area_names(&self) -> Vec<String> {
8523 self.cortical_areas
8524 .values()
8525 .map(|area| area.name.clone())
8526 .collect()
8527 }
8528
8529 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
8536 use crate::models::CorticalAreaExt;
8537 self.cortical_areas
8538 .values()
8539 .filter(|area| area.is_input_area())
8540 .map(|area| area.cortical_id)
8541 .collect()
8542 }
8543
8544 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
8551 use crate::models::CorticalAreaExt;
8552 self.cortical_areas
8553 .values()
8554 .filter(|area| area.is_output_area())
8555 .map(|area| area.cortical_id)
8556 .collect()
8557 }
8558
8559 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
8566 self.cortical_areas
8567 .values()
8568 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
8569 (
8570 max_w.max(area.dimensions.width as usize),
8571 max_h.max(area.dimensions.height as usize),
8572 max_d.max(area.dimensions.depth as usize),
8573 )
8574 })
8575 }
8576
8577 pub fn get_cortical_area_properties(
8588 &self,
8589 cortical_id: &CorticalID,
8590 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8591 let area = self.get_cortical_area(cortical_id)?;
8592
8593 let mut properties = std::collections::HashMap::new();
8594 properties.insert(
8595 "cortical_id".to_string(),
8596 serde_json::json!(area.cortical_id),
8597 );
8598 properties.insert(
8599 "cortical_id_s".to_string(),
8600 serde_json::json!(area.cortical_id.to_string()),
8601 );
8602 properties.insert(
8603 "cortical_idx".to_string(),
8604 serde_json::json!(area.cortical_idx),
8605 );
8606 properties.insert("name".to_string(), serde_json::json!(area.name));
8607 use crate::models::CorticalAreaExt;
8608 properties.insert(
8609 "area_type".to_string(),
8610 serde_json::json!(area.get_cortical_group()),
8611 );
8612 properties.insert(
8613 "dimensions".to_string(),
8614 serde_json::json!({
8615 "width": area.dimensions.width,
8616 "height": area.dimensions.height,
8617 "depth": area.dimensions.depth,
8618 }),
8619 );
8620 properties.insert("position".to_string(), serde_json::json!(area.position));
8621
8622 for (key, value) in &area.properties {
8624 properties.insert(key.clone(), value.clone());
8625 }
8626
8627 properties.extend(area.properties.clone());
8629
8630 Some(properties)
8631 }
8632
8633 pub fn get_all_cortical_area_properties(
8640 &self,
8641 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
8642 self.cortical_areas
8643 .keys()
8644 .filter_map(|id| self.get_cortical_area_properties(id))
8645 .collect()
8646 }
8647
8648 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
8659 self.brain_regions
8660 .get_all_region_ids()
8661 .into_iter()
8662 .cloned()
8663 .collect()
8664 }
8665
8666 pub fn get_brain_region_names(&self) -> Vec<String> {
8673 self.brain_regions
8674 .get_all_region_ids()
8675 .iter()
8676 .filter_map(|id| {
8677 self.brain_regions
8678 .get_region(id)
8679 .map(|region| region.name.clone())
8680 })
8681 .collect()
8682 }
8683
8684 pub fn get_brain_region_properties(
8695 &self,
8696 region_id: &str,
8697 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8698 let region = self.brain_regions.get_region(region_id)?;
8699
8700 let mut properties = std::collections::HashMap::new();
8701 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
8702 properties.insert("name".to_string(), serde_json::json!(region.name));
8703 properties.insert(
8704 "region_type".to_string(),
8705 serde_json::json!(format!("{:?}", region.region_type)),
8706 );
8707 properties.insert(
8708 "cortical_areas".to_string(),
8709 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
8710 );
8711
8712 properties.extend(region.properties.clone());
8714
8715 Some(properties)
8716 }
8717
8718 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
8729 self.cortical_areas.contains_key(cortical_id)
8730 }
8731
8732 pub fn brain_region_exists(&self, region_id: &str) -> bool {
8743 self.brain_regions.get_region(region_id).is_some()
8744 }
8745
8746 pub fn get_brain_region_count(&self) -> usize {
8753 self.brain_regions.region_count()
8754 }
8755
8756 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
8767 self.get_neurons_in_area(cortical_id)
8771 }
8772}
8773
8774impl std::fmt::Debug for ConnectomeManager {
8776 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8777 f.debug_struct("ConnectomeManager")
8778 .field("cortical_areas", &self.cortical_areas.len())
8779 .field("next_cortical_idx", &self.next_cortical_idx)
8780 .field("brain_regions", &self.brain_regions)
8781 .field(
8782 "npu",
8783 &if self.npu.is_some() {
8784 "Connected"
8785 } else {
8786 "Not connected"
8787 },
8788 )
8789 .field("initialized", &self.initialized)
8790 .finish()
8791 }
8792}
8793
8794#[cfg(test)]
8795mod tests {
8796 use super::*;
8797 use feagi_structures::genomic::cortical_area::CoreCorticalType;
8798
8799 #[cfg(feature = "plasticity")]
8803 #[test]
8804 fn prepare_for_new_genome_discards_memory_neurons_from_previous_brain() {
8805 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
8806 use feagi_npu_plasticity::executor::PlasticityExecutor;
8807 use feagi_npu_plasticity::{
8808 create_memory_stats_cache, AsyncPlasticityExecutor, MemoryNeuronDetail,
8809 PlasticityConfig,
8810 };
8811 use feagi_npu_runtime::StdRuntime;
8812
8813 let npu = Arc::new(TracingMutex::new(
8814 DynamicNPU::new_f32(
8815 StdRuntime::new(),
8816 feagi_npu_burst_engine::backend::CPUBackend::new(),
8817 16,
8818 16,
8819 8,
8820 )
8821 .unwrap(),
8822 "prepare-for-new-genome-test-npu",
8823 ));
8824 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
8825 PlasticityConfig::default(),
8826 create_memory_stats_cache(),
8827 npu.clone(),
8828 )));
8829
8830 let mut manager = ConnectomeManager::new_for_testing();
8831 manager.set_npu(npu);
8832 manager.set_plasticity_executor(executor.clone());
8833
8834 let previous_brain_memory_idx = 8;
8835 {
8836 let exec = executor.lock().expect("plasticity executor");
8837 PlasticityExecutor::register_memory_area(
8838 &*exec,
8839 previous_brain_memory_idx,
8840 "previous_brain_memory".to_string(),
8841 1,
8842 vec![7],
8843 None,
8844 false,
8845 );
8846 exec.restore_long_term_memory_neurons(&[MemoryNeuronDetail {
8847 neuron_id: 50_000_003,
8848 cortical_area_idx: previous_brain_memory_idx,
8849 pattern_hash: Some(9_631_261_403_772_054_764),
8850 is_longterm_memory: true,
8851 is_active: true,
8852 lifespan_current: 120,
8853 lifespan_initial: 20,
8854 lifespan_growth_rate: 3.0,
8855 creation_burst: 0,
8856 last_activation_burst: 0,
8857 activation_count: 5,
8858 spatial_signature: None,
8859 class_channels: Vec::new(),
8860 }])
8861 .expect("seeded long-term memory neuron");
8862 assert_eq!(
8863 exec.export_long_term_memory_neurons()
8864 .expect("plasticity service is initialized")
8865 .len(),
8866 1
8867 );
8868 }
8869
8870 manager
8871 .prepare_for_new_genome()
8872 .expect("genome preparation must succeed");
8873
8874 let exec = executor.lock().expect("plasticity executor");
8875 assert!(
8876 exec.export_long_term_memory_neurons()
8877 .expect("plasticity service is initialized")
8878 .is_empty(),
8879 "memory neurons from the previous brain must not survive a genome load"
8880 );
8881 assert_eq!(
8882 exec.paginated_memory_neuron_ids_in_area(previous_brain_memory_idx, 0, 10),
8883 Some((Vec::new(), 0)),
8884 "the previous brain's memory area must no longer report any memory neurons"
8885 );
8886 }
8887
8888 #[cfg(feature = "plasticity")]
8891 #[test]
8892 fn remove_cortical_area_unregisters_its_memory() {
8893 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
8894 use feagi_npu_plasticity::executor::PlasticityExecutor;
8895 use feagi_npu_plasticity::{
8896 create_memory_stats_cache, AsyncPlasticityExecutor, PlasticityConfig,
8897 };
8898 use feagi_npu_runtime::StdRuntime;
8899 use feagi_structures::genomic::cortical_area::{
8900 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8901 };
8902
8903 let npu = Arc::new(TracingMutex::new(
8904 DynamicNPU::new_f32(
8905 StdRuntime::new(),
8906 feagi_npu_burst_engine::backend::CPUBackend::new(),
8907 16,
8908 16,
8909 8,
8910 )
8911 .unwrap(),
8912 "remove-memory-area-test-npu",
8913 ));
8914 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
8915 PlasticityConfig::default(),
8916 create_memory_stats_cache(),
8917 npu.clone(),
8918 )));
8919 let mut manager = ConnectomeManager::new_for_testing();
8920 manager.set_npu(npu);
8921 manager.set_plasticity_executor(executor.clone());
8922
8923 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
8924 let mut area = CorticalArea::new(
8925 mem_id,
8926 0,
8927 "kernel_mem".to_string(),
8928 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8929 (0, 0, 0).into(),
8930 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8931 )
8932 .unwrap();
8933 area.properties
8934 .insert("is_mem_type".to_string(), serde_json::json!(true));
8935 let idx = manager.add_cortical_area(area).unwrap();
8936 {
8937 let exec = executor.lock().expect("plasticity executor");
8938 PlasticityExecutor::register_memory_area(
8939 &*exec,
8940 idx,
8941 mem_id.as_base_64(),
8942 1,
8943 vec![7],
8944 None,
8945 false,
8946 );
8947 }
8948
8949 manager
8950 .remove_cortical_area(&mem_id)
8951 .expect("memory area removal");
8952
8953 assert!(manager.get_cortical_id(idx).is_none());
8954 let exec = executor.lock().expect("plasticity executor");
8955 let indexes = exec
8956 .get_service()
8957 .expect("plasticity service")
8958 .registered_memory_area_indexes();
8959 assert!(
8960 !indexes.contains(&idx),
8961 "deleted memory area idx={idx} must leave plasticity, still registered: {indexes:?}"
8962 );
8963 }
8964
8965 #[test]
8966 fn test_singleton_instance() {
8967 let instance1 = ConnectomeManager::instance();
8968 let instance2 = ConnectomeManager::instance();
8969
8970 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
8972 }
8973
8974 #[test]
8975 fn test_add_cortical_area() {
8976 ConnectomeManager::reset_for_testing();
8977
8978 let instance = ConnectomeManager::instance();
8979 let mut manager = instance.write();
8980
8981 use feagi_structures::genomic::cortical_area::{
8982 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8983 };
8984 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
8986 let area = CorticalArea::new(
8987 cortical_id,
8988 0,
8989 "Visual Input".to_string(),
8990 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
8991 (0, 0, 0).into(),
8992 cortical_type,
8993 )
8994 .unwrap();
8995
8996 let initial_count = manager.get_cortical_area_count();
8997 let _cortical_idx = manager.add_cortical_area(area).unwrap();
8998
8999 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
9000 assert!(manager.has_cortical_area(&cortical_id));
9001 assert!(manager.is_initialized());
9002 }
9003
9004 #[test]
9005 fn refresh_all_connectome_hashes_publishes_mappings() {
9006 use feagi_structures::genomic::cortical_area::{
9007 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CustomCorticalType,
9008 };
9009
9010 let src_id = CorticalID::try_from_bytes(b"chashsrc").unwrap();
9011 let dst_id = CorticalID::try_from_bytes(b"chashdst").unwrap();
9012 let mut src = CorticalArea::new(
9013 src_id,
9014 1,
9015 "src".to_string(),
9016 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9017 (0, 0, 0).into(),
9018 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
9019 )
9020 .unwrap();
9021 src.properties.insert(
9022 "cortical_mapping_dst".to_string(),
9023 serde_json::json!({
9024 dst_id.as_base_64(): [{ "morphology_id": "projector" }]
9025 }),
9026 );
9027 let mut manager = ConnectomeManager::new_for_testing();
9028 manager.add_cortical_area(src).unwrap();
9029 manager.refresh_all_connectome_hashes();
9030 let mappings_hash = feagi_state_manager::StateManager::instance()
9031 .read()
9032 .get_cortical_mappings_hash();
9033 assert_ne!(
9034 mappings_hash, 0,
9035 "refresh_all_connectome_hashes must publish cortical_mappings_hash"
9036 );
9037 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
9038 classifier_id: "clf-hash".to_string(),
9039 name: "hash_demo".to_string(),
9040 parent_region_id: "root".to_string(),
9041 coordinates_3d: [0, 0, 0],
9042 kernel_area_id: None,
9043 class_area_id: None,
9044 fields: Vec::new(),
9045 kernel_memory_id: "mkmem1".to_string(),
9046 class_memory_id: "mcmem1".to_string(),
9047 properties: HashMap::new(),
9048 });
9049 manager.refresh_all_connectome_hashes();
9050 let classifiers_hash = feagi_state_manager::StateManager::instance()
9051 .read()
9052 .get_classifiers_hash();
9053 assert_ne!(
9054 classifiers_hash, 0,
9055 "refresh_all_connectome_hashes must publish classifiers_hash"
9056 );
9057 }
9058
9059 #[test]
9060 fn test_cortical_area_lookups() {
9061 ConnectomeManager::reset_for_testing();
9062
9063 let instance = ConnectomeManager::instance();
9064 let mut manager = instance.write();
9065
9066 use feagi_structures::genomic::cortical_area::{
9067 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9068 };
9069 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9071 let area = CorticalArea::new(
9072 cortical_id,
9073 0,
9074 "Test Area".to_string(),
9075 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9076 (0, 0, 0).into(),
9077 cortical_type,
9078 )
9079 .unwrap();
9080
9081 let cortical_idx = manager.add_cortical_area(area).unwrap();
9082
9083 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
9085
9086 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
9088
9089 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
9091 assert_eq!(retrieved_area.name, "Test Area");
9092 }
9093
9094 #[test]
9095 fn test_remove_cortical_area() {
9096 ConnectomeManager::reset_for_testing();
9097
9098 let instance = ConnectomeManager::instance();
9099 let mut manager = instance.write();
9100
9101 use feagi_structures::genomic::cortical_area::{
9102 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9103 };
9104 let cortical_id = CoreCorticalType::Power.to_cortical_id();
9105
9106 if manager.has_cortical_area(&cortical_id) {
9108 manager.remove_cortical_area(&cortical_id).unwrap();
9109 }
9110
9111 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9112 let area = CorticalArea::new(
9113 cortical_id,
9114 0,
9115 "Test".to_string(),
9116 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9117 (0, 0, 0).into(),
9118 cortical_type,
9119 )
9120 .unwrap();
9121
9122 let initial_count = manager.get_cortical_area_count();
9123 manager.add_cortical_area(area).unwrap();
9124 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
9125
9126 let areas_hash_before = feagi_state_manager::StateManager::instance()
9127 .read()
9128 .get_cortical_areas_hash();
9129 let geometry_hash_before = feagi_state_manager::StateManager::instance()
9130 .read()
9131 .get_brain_geometry_hash();
9132
9133 manager.remove_cortical_area(&cortical_id).unwrap();
9134 assert_eq!(manager.get_cortical_area_count(), initial_count);
9135 assert!(!manager.has_cortical_area(&cortical_id));
9136
9137 let areas_hash_after = feagi_state_manager::StateManager::instance()
9138 .read()
9139 .get_cortical_areas_hash();
9140 let geometry_hash_after = feagi_state_manager::StateManager::instance()
9141 .read()
9142 .get_brain_geometry_hash();
9143 assert_ne!(
9144 areas_hash_before, areas_hash_after,
9145 "remove_cortical_area must publish a new cortical_areas_hash"
9146 );
9147 assert_ne!(
9148 geometry_hash_before, geometry_hash_after,
9149 "remove_cortical_area must publish a new brain_geometry_hash"
9150 );
9151 }
9152
9153 #[test]
9154 fn test_duplicate_area_error() {
9155 ConnectomeManager::reset_for_testing();
9156
9157 let instance = ConnectomeManager::instance();
9158 let mut manager = instance.write();
9159
9160 use feagi_structures::genomic::cortical_area::{
9161 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9162 };
9163 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
9166 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9167 let area1 = CorticalArea::new(
9168 cortical_id,
9169 0,
9170 "First".to_string(),
9171 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9172 (0, 0, 0).into(),
9173 cortical_type,
9174 )
9175 .unwrap();
9176
9177 let area2 = CorticalArea::new(
9178 cortical_id, 1,
9180 "Second".to_string(),
9181 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9182 (0, 0, 0).into(),
9183 cortical_type,
9184 )
9185 .unwrap();
9186
9187 manager.add_cortical_area(area1).unwrap();
9188 let result = manager.add_cortical_area(area2);
9189
9190 assert!(result.is_err());
9191 }
9192
9193 #[test]
9194 fn test_brain_region_management() {
9195 ConnectomeManager::reset_for_testing();
9196
9197 let instance = ConnectomeManager::instance();
9198 let mut manager = instance.write();
9199
9200 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
9201 let region_id_str = region_id.to_string();
9202 let root = BrainRegion::new(
9203 region_id,
9204 "Root".to_string(),
9205 feagi_structures::genomic::brain_regions::RegionType::Undefined,
9206 )
9207 .unwrap();
9208
9209 let initial_count = manager.get_brain_region_ids().len();
9210 manager.add_brain_region(root, None).unwrap();
9211
9212 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
9213 assert!(manager.get_brain_region(®ion_id_str).is_some());
9214 }
9215
9216 #[test]
9217 fn test_update_brain_region_description_property() {
9218 ConnectomeManager::reset_for_testing();
9219
9220 let instance = ConnectomeManager::instance();
9221 let mut manager = instance.write();
9222
9223 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
9224 let region_id_str = region_id.to_string();
9225 let root = BrainRegion::new(
9226 region_id,
9227 "Root".to_string(),
9228 feagi_structures::genomic::brain_regions::RegionType::Undefined,
9229 )
9230 .unwrap();
9231 manager.add_brain_region(root, None).unwrap();
9232
9233 let mut properties = std::collections::HashMap::new();
9234 properties.insert(
9235 "description".to_string(),
9236 serde_json::json!("Holds core physiology"),
9237 );
9238 manager
9239 .update_brain_region_properties(®ion_id_str, properties)
9240 .unwrap();
9241
9242 let updated = manager
9243 .get_brain_region(®ion_id_str)
9244 .expect("region exists after description update");
9245 assert_eq!(
9246 updated.get_property("description"),
9247 Some(&serde_json::json!("Holds core physiology"))
9248 );
9249 }
9250
9251 #[test]
9252 fn test_synapse_operations() {
9253 use feagi_npu_burst_engine::npu::RustNPU;
9254 use feagi_npu_burst_engine::TracingMutex;
9255 use std::sync::Arc;
9256
9257 use feagi_npu_burst_engine::backend::CPUBackend;
9259 use feagi_npu_burst_engine::DynamicNPU;
9260 use feagi_npu_runtime::StdRuntime;
9261
9262 let runtime = StdRuntime;
9263 let backend = CPUBackend::new();
9264 let npu_result =
9265 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
9266 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
9267 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
9268
9269 use feagi_structures::genomic::cortical_area::{
9271 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9272 };
9273 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9275 let area = CorticalArea::new(
9276 cortical_id,
9277 0, "Test Area".to_string(),
9279 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
9280 (0, 0, 0).into(), cortical_type,
9282 )
9283 .unwrap();
9284 let cortical_idx = manager.add_cortical_area(area).unwrap();
9285
9286 if let Some(npu_arc) = manager.get_npu() {
9288 if let Ok(mut npu_guard) = npu_arc.try_lock() {
9289 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
9290 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
9291 }
9292 }
9293 }
9294
9295 let neuron1_id = manager
9297 .add_neuron(
9298 &cortical_id,
9299 0,
9300 0,
9301 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
9313 .unwrap();
9314
9315 let neuron2_id = manager
9316 .add_neuron(
9317 &cortical_id,
9318 1,
9319 0,
9320 0, 100.0,
9322 f32::MAX, 0.1,
9324 -60.0,
9325 0,
9326 2,
9327 1.0,
9328 5,
9329 10,
9330 false,
9331 )
9332 .unwrap();
9333
9334 manager
9336 .create_synapse(
9337 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
9341 .unwrap();
9342
9343 println!("✅ Synapse creation test passed");
9346 }
9347
9348 #[test]
9349 fn test_apply_cortical_mapping_missing_rules_is_ok() {
9350 let mut manager = ConnectomeManager::new_for_testing();
9353
9354 use feagi_structures::genomic::cortical_area::{
9355 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9356 };
9357
9358 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
9359 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
9360
9361 let src_area = CorticalArea::new(
9362 src_id,
9363 0,
9364 "src".to_string(),
9365 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9366 (0, 0, 0).into(),
9367 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9368 )
9369 .unwrap();
9370
9371 let dst_area = CorticalArea::new(
9372 dst_id,
9373 1,
9374 "dst".to_string(),
9375 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9376 (0, 0, 0).into(),
9377 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9378 )
9379 .unwrap();
9380
9381 manager.add_cortical_area(src_area).unwrap();
9382 manager.add_cortical_area(dst_area).unwrap();
9383
9384 let count = manager
9386 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
9387 .unwrap();
9388 assert_eq!(count, 0);
9389
9390 manager
9392 .update_cortical_mapping(
9393 &src_id,
9394 &dst_id,
9395 vec![serde_json::json!({"morphology_id":"m1"})],
9396 )
9397 .unwrap();
9398 manager
9399 .update_cortical_mapping(&src_id, &dst_id, vec![])
9400 .unwrap();
9401
9402 let count2 = manager
9403 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
9404 .unwrap();
9405 assert_eq!(count2, 0);
9406 }
9407
9408 #[test]
9409 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
9410 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
9411 let mapping_dst = serde_json::json!({
9412 "csrc0002": [
9413 {"morphology_id": "m1"}
9414 ]
9415 });
9416 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
9417
9418 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
9419 .expect("legacy destination key should resolve");
9420 assert_eq!(rules.len(), 1);
9421 assert_eq!(
9422 rules[0].get("morphology_id").and_then(|v| v.as_str()),
9423 Some("m1")
9424 );
9425 }
9426
9427 #[test]
9428 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
9429 use feagi_npu_burst_engine::backend::CPUBackend;
9430 use feagi_npu_burst_engine::RustNPU;
9431 use feagi_npu_burst_engine::TracingMutex;
9432 use feagi_npu_runtime::StdRuntime;
9433 use feagi_structures::genomic::cortical_area::{
9434 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9435 };
9436 use std::sync::Arc;
9437
9438 let runtime = StdRuntime;
9439 let backend = CPUBackend::new();
9440 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9441 let dyn_npu = Arc::new(TracingMutex::new(
9442 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9443 "TestNPU",
9444 ));
9445 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9446
9447 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
9448 let area = CorticalArea::new(
9449 area_id,
9450 0,
9451 "n".to_string(),
9452 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9453 (0, 0, 0).into(),
9454 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9455 )
9456 .unwrap();
9457
9458 manager.add_cortical_area(area).unwrap();
9459 let nid = manager
9460 .add_neuron(
9461 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
9462 )
9463 .unwrap();
9464
9465 let props = manager
9466 .get_neuron_properties(nid)
9467 .expect("neuron properties");
9468 for key in [
9469 "consecutive_fire_count",
9470 "consecutive_fire_limit",
9471 "snooze_period",
9472 "membrane_potential",
9473 "threshold",
9474 "refractory_countdown",
9475 "mp_charge_accumulation",
9476 "neuron_type",
9477 "mp_driven_psp",
9478 "psp_uniform_distribution",
9479 "leak_coefficient",
9480 "resting_potential",
9481 "excitability",
9482 "threshold_limit",
9483 "refractory_period",
9484 ] {
9485 assert!(props.contains_key(key), "missing neuron state key: {key}");
9486 }
9487 }
9488
9489 #[test]
9490 fn test_mapping_deletion_prunes_synapses_between_areas() {
9491 use feagi_npu_burst_engine::backend::CPUBackend;
9492 use feagi_npu_burst_engine::RustNPU;
9493 use feagi_npu_burst_engine::TracingMutex;
9494 use feagi_npu_runtime::StdRuntime;
9495 use feagi_structures::genomic::cortical_area::{
9496 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9497 };
9498 use std::sync::Arc;
9499
9500 let runtime = StdRuntime;
9502 let backend = CPUBackend::new();
9503 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9504 let dyn_npu = Arc::new(TracingMutex::new(
9505 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9506 "TestNPU",
9507 ));
9508 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9509
9510 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
9512 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
9513
9514 let src_area = CorticalArea::new(
9515 src_id,
9516 0,
9517 "src".to_string(),
9518 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9519 (0, 0, 0).into(),
9520 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9521 )
9522 .unwrap();
9523 let dst_area = CorticalArea::new(
9524 dst_id,
9525 1,
9526 "dst".to_string(),
9527 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9528 (0, 0, 0).into(),
9529 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9530 )
9531 .unwrap();
9532
9533 manager.add_cortical_area(src_area).unwrap();
9534 manager.add_cortical_area(dst_area).unwrap();
9535
9536 let s0 = manager
9538 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9539 .unwrap();
9540 let s1 = manager
9541 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9542 .unwrap();
9543 let t0 = manager
9544 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9545 .unwrap();
9546 let t1 = manager
9547 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9548 .unwrap();
9549
9550 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
9552 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
9553
9554 {
9556 let mut npu = dyn_npu.lock().unwrap();
9557 npu.rebuild_synapse_index();
9558 assert_eq!(npu.get_synapse_count(), 2);
9559 }
9560
9561 manager
9563 .update_cortical_mapping(&src_id, &dst_id, vec![])
9564 .unwrap();
9565 let created = manager
9566 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9567 .unwrap();
9568 assert_eq!(created, 0);
9569
9570 {
9572 let mut npu = dyn_npu.lock().unwrap();
9573 npu.rebuild_synapse_index();
9575 assert_eq!(npu.get_synapse_count(), 0);
9576 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
9577 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
9578 }
9579 }
9580
9581 #[test]
9582 fn test_mapping_update_prunes_synapses_between_areas() {
9583 use feagi_npu_burst_engine::backend::CPUBackend;
9584 use feagi_npu_burst_engine::RustNPU;
9585 use feagi_npu_burst_engine::TracingMutex;
9586 use feagi_npu_runtime::StdRuntime;
9587 use feagi_structures::genomic::cortical_area::{
9588 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9589 };
9590 use std::sync::Arc;
9591
9592 let runtime = StdRuntime;
9594 let backend = CPUBackend::new();
9595 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9596 let dyn_npu = Arc::new(TracingMutex::new(
9597 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9598 "TestNPU",
9599 ));
9600 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9601
9602 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9604
9605 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
9608 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
9609
9610 let src_area = CorticalArea::new(
9611 src_id,
9612 0,
9613 "src".to_string(),
9614 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9615 (0, 0, 0).into(),
9616 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9617 )
9618 .unwrap();
9619 let dst_area = CorticalArea::new(
9620 dst_id,
9621 0,
9622 "dst".to_string(),
9623 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9624 (0, 0, 0).into(),
9625 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9626 )
9627 .unwrap();
9628
9629 manager.add_cortical_area(src_area).unwrap();
9630 manager.add_cortical_area(dst_area).unwrap();
9631
9632 let s0 = manager
9634 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9635 .unwrap();
9636 let s1 = manager
9637 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9638 .unwrap();
9639 let t0 = manager
9640 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9641 .unwrap();
9642 let t1 = manager
9643 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9644 .unwrap();
9645
9646 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
9648 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
9649
9650 {
9652 let mut npu = dyn_npu.lock().unwrap();
9653 npu.rebuild_synapse_index();
9654 assert_eq!(npu.get_synapse_count(), 2);
9655 }
9656
9657 manager
9663 .update_cortical_mapping(
9664 &src_id,
9665 &dst_id,
9666 vec![serde_json::json!({
9667 "morphology_id": "episodic_memory",
9668 "morphology_scalar": [1],
9669 "postSynapticCurrent_multiplier": 1,
9670 "plasticity_flag": false,
9671 "plasticity_constant": 0,
9672 "ltp_multiplier": 0,
9673 "ltd_multiplier": 0,
9674 "plasticity_window": 0,
9675 })],
9676 )
9677 .unwrap();
9678 let created = manager
9679 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9680 .unwrap();
9681 assert_eq!(created, 0);
9682
9683 {
9685 let mut npu = dyn_npu.lock().unwrap();
9686 npu.rebuild_synapse_index();
9688 assert_eq!(npu.get_synapse_count(), 0);
9689 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
9690 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
9691 }
9692 }
9693
9694 #[test]
9695 fn test_upstream_area_tracking() {
9696 use crate::models::cortical_area::CorticalArea;
9698 use feagi_npu_burst_engine::backend::CPUBackend;
9699 use feagi_npu_burst_engine::TracingMutex;
9700 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9701 use feagi_npu_runtime::StdRuntime;
9702 use feagi_structures::genomic::cortical_area::{
9703 CorticalAreaDimensions, CorticalAreaType, CorticalID,
9704 };
9705
9706 let runtime = StdRuntime;
9708 let backend = CPUBackend::new();
9709 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9710 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9711 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9712
9713 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9716
9717 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
9719 let src_area = CorticalArea::new(
9720 src_id,
9721 0,
9722 "Source Area".to_string(),
9723 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9724 (0, 0, 0).into(),
9725 CorticalAreaType::Custom(
9726 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9727 ),
9728 )
9729 .unwrap();
9730 let src_idx = manager.add_cortical_area(src_area).unwrap();
9731
9732 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
9734 let dst_area = CorticalArea::new(
9735 dst_id,
9736 0,
9737 "Dest Area".to_string(),
9738 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9739 (0, 0, 0).into(),
9740 CorticalAreaType::Custom(
9741 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9742 ),
9743 )
9744 .unwrap();
9745 manager.add_cortical_area(dst_area).unwrap();
9746
9747 {
9749 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
9750 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
9751 assert!(
9752 upstream.as_array().unwrap().is_empty(),
9753 "Upstream areas should be empty initially"
9754 );
9755 }
9756
9757 let mapping_data = vec![serde_json::json!({
9759 "morphology_id": "episodic_memory",
9760 "morphology_scalar": 1,
9761 "postSynapticCurrent_multiplier": 1.0,
9762 })];
9763 manager
9764 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
9765 .unwrap();
9766 manager
9767 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9768 .unwrap();
9769
9770 {
9772 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
9773 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
9774 assert_eq!(
9775 upstream_areas[0], src_idx,
9776 "Upstream area should be src_idx"
9777 );
9778 }
9779
9780 manager
9782 .update_cortical_mapping(&src_id, &dst_id, vec![])
9783 .unwrap();
9784 manager
9785 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9786 .unwrap();
9787
9788 {
9790 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
9791 assert_eq!(
9792 upstream_areas.len(),
9793 0,
9794 "Should have 0 upstream areas after deletion"
9795 );
9796 }
9797 }
9798
9799 #[test]
9800 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
9801 use crate::models::cortical_area::CorticalArea;
9802 use feagi_npu_burst_engine::backend::CPUBackend;
9803 use feagi_npu_burst_engine::TracingMutex;
9804 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9805 use feagi_npu_runtime::StdRuntime;
9806 use feagi_structures::genomic::cortical_area::{
9807 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
9808 };
9809 use std::sync::Arc;
9810
9811 let runtime = StdRuntime;
9812 let backend = CPUBackend::new();
9813 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9814 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9815 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9816 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9817
9818 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
9819 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
9820 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
9821 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
9822
9823 let a1_area = CorticalArea::new(
9824 a1_id,
9825 0,
9826 "A1".to_string(),
9827 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9828 (0, 0, 0).into(),
9829 CorticalAreaType::Custom(
9830 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9831 ),
9832 )
9833 .unwrap();
9834 let a2_area = CorticalArea::new(
9835 a2_id,
9836 0,
9837 "A2".to_string(),
9838 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9839 (0, 0, 0).into(),
9840 CorticalAreaType::Custom(
9841 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9842 ),
9843 )
9844 .unwrap();
9845
9846 let mut m1_area = CorticalArea::new(
9847 m1_id,
9848 0,
9849 "M1".to_string(),
9850 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9851 (0, 0, 0).into(),
9852 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9853 )
9854 .unwrap();
9855 m1_area
9856 .properties
9857 .insert("is_mem_type".to_string(), serde_json::json!(true));
9858 m1_area
9859 .properties
9860 .insert("temporal_depth".to_string(), serde_json::json!(1));
9861
9862 let mut m2_area = CorticalArea::new(
9863 m2_id,
9864 0,
9865 "M2".to_string(),
9866 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9867 (0, 0, 0).into(),
9868 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9869 )
9870 .unwrap();
9871 m2_area
9872 .properties
9873 .insert("is_mem_type".to_string(), serde_json::json!(true));
9874 m2_area
9875 .properties
9876 .insert("temporal_depth".to_string(), serde_json::json!(1));
9877
9878 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
9879 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
9880 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
9881 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
9882
9883 manager
9884 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9885 .unwrap();
9886 manager
9887 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9888 .unwrap();
9889
9890 let episodic_mapping = vec![serde_json::json!({
9891 "morphology_id": "episodic_memory",
9892 "morphology_scalar": 1,
9893 "postSynapticCurrent_multiplier": 1.0,
9894 })];
9895 manager
9896 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
9897 .unwrap();
9898 manager
9899 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
9900 .unwrap();
9901 manager
9902 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
9903 .unwrap();
9904 manager
9905 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
9906 .unwrap();
9907
9908 let assoc_mapping = vec![serde_json::json!({
9909 "morphology_id": "associative_memory",
9910 "morphology_scalar": 1,
9911 "postSynapticCurrent_multiplier": 1.0,
9912 "plasticity_flag": true,
9913 "plasticity_constant": 1,
9914 "ltp_multiplier": 1,
9915 "ltd_multiplier": 1,
9916 "plasticity_window": 5,
9917 })];
9918 manager
9919 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
9920 .unwrap();
9921 manager
9922 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
9923 .unwrap();
9924 manager
9926 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
9927 .unwrap();
9928 manager
9929 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
9930 .unwrap();
9931
9932 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
9933 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
9934 assert_eq!(
9935 upstream_m1.len(),
9936 2,
9937 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
9938 );
9939 assert_eq!(
9940 upstream_m2.len(),
9941 2,
9942 "M2 should have A2 and M1 as upstreams"
9943 );
9944
9945 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
9946 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
9947
9948 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
9949 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
9950 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
9951 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
9952 assert!(upstream_m1.contains(&a1_idx));
9953 assert!(upstream_m1.contains(&m2_idx));
9954 assert!(upstream_m2.contains(&a2_idx));
9955 assert!(upstream_m2.contains(&m1_idx));
9956
9957 {
9959 let mut npu_lock = dyn_npu.lock().unwrap();
9960 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
9961 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
9962 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
9963 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
9964 npu_lock.process_burst().expect("Burst processing failed");
9965 }
9966
9967 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
9968 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
9969 assert_eq!(
9970 upstream_m1.len(),
9971 2,
9972 "M1 should keep 2 upstreams after firing"
9973 );
9974 assert_eq!(
9975 upstream_m2.len(),
9976 2,
9977 "M2 should keep 2 upstreams after firing"
9978 );
9979
9980 let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
9981 let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
9982 assert_eq!(
9983 episodic_upstream_m1,
9984 vec![a1_idx],
9985 "Episodic upstream list for M1 should exclude associative-only memory source M2"
9986 );
9987 assert_eq!(
9988 episodic_upstream_m2,
9989 vec![a2_idx],
9990 "Episodic upstream list for M2 should exclude associative-only memory source M1"
9991 );
9992 }
9993
9994 #[test]
9995 fn test_memory_to_non_memory_requires_associative_morphology() {
9996 use crate::models::cortical_area::CorticalArea;
9997 use feagi_structures::genomic::cortical_area::{
9998 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
9999 MemoryCorticalType,
10000 };
10001
10002 let mut manager = ConnectomeManager::new_for_testing();
10003 let memory_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10004 let destination_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
10005 let memory_area = CorticalArea::new(
10006 memory_id,
10007 0,
10008 "Memory Area".to_string(),
10009 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10010 (0, 0, 0).into(),
10011 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10012 )
10013 .unwrap();
10014 let destination_area = CorticalArea::new(
10015 destination_id,
10016 0,
10017 "Destination Area".to_string(),
10018 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10019 (1, 0, 0).into(),
10020 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10021 )
10022 .unwrap();
10023 manager.add_cortical_area(memory_area).unwrap();
10024 manager.add_cortical_area(destination_area).unwrap();
10025
10026 let invalid = manager.update_cortical_mapping(
10027 &memory_id,
10028 &destination_id,
10029 vec![serde_json::json!({"morphology_id": "episodic_memory"})],
10030 );
10031 assert!(matches!(invalid, Err(BduError::InvalidMorphology(_))));
10032
10033 manager
10034 .update_cortical_mapping(
10035 &memory_id,
10036 &destination_id,
10037 vec![serde_json::json!({"morphology_id": "associative_memory"})],
10038 )
10039 .unwrap();
10040 }
10041
10042 #[test]
10043 fn test_memory_twin_created_for_memory_mapping() {
10044 use crate::models::cortical_area::CorticalArea;
10045 use feagi_npu_burst_engine::backend::CPUBackend;
10046 use feagi_npu_burst_engine::TracingMutex;
10047 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10048 use feagi_npu_runtime::StdRuntime;
10049 use feagi_structures::genomic::cortical_area::{
10050 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10051 MemoryCorticalType,
10052 };
10053 use std::sync::Arc;
10054
10055 let runtime = StdRuntime;
10056 let backend = CPUBackend::new();
10057 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10058 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10059 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10060 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10061
10062 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
10063 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10064
10065 let src_area = CorticalArea::new(
10066 src_id,
10067 0,
10068 "Source Area".to_string(),
10069 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10070 (0, 0, 0).into(),
10071 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10072 )
10073 .unwrap();
10074 let mut dst_area = CorticalArea::new(
10075 dst_id,
10076 0,
10077 "Memory Area".to_string(),
10078 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10079 (0, 0, 0).into(),
10080 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10081 )
10082 .unwrap();
10083 dst_area
10084 .properties
10085 .insert("is_mem_type".to_string(), serde_json::json!(true));
10086 dst_area
10087 .properties
10088 .insert("temporal_depth".to_string(), serde_json::json!(1));
10089
10090 manager.add_cortical_area(src_area).unwrap();
10091 manager.add_cortical_area(dst_area).unwrap();
10092
10093 let mapping_data = vec![serde_json::json!({
10094 "morphology_id": "episodic_memory",
10095 "morphology_scalar": 1,
10096 "postSynapticCurrent_multiplier": 1.0,
10097 })];
10098 manager
10099 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
10100 .unwrap();
10101 manager
10102 .regenerate_synapses_for_mapping(&src_id, &dst_id)
10103 .unwrap();
10104
10105 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
10106 let twin_map = memory_area
10107 .properties
10108 .get("memory_twin_areas")
10109 .and_then(|v| v.as_object())
10110 .expect("memory_twin_areas should be set");
10111 let twin_id_str = twin_map
10112 .get(&src_id.as_base_64())
10113 .and_then(|v| v.as_str())
10114 .expect("Missing twin entry for upstream area");
10115 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
10116 let mapping = memory_area
10117 .properties
10118 .get("cortical_mapping_dst")
10119 .and_then(|v| v.as_object())
10120 .and_then(|map| map.get(&twin_id.as_base_64()))
10121 .and_then(|v| v.as_array())
10122 .expect("Missing memory replay mapping for twin area");
10123 let uses_replay = mapping.iter().any(|rule| {
10124 rule.get("morphology_id")
10125 .and_then(|v| v.as_str())
10126 .is_some_and(|id| id == "memory_replay")
10127 });
10128 assert!(uses_replay, "Expected memory_replay mapping for twin area");
10129
10130 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10131 assert!(matches!(
10132 twin_area.cortical_type,
10133 CorticalAreaType::Custom(_)
10134 ));
10135 assert_eq!(
10136 twin_area
10137 .properties
10138 .get("memory_twin_of")
10139 .and_then(|v| v.as_str()),
10140 Some(src_id.as_base_64().as_str())
10141 );
10142 assert_eq!(
10143 twin_area
10144 .properties
10145 .get("memory_twin_for")
10146 .and_then(|v| v.as_str()),
10147 Some(dst_id.as_base_64().as_str())
10148 );
10149 }
10150
10151 #[test]
10152 fn test_associative_memory_between_memory_areas_creates_synapses() {
10153 use crate::models::cortical_area::CorticalArea;
10154 use feagi_npu_burst_engine::backend::CPUBackend;
10155 use feagi_npu_burst_engine::TracingMutex;
10156 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10157 use feagi_npu_runtime::StdRuntime;
10158 use feagi_structures::genomic::cortical_area::{
10159 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10160 };
10161 use std::sync::Arc;
10162
10163 let runtime = StdRuntime;
10164 let backend = CPUBackend::new();
10165 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10166 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10167 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10168 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10169
10170 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
10171 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
10172
10173 let mut m1_area = CorticalArea::new(
10174 m1_id,
10175 0,
10176 "Memory M1".to_string(),
10177 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10178 (0, 0, 0).into(),
10179 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10180 )
10181 .unwrap();
10182 m1_area
10183 .properties
10184 .insert("is_mem_type".to_string(), serde_json::json!(true));
10185 m1_area
10186 .properties
10187 .insert("temporal_depth".to_string(), serde_json::json!(1));
10188
10189 let mut m2_area = CorticalArea::new(
10190 m2_id,
10191 0,
10192 "Memory M2".to_string(),
10193 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10194 (0, 0, 0).into(),
10195 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10196 )
10197 .unwrap();
10198 m2_area
10199 .properties
10200 .insert("is_mem_type".to_string(), serde_json::json!(true));
10201 m2_area
10202 .properties
10203 .insert("temporal_depth".to_string(), serde_json::json!(1));
10204
10205 manager.add_cortical_area(m1_area).unwrap();
10206 manager.add_cortical_area(m2_area).unwrap();
10207
10208 manager
10209 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10210 .unwrap();
10211 manager
10212 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10213 .unwrap();
10214
10215 let mapping_data = vec![serde_json::json!({
10216 "morphology_id": "associative_memory",
10217 "morphology_scalar": 1,
10218 "postSynapticCurrent_multiplier": 1.0,
10219 "plasticity_flag": true,
10220 "plasticity_constant": 1,
10221 "ltp_multiplier": 1,
10222 "ltd_multiplier": 1,
10223 "plasticity_window": 5,
10224 })];
10225 manager
10226 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
10227 .unwrap();
10228 let created = manager
10229 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
10230 .unwrap();
10231 assert!(
10232 created > 0,
10233 "Expected associative memory mapping between memory areas to create synapses"
10234 );
10235 let npu_guard = dyn_npu.lock().unwrap();
10236 let assoc_tagged =
10237 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
10238 assert!(
10239 assoc_tagged >= 1,
10240 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
10241 );
10242 }
10243
10244 #[test]
10245 fn test_scan_twin_uses_field_xy_and_class_count() {
10246 use crate::models::cortical_area::CorticalArea;
10247 use feagi_npu_burst_engine::backend::CPUBackend;
10248 use feagi_npu_burst_engine::TracingMutex;
10249 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10250 use feagi_npu_runtime::StdRuntime;
10251 use feagi_structures::genomic::cortical_area::{
10252 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10253 MemoryCorticalType,
10254 };
10255 use std::sync::Arc;
10256
10257 let runtime = StdRuntime;
10258 let backend = CPUBackend::new();
10259 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10260 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10261 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10262 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10263
10264 let field_id = CorticalID::try_from_bytes(b"cfld0001").unwrap();
10265 let class_id = CorticalID::try_from_bytes(b"ccls0001").unwrap();
10266 let mem_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10267
10268 let field_area = CorticalArea::new(
10269 field_id,
10270 0,
10271 "Field".to_string(),
10272 CorticalAreaDimensions::new(8, 6, 4).unwrap(),
10273 (0, 0, 0).into(),
10274 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10275 )
10276 .unwrap();
10277 let class_area = CorticalArea::new(
10278 class_id,
10279 0,
10280 "Class".to_string(),
10281 CorticalAreaDimensions::new(1, 1, 5).unwrap(),
10282 (20, 0, 0).into(),
10283 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10284 )
10285 .unwrap();
10286 let mut mem_area = CorticalArea::new(
10287 mem_id,
10288 0,
10289 "KernelMem".to_string(),
10290 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10291 (40, 0, 0).into(),
10292 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10293 )
10294 .unwrap();
10295 mem_area
10296 .properties
10297 .insert("is_mem_type".to_string(), serde_json::json!(true));
10298 mem_area.properties.insert(
10299 "classifier_class_area_id".to_string(),
10300 serde_json::json!(class_id.as_base_64()),
10301 );
10302
10303 manager.add_cortical_area(field_area).unwrap();
10304 manager.add_cortical_area(class_area).unwrap();
10305 manager.add_cortical_area(mem_area).unwrap();
10306
10307 let mapping_data = vec![serde_json::json!({
10308 "morphology_id": "episodic_scan",
10309 "morphology_scalar": 1,
10310 "postSynapticCurrent_multiplier": 1.0,
10311 })];
10312 manager
10313 .update_cortical_mapping(&field_id, &mem_id, mapping_data)
10314 .unwrap();
10315 manager
10316 .regenerate_synapses_for_mapping(&field_id, &mem_id)
10317 .unwrap();
10318
10319 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
10320 let twin_map = memory_area
10321 .properties
10322 .get("memory_twin_areas")
10323 .and_then(|v| v.as_object())
10324 .expect("memory_twin_areas should be set");
10325 let twin_id_str = twin_map
10326 .get(&field_id.as_base_64())
10327 .and_then(|v| v.as_str())
10328 .expect("Missing scan twin entry");
10329 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
10330 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10331 assert_eq!(twin_area.dimensions.width, 8);
10332 assert_eq!(twin_area.dimensions.height, 6);
10333 assert_eq!(twin_area.dimensions.depth, 5);
10334 assert_eq!(
10335 twin_area
10336 .properties
10337 .get("scan_twin")
10338 .and_then(|v| v.as_bool()),
10339 Some(true)
10340 );
10341 let has_replay = memory_area
10342 .properties
10343 .get("cortical_mapping_dst")
10344 .and_then(|v| v.as_object())
10345 .and_then(|map| map.get(&twin_id.as_base_64()))
10346 .is_some();
10347 assert!(!has_replay, "scan twin must not receive memory_replay");
10348 let episodic_upstreams = manager.get_episodic_memory_upstream_cortical_areas(&mem_id);
10349 let field_idx = manager.get_cortical_idx(&field_id).unwrap();
10350 assert!(
10351 !episodic_upstreams.contains(&field_idx),
10352 "scan source must not enter episodic hash upstreams"
10353 );
10354 }
10355
10356 #[test]
10357 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
10358 use crate::models::cortical_area::CorticalArea;
10359 use feagi_npu_burst_engine::backend::CPUBackend;
10360 use feagi_npu_burst_engine::TracingMutex;
10361 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10362 use feagi_npu_runtime::StdRuntime;
10363 use feagi_structures::genomic::cortical_area::{
10364 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10365 MemoryCorticalType,
10366 };
10367 use std::sync::Arc;
10368
10369 let runtime = StdRuntime;
10370 let backend = CPUBackend::new();
10371 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10372 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10373 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10374 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10375
10376 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
10377 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
10378
10379 let src_area = CorticalArea::new(
10380 src_id,
10381 0,
10382 "Source Area".to_string(),
10383 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10384 (0, 0, 0).into(),
10385 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10386 )
10387 .unwrap();
10388 let mut mem_area = CorticalArea::new(
10389 mem_id,
10390 0,
10391 "Memory Area".to_string(),
10392 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10393 (0, 0, 0).into(),
10394 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10395 )
10396 .unwrap();
10397 mem_area
10398 .properties
10399 .insert("is_mem_type".to_string(), serde_json::json!(true));
10400 mem_area
10401 .properties
10402 .insert("temporal_depth".to_string(), serde_json::json!(1));
10403
10404 manager.add_cortical_area(src_area).unwrap();
10405 manager.add_cortical_area(mem_area).unwrap();
10406
10407 let twin_id = manager
10408 .build_memory_twin_id(&mem_id, &src_id)
10409 .expect("Failed to build twin id");
10410 let twin_area = CorticalArea::new(
10411 twin_id,
10412 0,
10413 "Source Area_twin".to_string(),
10414 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10415 (0, 0, 0).into(),
10416 CorticalAreaType::Custom(
10417 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10418 ),
10419 )
10420 .unwrap();
10421 manager.add_cortical_area(twin_area).unwrap();
10422
10423 let repaired = manager
10424 .ensure_memory_twin_area(&mem_id, &src_id)
10425 .expect("Failed to repair twin");
10426 assert_eq!(repaired, twin_id);
10427
10428 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
10429 let twin_map = mem_area
10430 .properties
10431 .get("memory_twin_areas")
10432 .and_then(|v| v.as_object())
10433 .expect("memory_twin_areas should be set");
10434 let twin_id_str = twin_map
10435 .get(&src_id.as_base_64())
10436 .and_then(|v| v.as_str())
10437 .expect("Missing twin entry for upstream area");
10438 assert_eq!(twin_id_str, twin_id.as_base_64());
10439
10440 let replay_map = mem_area
10441 .properties
10442 .get("cortical_mapping_dst")
10443 .and_then(|v| v.as_object())
10444 .and_then(|map| map.get(&twin_id.as_base_64()))
10445 .and_then(|v| v.as_array())
10446 .expect("Missing memory replay mapping for twin area");
10447 let uses_replay = replay_map.iter().any(|rule| {
10448 rule.get("morphology_id")
10449 .and_then(|v| v.as_str())
10450 .is_some_and(|id| id == "memory_replay")
10451 });
10452 assert!(uses_replay, "Expected memory_replay mapping for twin area");
10453
10454 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10455 assert_eq!(
10456 twin_area
10457 .properties
10458 .get("memory_twin_of")
10459 .and_then(|v| v.as_str()),
10460 Some(src_id.as_base_64().as_str())
10461 );
10462 assert_eq!(
10463 twin_area
10464 .properties
10465 .get("memory_twin_for")
10466 .and_then(|v| v.as_str()),
10467 Some(mem_id.as_base_64().as_str())
10468 );
10469 }
10470
10471 #[test]
10472 fn classifier_mapping_change_updates_kernel_slot_not_field() {
10473 let mut manager = ConnectomeManager::new_for_testing();
10474 let mut classifier = feagi_structures::genomic::classifiers::Classifier {
10475 classifier_id: "clf-1".to_string(),
10476 name: "demo".to_string(),
10477 parent_region_id: "root".to_string(),
10478 coordinates_3d: [0, 0, 0],
10479 kernel_area_id: None,
10480 class_area_id: None,
10481 fields: Vec::new(),
10482 kernel_memory_id: "mkmem1".to_string(),
10483 class_memory_id: "mcmem1".to_string(),
10484 properties: HashMap::new(),
10485 };
10486 manager.upsert_classifier(classifier.clone());
10487 let hash_after_upsert = feagi_state_manager::StateManager::instance()
10488 .read()
10489 .get_classifiers_hash();
10490 assert_ne!(hash_after_upsert, 0, "upsert must publish classifiers_hash");
10491 let ignored = manager.apply_classifier_mapping_change(
10492 "cfield",
10493 "mkmem1",
10494 feagi_structures::genomic::classifiers::CLASSIFIER_SCAN_MORPHOLOGY,
10495 false,
10496 );
10497 assert_eq!(ignored, 0);
10498 assert!(manager.get_classifier("clf-1").unwrap().fields.is_empty());
10499 let updated = manager.apply_classifier_mapping_change(
10500 "ckern1",
10501 "mkmem1",
10502 feagi_structures::genomic::classifiers::CLASSIFIER_KERNEL_MORPHOLOGY,
10503 false,
10504 );
10505 assert_eq!(updated, 1);
10506 classifier = manager.get_classifier("clf-1").cloned().unwrap();
10507 assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
10508 let hash_after_kernel_bind = feagi_state_manager::StateManager::instance()
10509 .read()
10510 .get_classifiers_hash();
10511 assert_ne!(
10512 hash_after_kernel_bind, hash_after_upsert,
10513 "binding a classifier input must change classifiers_hash"
10514 );
10515 }
10516
10517 #[test]
10518 fn deleting_referenced_input_clears_classifier_slot() {
10519 let mut manager = ConnectomeManager::new_for_testing();
10520 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
10521 classifier_id: "clf-1".to_string(),
10522 name: "demo".to_string(),
10523 parent_region_id: "root".to_string(),
10524 coordinates_3d: [0, 0, 0],
10525 kernel_area_id: Some("ckern1".to_string()),
10526 class_area_id: Some("ccls01".to_string()),
10527 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
10528 field_area_id: "cfield".to_string(),
10529 scan_twin_id: "cscan1".to_string(),
10530 }],
10531 kernel_memory_id: "mkmem1".to_string(),
10532 class_memory_id: "mcmem1".to_string(),
10533 properties: HashMap::new(),
10534 });
10535 assert_eq!(manager.clear_classifier_inputs_for_area("cfield"), 1);
10536 let classifier = manager.get_classifier("clf-1").unwrap();
10537 assert!(classifier.binding_for_field("cfield").is_none());
10538 assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
10539 assert!(manager.classifiers_owning_area("cfield").is_empty());
10540 assert_eq!(manager.classifiers_owning_area("mkmem1").len(), 1);
10541 let hash_after_clear = feagi_state_manager::StateManager::instance()
10542 .read()
10543 .get_classifiers_hash();
10544 manager.remove_classifier("clf-1");
10545 let hash_after_remove = feagi_state_manager::StateManager::instance()
10546 .read()
10547 .get_classifiers_hash();
10548 assert_ne!(
10549 hash_after_remove, hash_after_clear,
10550 "removing a classifier must change classifiers_hash"
10551 );
10552 }
10553
10554 #[test]
10555 fn classifier_assembly_mappings_do_not_become_region_io() {
10556 use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
10557 use feagi_structures::genomic::cortical_area::{
10558 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10559 MemoryCorticalType,
10560 };
10561
10562 let mut manager = ConnectomeManager::new_for_testing();
10563 let root_id = RegionID::new();
10564 let child_id = RegionID::new();
10565 let root_key = root_id.to_string();
10566 let child_key = child_id.to_string();
10567 manager
10568 .add_brain_region(
10569 BrainRegion::new(root_id, "Root".to_string(), RegionType::Undefined).unwrap(),
10570 None,
10571 )
10572 .unwrap();
10573 manager
10574 .add_brain_region(
10575 BrainRegion::new(child_id, "Child".to_string(), RegionType::Undefined).unwrap(),
10576 Some(root_key.clone()),
10577 )
10578 .unwrap();
10579
10580 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
10581 let mem_id = CorticalID::try_from_bytes(b"mclfmem1").unwrap();
10582 let regular_field_id = CorticalID::try_from_bytes(b"cregfld1").unwrap();
10583 let regular_mem_id = CorticalID::try_from_bytes(b"mregmem1").unwrap();
10584
10585 let mut field = CorticalArea::new(
10586 field_id,
10587 0,
10588 "field".to_string(),
10589 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10590 (0, 0, 0).into(),
10591 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10592 )
10593 .unwrap();
10594 field.properties.insert(
10595 "parent_region_id".to_string(),
10596 serde_json::json!(child_key.clone()),
10597 );
10598 field.properties.insert(
10599 "cortical_mapping_dst".to_string(),
10600 serde_json::json!({
10601 mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
10602 }),
10603 );
10604
10605 let mut classifier_mem = CorticalArea::new(
10606 mem_id,
10607 0,
10608 "clf_kernel_mem".to_string(),
10609 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10610 (10, 0, 0).into(),
10611 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10612 )
10613 .unwrap();
10614 classifier_mem.properties.insert(
10615 "parent_region_id".to_string(),
10616 serde_json::json!(root_key.clone()),
10617 );
10618 classifier_mem
10619 .properties
10620 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10621 classifier_mem.properties.insert(
10622 "classifier_role".to_string(),
10623 serde_json::json!("kernel_memory"),
10624 );
10625
10626 let mut regular_field = CorticalArea::new(
10627 regular_field_id,
10628 0,
10629 "regular_field".to_string(),
10630 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10631 (20, 0, 0).into(),
10632 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10633 )
10634 .unwrap();
10635 regular_field.properties.insert(
10636 "parent_region_id".to_string(),
10637 serde_json::json!(child_key.clone()),
10638 );
10639 regular_field.properties.insert(
10640 "cortical_mapping_dst".to_string(),
10641 serde_json::json!({
10642 regular_mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
10643 }),
10644 );
10645
10646 let mut regular_mem = CorticalArea::new(
10647 regular_mem_id,
10648 0,
10649 "regular_mem".to_string(),
10650 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10651 (30, 0, 0).into(),
10652 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10653 )
10654 .unwrap();
10655 regular_mem.properties.insert(
10656 "parent_region_id".to_string(),
10657 serde_json::json!(root_key.clone()),
10658 );
10659
10660 manager.add_cortical_area(field).unwrap();
10661 manager.add_cortical_area(classifier_mem).unwrap();
10662 manager.add_cortical_area(regular_field).unwrap();
10663 manager.add_cortical_area(regular_mem).unwrap();
10664
10665 let io = manager.recompute_brain_region_io_registry().unwrap();
10666 let child_outputs = &io.get(&child_key).expect("child region io").1;
10667 assert!(
10668 !child_outputs.contains(&field_id.as_base_64()),
10669 "classifier input must stay inside the circuit; got outputs {child_outputs:?}"
10670 );
10671 assert!(
10672 child_outputs.contains(®ular_field_id.as_base_64()),
10673 "non-classifier cross-region mappings must still become region outputs; got {child_outputs:?}"
10674 );
10675 }
10676
10677 #[test]
10678 fn classifier_assembly_area_is_detected_from_role_or_flag() {
10679 use feagi_structures::genomic::cortical_area::{
10680 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10681 };
10682
10683 let mut flagged = CorticalArea::new(
10684 CorticalID::try_from_bytes(b"mflagged").unwrap(),
10685 0,
10686 "flagged".to_string(),
10687 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10688 (0, 0, 0).into(),
10689 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10690 )
10691 .unwrap();
10692 flagged
10693 .properties
10694 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10695 assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
10696 &flagged
10697 ));
10698
10699 let mut role = CorticalArea::new(
10700 CorticalID::try_from_bytes(b"mrole001").unwrap(),
10701 0,
10702 "role".to_string(),
10703 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10704 (0, 0, 0).into(),
10705 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10706 )
10707 .unwrap();
10708 role.properties.insert(
10709 "classifier_role".to_string(),
10710 serde_json::json!("scan_twin"),
10711 );
10712 assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
10713 &role
10714 ));
10715
10716 let plain = CorticalArea::new(
10717 CorticalID::try_from_bytes(b"mplain01").unwrap(),
10718 0,
10719 "plain".to_string(),
10720 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10721 (0, 0, 0).into(),
10722 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10723 )
10724 .unwrap();
10725 assert!(!ConnectomeManager::area_belongs_to_classifier_assembly(
10726 &plain
10727 ));
10728 }
10729
10730 #[test]
10731 fn rekey_memory_twin_source_retargets_classifier_stamp() {
10732 use feagi_structures::genomic::cortical_area::{
10733 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10734 MemoryCorticalType,
10735 };
10736
10737 let mut manager = ConnectomeManager::new_for_testing();
10738 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
10739 let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
10740 let mut mem_area = CorticalArea::new(
10741 mem_id,
10742 0,
10743 "kernel_mem".to_string(),
10744 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10745 (0, 0, 0).into(),
10746 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10747 )
10748 .unwrap();
10749 mem_area.properties.insert(
10750 "classifier_role".to_string(),
10751 serde_json::json!("kernel_memory"),
10752 );
10753 mem_area.properties.insert(
10754 "memory_twin_areas".to_string(),
10755 serde_json::json!({ "cfield01": stamp_id.as_base_64() }),
10756 );
10757 let mut stamp = CorticalArea::new(
10758 stamp_id,
10759 0,
10760 "stamp".to_string(),
10761 CorticalAreaDimensions::new(4, 4, 2).unwrap(),
10762 (0, 0, 0).into(),
10763 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10764 )
10765 .unwrap();
10766 stamp.properties.insert(
10767 "classifier_role".to_string(),
10768 serde_json::json!("scan_twin"),
10769 );
10770 stamp
10771 .properties
10772 .insert("memory_twin_of".to_string(), serde_json::json!("cfield01"));
10773 manager.add_cortical_area(mem_area).unwrap();
10774 manager.add_cortical_area(stamp).unwrap();
10775 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
10776 classifier_id: "clf-1".to_string(),
10777 name: "demo".to_string(),
10778 parent_region_id: "root".to_string(),
10779 coordinates_3d: [0, 0, 0],
10780 kernel_area_id: Some("ckern001".to_string()),
10781 class_area_id: Some("ccls0001".to_string()),
10782 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
10783 field_area_id: "cfield01".to_string(),
10784 scan_twin_id: stamp_id.as_base_64(),
10785 }],
10786 kernel_memory_id: mem_id.as_base_64(),
10787 class_memory_id: "mcmem001".to_string(),
10788 properties: HashMap::new(),
10789 });
10790
10791 manager
10792 .rekey_memory_twin_source(&mem_id.as_base_64(), "cfield01", "cfield02")
10793 .unwrap();
10794
10795 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
10796 let twins = memory_area
10797 .properties
10798 .get("memory_twin_areas")
10799 .and_then(|value| value.as_object())
10800 .expect("memory_twin_areas");
10801 assert_eq!(
10802 twins.get("cfield02").and_then(|value| value.as_str()),
10803 Some(stamp_id.as_base_64().as_str())
10804 );
10805 assert!(!twins.contains_key("cfield01"));
10806 let stamp = manager.get_cortical_area(&stamp_id).unwrap();
10807 assert_eq!(
10808 stamp
10809 .properties
10810 .get("memory_twin_of")
10811 .and_then(|value| value.as_str()),
10812 Some("cfield02")
10813 );
10814 }
10815
10816 #[test]
10817 fn classifier_stamp_survives_field_mapping_delete() {
10818 use feagi_npu_burst_engine::backend::CPUBackend;
10819 use feagi_npu_burst_engine::TracingMutex;
10820 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10821 use feagi_npu_runtime::StdRuntime;
10822 use feagi_structures::genomic::cortical_area::{
10823 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10824 IOCorticalAreaConfigurationFlag, MemoryCorticalType,
10825 };
10826 use std::sync::Arc;
10827
10828 let runtime = StdRuntime;
10829 let backend = CPUBackend::new();
10830 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10831 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10832 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
10833 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10834
10835 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
10836 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
10837 let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
10838 let field_area = CorticalArea::new(
10839 field_id,
10840 0,
10841 "Field".to_string(),
10842 CorticalAreaDimensions::new(4, 3, 1).unwrap(),
10843 (0, 0, 0).into(),
10844 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10845 )
10846 .unwrap();
10847 let mut mem_area = CorticalArea::new(
10848 mem_id,
10849 0,
10850 "kernel_mem".to_string(),
10851 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10852 (10, 0, 0).into(),
10853 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10854 )
10855 .unwrap();
10856 mem_area.properties.insert(
10857 "classifier_role".to_string(),
10858 serde_json::json!("kernel_memory"),
10859 );
10860 mem_area
10861 .properties
10862 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10863 mem_area.properties.insert(
10864 "memory_twin_areas".to_string(),
10865 serde_json::json!({ field_id.as_base_64(): stamp_id.as_base_64() }),
10866 );
10867 let mut stamp = CorticalArea::new(
10868 stamp_id,
10869 0,
10870 "stamp".to_string(),
10871 CorticalAreaDimensions::new(4, 3, 2).unwrap(),
10872 (20, 0, 0).into(),
10873 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10874 )
10875 .unwrap();
10876 stamp.properties.insert(
10877 "classifier_role".to_string(),
10878 serde_json::json!("scan_twin"),
10879 );
10880 stamp
10881 .properties
10882 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10883 stamp.properties.insert(
10884 "memory_twin_of".to_string(),
10885 serde_json::json!(field_id.as_base_64()),
10886 );
10887 manager.add_cortical_area(field_area).unwrap();
10888 manager.add_cortical_area(mem_area).unwrap();
10889 manager.add_cortical_area(stamp).unwrap();
10890 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
10891 classifier_id: "clf-1".to_string(),
10892 name: "demo".to_string(),
10893 parent_region_id: "root".to_string(),
10894 coordinates_3d: [0, 0, 0],
10895 kernel_area_id: Some("ckern001".to_string()),
10896 class_area_id: Some("ccls0001".to_string()),
10897 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
10898 field_area_id: field_id.as_base_64(),
10899 scan_twin_id: stamp_id.as_base_64(),
10900 }],
10901 kernel_memory_id: mem_id.as_base_64(),
10902 class_memory_id: "mcmem001".to_string(),
10903 properties: HashMap::new(),
10904 });
10905
10906 let mapping_data = vec![serde_json::json!({
10907 "morphology_id": "episodic_scan",
10908 "morphology_scalar": [1, 1, 1],
10909 "postSynapticCurrent_multiplier": 1,
10910 })];
10911 manager
10912 .update_cortical_mapping(&field_id, &mem_id, mapping_data)
10913 .unwrap();
10914 manager
10915 .update_cortical_mapping(&field_id, &mem_id, Vec::new())
10916 .unwrap();
10917
10918 assert!(
10919 manager.get_cortical_area(&stamp_id).is_some(),
10920 "classifier stamp must survive field mapping delete"
10921 );
10922 assert_eq!(
10923 manager
10924 .get_classifier("clf-1")
10925 .and_then(|classifier| {
10926 classifier
10927 .binding_for_field(&field_id.as_base_64())
10928 .map(|field| field.field_area_id.clone())
10929 }),
10930 Some(field_id.as_base_64()),
10931 "empty mapping delete must not clear the classifier field slot when morphology is omitted"
10932 );
10933 }
10934
10935 #[cfg(feature = "plasticity")]
10938 #[test]
10939 fn classifier_scan_config_targets_each_field_twin() {
10940 use feagi_structures::genomic::cortical_area::{
10941 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10942 MemoryCorticalType,
10943 };
10944
10945 let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
10946 let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
10947 let field_a = CorticalID::try_from_bytes(b"cfield01").unwrap();
10948 let field_b = CorticalID::try_from_bytes(b"cfield02").unwrap();
10949 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
10950 let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
10951 let twin_a = CorticalID::try_from_bytes(b"ctwin001").unwrap();
10952 let twin_b = CorticalID::try_from_bytes(b"ctwin002").unwrap();
10953
10954 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
10955 CorticalArea::new(
10956 id,
10957 0,
10958 name.to_string(),
10959 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
10960 (0, 0, 0).into(),
10961 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10962 )
10963 .unwrap()
10964 };
10965 let memory = |id: CorticalID, name: &str| {
10966 let mut area = CorticalArea::new(
10967 id,
10968 0,
10969 name.to_string(),
10970 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10971 (0, 0, 0).into(),
10972 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10973 )
10974 .unwrap();
10975 area.properties
10976 .insert("is_mem_type".to_string(), serde_json::json!(true));
10977 area
10978 };
10979
10980 let mut manager = ConnectomeManager::new_for_testing();
10981 let kernel = custom(kernel_id, "kernel", (2, 2, 1));
10982 let class_area = custom(class_id, "class", (1, 1, 10));
10983 let mut field_a_area = custom(field_a, "field_a", (4, 3, 1));
10984 let mut field_b_area = custom(field_b, "field_b", (2, 2, 1));
10985 field_a_area.properties.insert(
10986 "cortical_mapping_dst".to_string(),
10987 serde_json::json!({
10988 mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
10989 }),
10990 );
10991 field_b_area.properties.insert(
10992 "cortical_mapping_dst".to_string(),
10993 serde_json::json!({
10994 mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
10995 }),
10996 );
10997 let mut kernel_mem = memory(mem_id, "kernel_mem");
10998 kernel_mem.properties.insert(
10999 "classifier_kernel_area_id".to_string(),
11000 serde_json::json!(kernel_id.as_base_64()),
11001 );
11002 kernel_mem.properties.insert(
11003 "classifier_class_area_id".to_string(),
11004 serde_json::json!(class_id.as_base_64()),
11005 );
11006 kernel_mem.properties.insert(
11007 "classifier_class_memory_id".to_string(),
11008 serde_json::json!(class_mem_id.as_base_64()),
11009 );
11010 kernel_mem.properties.insert(
11011 "cortical_mapping_dst".to_string(),
11012 serde_json::json!({
11013 class_mem_id.as_base_64(): [{ "morphology_id": "associative_memory" }]
11014 }),
11015 );
11016 kernel_mem.properties.insert(
11017 "memory_twin_areas".to_string(),
11018 serde_json::json!({
11019 field_a.as_base_64(): twin_a.as_base_64(),
11020 field_b.as_base_64(): twin_b.as_base_64(),
11021 }),
11022 );
11023 let class_mem = memory(class_mem_id, "class_mem");
11024 let twin_a_area = custom(twin_a, "twin_a", (4, 3, 10));
11025 let twin_b_area = custom(twin_b, "twin_b", (2, 2, 10));
11026
11027 manager.add_cortical_area(kernel).unwrap();
11028 manager.add_cortical_area(class_area).unwrap();
11029 manager.add_cortical_area(field_a_area).unwrap();
11030 manager.add_cortical_area(field_b_area).unwrap();
11031 manager.add_cortical_area(kernel_mem).unwrap();
11032 manager.add_cortical_area(class_mem).unwrap();
11033 manager.add_cortical_area(twin_a_area).unwrap();
11034 manager.add_cortical_area(twin_b_area).unwrap();
11035
11036 let scan = manager
11037 .build_memory_scan_config(&mem_id)
11038 .expect("a mapped field twin must produce a scan config");
11039 assert_eq!(scan.sources.len(), 2);
11040 assert_eq!(scan.class_channel_count, 10);
11041 assert_eq!(scan.kernel.width, 2);
11042 let source_a = scan
11043 .sources
11044 .iter()
11045 .find(|source| source.field_area_idx == manager.get_cortical_idx(&field_a).unwrap())
11046 .expect("field A scan source");
11047 assert_eq!(
11048 source_a.twin_area_idx,
11049 manager.get_cortical_idx(&twin_a).unwrap()
11050 );
11051 assert_eq!(source_a.field_width, 4);
11052 assert_eq!(source_a.field_height, 3);
11053 let source_b = scan
11054 .sources
11055 .iter()
11056 .find(|source| source.field_area_idx == manager.get_cortical_idx(&field_b).unwrap())
11057 .expect("field B scan source");
11058 assert_eq!(
11059 source_b.twin_area_idx,
11060 manager.get_cortical_idx(&twin_b).unwrap()
11061 );
11062
11063 manager
11064 .get_cortical_area_mut(&mem_id)
11065 .unwrap()
11066 .properties
11067 .insert("memory_twin_areas".to_string(), serde_json::json!({}));
11068 assert!(
11069 manager.build_memory_scan_config(&mem_id).is_none(),
11070 "an empty twin map must not inject, which is why a dark twin stays dark"
11071 );
11072 }
11073
11074 #[cfg(feature = "plasticity")]
11077 #[test]
11078 fn classifier_genome_round_trip_restores_scan_target() {
11079 use feagi_evolutionary::{
11080 convert_hierarchical_to_flat, load_genome_from_json, GenomeMetadata, GenomeSignatures,
11081 GenomeStats, PhysiologyConfig, RuntimeGenome,
11082 };
11083 use feagi_structures::genomic::classifiers::{Classifier, ClassifierField};
11084 use feagi_structures::genomic::cortical_area::{
11085 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11086 MemoryCorticalType,
11087 };
11088
11089 let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
11090 let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
11091 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
11092 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11093 let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
11094 let twin_id = CorticalID::try_from_bytes(b"ctwin001").unwrap();
11095
11096 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11097 CorticalArea::new(
11098 id,
11099 0,
11100 name.to_string(),
11101 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11102 (0, 0, 0).into(),
11103 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11104 )
11105 .unwrap()
11106 };
11107 let memory = |id: CorticalID, name: &str| {
11108 let mut area = CorticalArea::new(
11109 id,
11110 0,
11111 name.to_string(),
11112 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11113 (0, 0, 0).into(),
11114 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11115 )
11116 .unwrap();
11117 area.properties
11118 .insert("is_mem_type".to_string(), serde_json::json!(true));
11119 area
11120 };
11121
11122 let mut kernel = custom(kernel_id, "kernel", (2, 2, 1));
11123 kernel.properties.insert(
11124 "cortical_mapping_dst".to_string(),
11125 serde_json::json!({
11126 mem_id.as_base_64(): [{
11127 "morphology_id": "episodic_memory",
11128 "postSynapticCurrent_multiplier": 1.0,
11129 "plasticity_flag": false
11130 }]
11131 }),
11132 );
11133 let class_area = custom(class_id, "class", (1, 1, 4));
11134 let mut field = custom(field_id, "field", (3, 2, 1));
11135 field
11136 .properties
11137 .insert("burst_engine_active".to_string(), serde_json::json!(true));
11138 field.properties.insert(
11139 "cortical_mapping_dst".to_string(),
11140 serde_json::json!({
11141 mem_id.as_base_64(): [{
11142 "morphology_id": "episodic_scan",
11143 "postSynapticCurrent_multiplier": 1.0,
11144 "plasticity_flag": false
11145 }]
11146 }),
11147 );
11148 let mut kernel_mem = memory(mem_id, "kernel_mem");
11149 kernel_mem.properties.insert(
11150 "cortical_mapping_dst".to_string(),
11151 serde_json::json!({
11152 class_mem_id.as_base_64(): [{
11153 "morphology_id": "associative_memory",
11154 "postSynapticCurrent_multiplier": 1.0,
11155 "plasticity_flag": false
11156 }]
11157 }),
11158 );
11159 let class_mem = memory(class_mem_id, "class_mem");
11160 let mut twin = custom(twin_id, "twin", (3, 2, 4));
11161 twin.properties
11162 .insert("burst_engine_active".to_string(), serde_json::json!(true));
11163 twin.properties.insert(
11164 "memory_twin_of".to_string(),
11165 serde_json::json!(field_id.as_base_64()),
11166 );
11167
11168 let mut genome = RuntimeGenome {
11169 metadata: GenomeMetadata {
11170 genome_id: "clf-round-trip".to_string(),
11171 genome_title: "clf".to_string(),
11172 genome_description: "".to_string(),
11173 version: "3.0".to_string(),
11174 timestamp: 0.0,
11175 brain_regions_root: None,
11176 },
11177 cortical_areas: HashMap::new(),
11178 brain_regions: HashMap::new(),
11179 classifiers: HashMap::new(),
11180 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
11181 physiology: PhysiologyConfig::default(),
11182 signatures: GenomeSignatures {
11183 genome: "0".to_string(),
11184 blueprint: "0".to_string(),
11185 physiology: "0".to_string(),
11186 morphologies: None,
11187 },
11188 stats: GenomeStats::default(),
11189 };
11190 for area in [kernel, class_area, field, kernel_mem, class_mem, twin] {
11191 genome.cortical_areas.insert(area.cortical_id, area);
11192 }
11193 genome.classifiers.insert(
11194 "clf-1".to_string(),
11195 Classifier {
11196 classifier_id: "clf-1".to_string(),
11197 name: "asdf".to_string(),
11198 parent_region_id: "root".to_string(),
11199 coordinates_3d: [0, 0, 0],
11200 kernel_area_id: Some(kernel_id.as_base_64()),
11201 class_area_id: Some(class_id.as_base_64()),
11202 fields: vec![ClassifierField {
11203 field_area_id: field_id.as_base_64(),
11204 scan_twin_id: twin_id.as_base_64(),
11205 }],
11206 kernel_memory_id: mem_id.as_base_64(),
11207 class_memory_id: class_mem_id.as_base_64(),
11208 properties: HashMap::new(),
11209 },
11210 );
11211
11212 let flat = convert_hierarchical_to_flat(&genome).unwrap();
11213 let loaded = load_genome_from_json(&flat.to_string()).unwrap();
11214 let loaded_classifier = loaded.classifiers.get("clf-1").unwrap();
11215 assert_eq!(
11216 loaded_classifier.fields[0].scan_twin_id,
11217 twin_id.as_base_64()
11218 );
11219 assert_eq!(
11220 loaded
11221 .cortical_areas
11222 .get(&twin_id)
11223 .unwrap()
11224 .properties
11225 .get("burst_engine_active")
11226 .and_then(|value| value.as_bool()),
11227 Some(true),
11228 "twin burst must survive save and load"
11229 );
11230 assert!(
11231 !loaded.cortical_areas[&mem_id]
11232 .properties
11233 .contains_key("classifier_kernel_area_id"),
11234 "the blueprint does not store classifier area ids; load must restore them"
11235 );
11236
11237 let mut manager = ConnectomeManager::new_for_testing();
11238 for area in loaded.cortical_areas.values() {
11239 manager.add_cortical_area(area.clone()).unwrap();
11240 }
11241 manager.replace_classifiers(loaded.classifiers);
11242 manager.apply_loaded_classifier_assemblies();
11243
11244 let kernel_mem = manager.get_cortical_area(&mem_id).unwrap();
11245 assert_eq!(
11246 kernel_mem
11247 .properties
11248 .get("classifier_kernel_area_id")
11249 .and_then(|value| value.as_str()),
11250 Some(kernel_id.as_base_64().as_str())
11251 );
11252 assert_eq!(
11253 kernel_mem
11254 .properties
11255 .get("classifier_class_area_id")
11256 .and_then(|value| value.as_str()),
11257 Some(class_id.as_base_64().as_str())
11258 );
11259 assert_eq!(
11260 kernel_mem
11261 .properties
11262 .get("memory_twin_areas")
11263 .and_then(|value| value.as_object())
11264 .and_then(|map| map.get(&field_id.as_base_64()))
11265 .and_then(|value| value.as_str()),
11266 Some(twin_id.as_base_64().as_str())
11267 );
11268 assert_eq!(
11269 kernel_mem
11270 .properties
11271 .get("burst_engine_active")
11272 .and_then(|value| value.as_bool()),
11273 Some(true)
11274 );
11275 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
11276 assert_eq!(
11277 twin_area
11278 .properties
11279 .get("memory_twin_for")
11280 .and_then(|value| value.as_str()),
11281 Some(mem_id.as_base_64().as_str())
11282 );
11283 assert_eq!(
11284 twin_area
11285 .properties
11286 .get("scan_twin")
11287 .and_then(|value| value.as_bool()),
11288 Some(true)
11289 );
11290
11291 assert!(
11292 manager.mapping_from_src_to_dst_has_associative(&mem_id, &class_mem_id),
11293 "kernel memory to class memory associative mapping must survive save and load, got {:?}",
11294 manager
11295 .get_cortical_area(&mem_id)
11296 .unwrap()
11297 .properties
11298 .get("cortical_mapping_dst")
11299 );
11300 assert!(
11301 !manager
11302 .get_episodic_scan_upstream_cortical_areas(&mem_id)
11303 .is_empty(),
11304 "field episodic_scan must survive save and load, got {:?}",
11305 manager
11306 .get_cortical_area(&field_id)
11307 .unwrap()
11308 .properties
11309 .get("cortical_mapping_dst")
11310 );
11311 let scan = manager
11312 .build_memory_scan_config(&mem_id)
11313 .expect("a reloaded classifier must scan into its twin");
11314 assert_eq!(scan.sources.len(), 1);
11315 assert_eq!(
11316 scan.sources[0].twin_area_idx,
11317 manager.get_cortical_idx(&twin_id).unwrap()
11318 );
11319 assert_eq!(
11320 scan.sources[0].field_area_idx,
11321 manager.get_cortical_idx(&field_id).unwrap()
11322 );
11323 }
11324}