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 morphology_registry: feagi_evolutionary::MorphologyRegistry,
149
150 config: ConnectomeConfig,
152
153 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
159
160 #[cfg(feature = "plasticity")]
162 plasticity_executor:
163 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
164
165 cached_neuron_count: Arc<AtomicUsize>,
168
169 cached_synapse_count: Arc<AtomicUsize>,
172
173 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
176
177 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
180
181 initialized: bool,
183
184 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
186}
187
188type NeuronData = (
190 u32,
191 u32,
192 u32,
193 f32,
194 f32,
195 f32,
196 f32,
197 i32,
198 u16,
199 f32,
200 u16,
201 u16,
202 bool,
203);
204
205impl ConnectomeManager {
206 fn get_mapping_rules_for_destination<'a>(
207 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
208 dst_area_id: &CorticalID,
209 ) -> Option<&'a Vec<serde_json::Value>> {
210 if let Some(rules) = mapping_dst
211 .get(&dst_area_id.as_base_64())
212 .and_then(|value| value.as_array())
213 {
214 return Some(rules);
215 }
216
217 for (raw_dst_key, rules_value) in mapping_dst {
220 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
221 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
222 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
223 continue;
224 }
225 if let Some(rules) = rules_value.as_array() {
226 return Some(rules);
227 }
228 }
229
230 None
231 }
232
233 fn new() -> Self {
235 Self {
236 cortical_areas: HashMap::new(),
237 cortical_id_to_idx: HashMap::new(),
238 cortical_idx_to_id: HashMap::new(),
239 next_cortical_idx: 7, brain_regions: BrainRegionHierarchy::new(),
242 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
243 config: ConnectomeConfig::default(),
244 npu: None,
245 #[cfg(feature = "plasticity")]
246 plasticity_executor: None,
247 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
248 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
249 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
250 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
251 initialized: false,
252 last_fatigue_calculation: Arc::new(Mutex::new(
253 std::time::Instant::now() - std::time::Duration::from_secs(10),
254 )), }
256 }
257
258 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
275 Arc::clone(&*INSTANCE)
278 }
279
280 pub fn new_for_testing() -> Self {
308 Self {
309 cortical_areas: HashMap::new(),
310 cortical_id_to_idx: HashMap::new(),
311 cortical_idx_to_id: HashMap::new(),
312 next_cortical_idx: 0,
313 brain_regions: BrainRegionHierarchy::new(),
314 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
315 config: ConnectomeConfig::default(),
316 npu: None,
317 #[cfg(feature = "plasticity")]
318 plasticity_executor: None,
319 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
320 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
321 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
322 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
323 initialized: false,
324 last_fatigue_calculation: Arc::new(Mutex::new(
325 std::time::Instant::now() - std::time::Duration::from_secs(10),
326 )),
327 }
328 }
329
330 pub fn new_for_testing_with_npu(
346 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
347 ) -> Self {
348 Self {
349 cortical_areas: HashMap::new(),
350 cortical_id_to_idx: HashMap::new(),
351 cortical_idx_to_id: HashMap::new(),
352 next_cortical_idx: 7,
353 brain_regions: BrainRegionHierarchy::new(),
354 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
355 config: ConnectomeConfig::default(),
356 npu: Some(npu),
357 #[cfg(feature = "plasticity")]
358 plasticity_executor: None,
359 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
360 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
361 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
362 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
363 initialized: false,
364 last_fatigue_calculation: Arc::new(Mutex::new(
365 std::time::Instant::now() - std::time::Duration::from_secs(10),
366 )),
367 }
368 }
369
370 pub fn setup_core_morphologies_for_testing(&mut self) {
380 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
381 }
382
383 #[cfg(test)]
392 pub fn reset_for_testing() {
393 let mut instance = INSTANCE.write();
394 *instance = Self::new();
395 }
396
397 fn update_state_hashes(
403 &self,
404 brain_regions: Option<u64>,
405 cortical_areas: Option<u64>,
406 brain_geometry: Option<u64>,
407 morphologies: Option<u64>,
408 cortical_mappings: Option<u64>,
409 ) {
410 let state_manager = StateManager::instance();
411 let state_manager = state_manager.read();
412 if let Some(value) = brain_regions {
413 state_manager.set_brain_regions_hash(value);
414 }
415 if let Some(value) = cortical_areas {
416 state_manager.set_cortical_areas_hash(value);
417 }
418 if let Some(value) = brain_geometry {
419 state_manager.set_brain_geometry_hash(value);
420 }
421 if let Some(value) = morphologies {
422 state_manager.set_morphologies_hash(value);
423 }
424 if let Some(value) = cortical_mappings {
425 state_manager.set_cortical_mappings_hash(value);
426 }
427 }
428
429 fn refresh_brain_regions_hash(&self) {
431 let hash = self.compute_brain_regions_hash();
432 self.update_state_hashes(Some(hash), None, None, None, None);
433 }
434
435 #[allow(dead_code)]
436 fn refresh_cortical_areas_hash(&self) {
438 let hash = self.compute_cortical_areas_hash();
439 self.update_state_hashes(None, Some(hash), None, None, None);
440 }
441
442 #[allow(dead_code)]
443 fn refresh_brain_geometry_hash(&self) {
445 let hash = self.compute_brain_geometry_hash();
446 self.update_state_hashes(None, None, Some(hash), None, None);
447 }
448
449 fn refresh_morphologies_hash(&self) {
451 let hash = self.compute_morphologies_hash();
452 self.update_state_hashes(None, None, None, Some(hash), None);
453 }
454
455 pub fn refresh_cortical_mappings_hash(&self) {
457 let hash = self.compute_cortical_mappings_hash();
458 self.update_state_hashes(None, None, None, None, Some(hash));
459 }
460
461 pub fn refresh_all_connectome_hashes(&self) {
466 self.update_state_hashes(
467 Some(self.compute_brain_regions_hash()),
468 Some(self.compute_cortical_areas_hash()),
469 Some(self.compute_brain_geometry_hash()),
470 Some(self.compute_morphologies_hash()),
471 Some(self.compute_cortical_mappings_hash()),
472 );
473 }
474
475 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
477 let cortical_hash = if properties_changed {
478 Some(self.compute_cortical_areas_hash())
479 } else {
480 None
481 };
482 let geometry_hash = if geometry_changed {
483 Some(self.compute_brain_geometry_hash())
484 } else {
485 None
486 };
487 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
488 }
489
490 fn compute_brain_regions_hash(&self) -> u64 {
492 let mut hasher = Xxh64::new(DATA_HASH_SEED);
493 let mut region_ids: Vec<String> = self
494 .brain_regions
495 .get_all_region_ids()
496 .into_iter()
497 .cloned()
498 .collect();
499 region_ids.sort();
500
501 for region_id in region_ids {
502 let Some(region) = self.brain_regions.get_region(®ion_id) else {
503 continue;
504 };
505 Self::hash_str(&mut hasher, ®ion_id);
506 Self::hash_str(&mut hasher, ®ion.name);
507 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
508 let parent_id = self.brain_regions.get_parent(®ion_id);
509 match parent_id {
510 Some(parent) => Self::hash_str(&mut hasher, parent),
511 None => Self::hash_str(&mut hasher, "null"),
512 }
513
514 let mut cortical_ids: Vec<String> = region
515 .cortical_areas
516 .iter()
517 .map(|id| id.as_base_64())
518 .collect();
519 cortical_ids.sort();
520 for cortical_id in cortical_ids {
521 Self::hash_str(&mut hasher, &cortical_id);
522 }
523
524 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
525 }
526
527 hasher.finish() & HASH_SAFE_MASK
528 }
529
530 fn compute_cortical_areas_hash(&self) -> u64 {
532 let mut hasher = Xxh64::new(DATA_HASH_SEED);
533 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
534 areas.sort_by_key(|area| area.cortical_id.as_base_64());
535
536 for area in areas {
537 let cortical_id = area.cortical_id.as_base_64();
538 Self::hash_str(&mut hasher, &cortical_id);
539 hasher.write_u32(area.cortical_idx);
540 Self::hash_str(&mut hasher, &area.name);
541 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
542
543 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
544 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
545 }
546
547 hasher.finish() & HASH_SAFE_MASK
548 }
549
550 fn compute_brain_geometry_hash(&self) -> u64 {
552 let mut hasher = Xxh64::new(DATA_HASH_SEED);
553 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
554 areas.sort_by_key(|area| area.cortical_id.as_base_64());
555
556 for area in areas {
557 let cortical_id = area.cortical_id.as_base_64();
558 Self::hash_str(&mut hasher, &cortical_id);
559
560 Self::hash_i32(&mut hasher, area.position.x);
561 Self::hash_i32(&mut hasher, area.position.y);
562 Self::hash_i32(&mut hasher, area.position.z);
563
564 Self::hash_u32(&mut hasher, area.dimensions.width);
565 Self::hash_u32(&mut hasher, area.dimensions.height);
566 Self::hash_u32(&mut hasher, area.dimensions.depth);
567
568 let coord_2d = area
569 .properties
570 .get("coordinate_2d")
571 .or_else(|| area.properties.get("coordinates_2d"));
572 match coord_2d {
573 Some(value) => Self::hash_json_value(&mut hasher, value),
574 None => Self::hash_str(&mut hasher, "null"),
575 }
576 }
577
578 hasher.finish() & HASH_SAFE_MASK
579 }
580
581 fn compute_morphologies_hash(&self) -> u64 {
583 let mut hasher = Xxh64::new(DATA_HASH_SEED);
584 let mut morphology_ids = self.morphology_registry.morphology_ids();
585 morphology_ids.sort();
586
587 for morphology_id in morphology_ids {
588 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
589 Self::hash_str(&mut hasher, &morphology_id);
590 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
591 Self::hash_str(&mut hasher, &morphology.class);
592 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
593 Self::hash_json_value(&mut hasher, &value);
594 }
595 }
596 }
597
598 hasher.finish() & HASH_SAFE_MASK
599 }
600
601 fn compute_cortical_mappings_hash(&self) -> u64 {
603 let mut hasher = Xxh64::new(DATA_HASH_SEED);
604 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
605 areas.sort_by_key(|area| area.cortical_id.as_base_64());
606
607 for area in areas {
608 let cortical_id = area.cortical_id.as_base_64();
609 Self::hash_str(&mut hasher, &cortical_id);
610 if let Some(serde_json::Value::Object(map)) =
611 area.properties.get("cortical_mapping_dst")
612 {
613 let mut dest_ids: Vec<&String> = map.keys().collect();
614 dest_ids.sort();
615 for dest_id in dest_ids {
616 Self::hash_str(&mut hasher, dest_id);
617 if let Some(value) = map.get(dest_id) {
618 Self::hash_json_value(&mut hasher, value);
619 }
620 }
621 } else {
622 Self::hash_str(&mut hasher, "null");
623 }
624 }
625
626 hasher.finish() & HASH_SAFE_MASK
627 }
628
629 fn hash_str(hasher: &mut Xxh64, value: &str) {
631 hasher.write(value.as_bytes());
632 hasher.write_u8(0);
633 }
634
635 fn hash_i32(hasher: &mut Xxh64, value: i32) {
637 hasher.write(&value.to_le_bytes());
638 }
639
640 fn hash_u32(hasher: &mut Xxh64, value: u32) {
642 hasher.write(&value.to_le_bytes());
643 }
644
645 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
647 match value {
648 serde_json::Value::Null => {
649 hasher.write_u8(0);
650 }
651 serde_json::Value::Bool(val) => {
652 hasher.write_u8(1);
653 hasher.write_u8(*val as u8);
654 }
655 serde_json::Value::Number(num) => {
656 hasher.write_u8(2);
657 Self::hash_str(hasher, &num.to_string());
658 }
659 serde_json::Value::String(val) => {
660 hasher.write_u8(3);
661 Self::hash_str(hasher, val);
662 }
663 serde_json::Value::Array(items) => {
664 hasher.write_u8(4);
665 for item in items {
666 Self::hash_json_value(hasher, item);
667 }
668 }
669 serde_json::Value::Object(map) => {
670 hasher.write_u8(5);
671 let mut keys: Vec<&String> = map.keys().collect();
672 keys.sort();
673 for key in keys {
674 Self::hash_str(hasher, key);
675 if let Some(val) = map.get(key) {
676 Self::hash_json_value(hasher, val);
677 }
678 }
679 }
680 }
681 }
682
683 fn hash_properties_filtered(
685 hasher: &mut Xxh64,
686 properties: &HashMap<String, serde_json::Value>,
687 excluded_keys: &[&str],
688 ) {
689 let mut keys: Vec<&String> = properties.keys().collect();
690 keys.sort();
691 for key in keys {
692 if excluded_keys.contains(&key.as_str()) {
693 continue;
694 }
695 Self::hash_str(hasher, key);
696 if let Some(value) = properties.get(key) {
697 Self::hash_json_value(hasher, value);
698 }
699 }
700 }
701
702 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
723 if self.cortical_areas.contains_key(&area.cortical_id) {
725 return Err(BduError::InvalidArea(format!(
726 "Cortical area {} already exists",
727 area.cortical_id
728 )));
729 }
730
731 use feagi_structures::genomic::cortical_area::CoreCorticalType;
734
735 let death_id = CoreCorticalType::Death.to_cortical_id();
736 let power_id = CoreCorticalType::Power.to_cortical_id();
737 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
738 let pain_id = CoreCorticalType::Pain.to_cortical_id();
739 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
740 let fear_id = CoreCorticalType::Fear.to_cortical_id();
741 let hope_id = CoreCorticalType::Hope.to_cortical_id();
742
743 let is_death_area = area.cortical_id == death_id;
744 let is_power_area = area.cortical_id == power_id;
745 let is_fatigue_area = area.cortical_id == fatigue_id;
746 let is_pain_area = area.cortical_id == pain_id;
747 let is_pleasure_area = area.cortical_id == pleasure_id;
748 let is_fear_area = area.cortical_id == fear_id;
749 let is_hope_area = area.cortical_id == hope_id;
750
751 if is_death_area {
752 trace!(
753 target: "feagi-bdu",
754 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
755 area.cortical_id
756 );
757 area.cortical_idx = 0;
758 } else if is_power_area {
759 trace!(
760 target: "feagi-bdu",
761 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
762 area.cortical_id
763 );
764 area.cortical_idx = 1;
765 } else if is_fatigue_area {
766 trace!(
767 target: "feagi-bdu",
768 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
769 area.cortical_id
770 );
771 area.cortical_idx = 2;
772 } else if is_pain_area {
773 trace!(
774 target: "feagi-bdu",
775 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
776 area.cortical_id
777 );
778 area.cortical_idx = 3;
779 } else if is_pleasure_area {
780 trace!(
781 target: "feagi-bdu",
782 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
783 area.cortical_id
784 );
785 area.cortical_idx = 4;
786 } else if is_fear_area {
787 trace!(
788 target: "feagi-bdu",
789 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
790 area.cortical_id
791 );
792 area.cortical_idx = 5;
793 } else if is_hope_area {
794 trace!(
795 target: "feagi-bdu",
796 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
797 area.cortical_id
798 );
799 area.cortical_idx = 6;
800 } else {
801 if area.cortical_idx == 0 {
803 area.cortical_idx = self.next_cortical_idx;
804 self.next_cortical_idx += 1;
805 trace!(
806 target: "feagi-bdu",
807 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
808 area.cortical_idx,
809 area.cortical_id.as_base_64()
810 );
811 } else {
812 if area.cortical_idx <= 6 {
814 warn!(
815 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
816 area.cortical_id, area.cortical_idx);
817 area.cortical_idx = self.next_cortical_idx;
818 self.next_cortical_idx += 1;
819 info!(
820 " Reassigned '{}' to cortical_idx={}",
821 area.cortical_id, area.cortical_idx
822 );
823 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
824 return Err(BduError::InvalidArea(format!(
825 "Cortical index {} is already in use",
826 area.cortical_idx
827 )));
828 }
829
830 if area.cortical_idx >= self.next_cortical_idx {
832 self.next_cortical_idx = area.cortical_idx + 1;
833 }
834 }
835 }
836
837 let cortical_id = area.cortical_id;
838 let cortical_idx = area.cortical_idx;
839
840 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
842 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
843
844 area.properties
846 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
847
848 let parent_region_id = area
857 .properties
858 .get("parent_region_id")
859 .and_then(|v| v.as_str())
860 .map(|s| s.to_string());
861
862 self.cortical_areas.insert(cortical_id, area);
864
865 if let Some(region_id) = parent_region_id {
867 let region = self
868 .brain_regions
869 .get_region_mut(®ion_id)
870 .ok_or_else(|| {
871 BduError::InvalidArea(format!(
872 "Unknown parent_region_id '{}' for cortical area {}",
873 region_id,
874 cortical_id.as_base_64()
875 ))
876 })?;
877 region.add_area(cortical_id);
878 }
879
880 {
883 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
884 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
885 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
886 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
887 }
888 let state_manager = StateManager::instance();
890 let state_manager = state_manager.read();
891 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
892
893 if let Some(ref npu) = self.npu {
897 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
898 if let Ok(mut npu_lock) = npu.lock() {
899 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
900 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
901 trace!(
902 target: "feagi-bdu",
903 "Registered cortical area idx={} -> '{}' in NPU",
904 cortical_idx,
905 cortical_id.as_base_64()
906 );
907 }
908 }
909
910 self.sync_cortical_area_flags_to_npu()?;
912
913 self.initialized = true;
914
915 self.refresh_cortical_area_hashes(true, true);
916 self.refresh_brain_regions_hash();
917
918 Ok(cortical_idx)
919 }
920
921 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
936 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
937 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
938 })?;
939
940 self.cortical_id_to_idx.remove(cortical_id);
942 self.cortical_idx_to_id.remove(&area.cortical_idx);
943
944 self.refresh_cortical_area_hashes(true, true);
945 Ok(())
946 }
947
948 pub fn rename_cortical_area_id(
952 &mut self,
953 old_id: &CorticalID,
954 new_id: CorticalID,
955 new_cortical_type: CorticalAreaType,
956 ) -> BduResult<()> {
957 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
958 }
959
960 pub fn rename_cortical_area_id_with_options(
962 &mut self,
963 old_id: &CorticalID,
964 new_id: CorticalID,
965 new_cortical_type: CorticalAreaType,
966 update_npu_registry: bool,
967 ) -> BduResult<()> {
968 if !self.cortical_areas.contains_key(old_id) {
969 return Err(BduError::InvalidArea(format!(
970 "Cortical area {} does not exist",
971 old_id
972 )));
973 }
974 if self.cortical_areas.contains_key(&new_id) {
975 return Err(BduError::InvalidArea(format!(
976 "Cortical area {} already exists",
977 new_id
978 )));
979 }
980
981 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
982 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
983 })?;
984 let cortical_idx = area.cortical_idx;
985 area.cortical_id = new_id;
986 area.cortical_type = new_cortical_type;
987
988 self.cortical_areas.insert(new_id, area);
989 self.cortical_id_to_idx.remove(old_id);
990 self.cortical_id_to_idx.insert(new_id, cortical_idx);
991 self.cortical_idx_to_id.insert(cortical_idx, new_id);
992
993 {
995 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
996 if let Some(value) = neuron_cache.remove(old_id) {
997 neuron_cache.insert(new_id, value);
998 }
999 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
1000 if let Some(value) = synapse_cache.remove(old_id) {
1001 synapse_cache.insert(new_id, value);
1002 }
1003 }
1004
1005 self.brain_regions.rename_cortical_area_id(old_id, new_id);
1007
1008 let old_id_str = old_id.as_base_64();
1010 let new_id_str = new_id.as_base_64();
1011 for area in self.cortical_areas.values_mut() {
1012 if let Some(mapping) = area
1013 .properties
1014 .get_mut("cortical_mapping_dst")
1015 .and_then(|v| v.as_object_mut())
1016 {
1017 if let Some(value) = mapping.remove(&old_id_str) {
1018 mapping.insert(new_id_str.clone(), value);
1019 }
1020 }
1021 }
1022
1023 if update_npu_registry {
1025 if let Some(ref npu) = self.npu {
1026 if let Ok(mut npu_lock) = npu.lock() {
1027 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
1028 }
1029 }
1030 }
1031
1032 self.refresh_cortical_area_hashes(true, true);
1033 self.refresh_brain_regions_hash();
1034 self.refresh_cortical_mappings_hash();
1035
1036 Ok(())
1037 }
1038
1039 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1041 self.cortical_areas.get(cortical_id)
1042 }
1043
1044 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1046 self.cortical_areas.get_mut(cortical_id)
1047 }
1048
1049 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1051 self.cortical_id_to_idx.get(cortical_id).copied()
1052 }
1053
1054 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1066 self.brain_regions.find_region_containing_area(cortical_id)
1067 }
1068
1069 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1072 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1073 return false;
1074 };
1075 let Some(src_area) = self.cortical_areas.get(area) else {
1076 return false;
1077 };
1078 let Some(dst_obj) = src_area
1079 .properties
1080 .get("cortical_mapping_dst")
1081 .and_then(|v| v.as_object())
1082 else {
1083 return false;
1084 };
1085 for dst_key in dst_obj.keys() {
1086 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1087 continue;
1088 };
1089 match self.brain_regions.find_region_containing_area(&dst_id) {
1090 None => return true,
1091 Some(rid) if rid != my_region => return true,
1092 _ => {}
1093 }
1094 }
1095 false
1096 }
1097
1098 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1100 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1101 return false;
1102 };
1103 let my_b64 = area.as_base_64();
1104 for (src_id, src_area) in &self.cortical_areas {
1105 if src_id == area {
1106 continue;
1107 }
1108 let Some(dst_map) = src_area
1109 .properties
1110 .get("cortical_mapping_dst")
1111 .and_then(|v| v.as_object())
1112 else {
1113 continue;
1114 };
1115 if !dst_map.contains_key(&my_b64) {
1116 continue;
1117 }
1118 match self.brain_regions.find_region_containing_area(src_id) {
1119 None => return true,
1120 Some(rid) if rid != my_region => return true,
1121 _ => {}
1122 }
1123 }
1124 false
1125 }
1126
1127 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1138 use std::collections::HashSet;
1139
1140 let region_ids: Vec<String> = self
1141 .brain_regions
1142 .get_all_region_ids()
1143 .into_iter()
1144 .cloned()
1145 .collect();
1146
1147 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1148 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1149
1150 for rid in ®ion_ids {
1152 inputs_by_region.insert(rid.clone(), HashSet::new());
1153 outputs_by_region.insert(rid.clone(), HashSet::new());
1154 }
1155
1156 for (src_id, src_area) in &self.cortical_areas {
1157 let Some(dstmap) = src_area
1158 .properties
1159 .get("cortical_mapping_dst")
1160 .and_then(|v| v.as_object())
1161 else {
1162 continue;
1163 };
1164
1165 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1166 debug!(
1167 target: "feagi-bdu",
1168 "Skipping region IO for source area {} (not in any region)",
1169 src_id.as_base_64()
1170 );
1171 continue;
1172 };
1173
1174 for dst_id_str in dstmap.keys() {
1175 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1176 BduError::InvalidArea(format!(
1177 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1178 dst_id_str,
1179 src_id.as_base_64(),
1180 e
1181 ))
1182 })?;
1183
1184 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1185 else {
1186 warn!(
1187 target: "feagi-bdu",
1188 "Skipping region IO for destination area {} (not in any region)",
1189 dst_id.as_base_64()
1190 );
1191 continue;
1192 };
1193
1194 if src_region_id == dst_region_id {
1195 continue;
1196 }
1197
1198 outputs_by_region
1199 .entry(src_region_id.clone())
1200 .or_default()
1201 .insert(src_id.as_base_64());
1202 inputs_by_region
1203 .entry(dst_region_id.clone())
1204 .or_default()
1205 .insert(dst_id.as_base_64());
1206 }
1207 }
1208
1209 for rid in ®ion_ids {
1211 let Some(region) = self.brain_regions.get_region(rid) else {
1212 continue;
1213 };
1214 let in_ids = crate::region_io_designation::parse_designated_id_list(
1215 region
1216 .properties
1217 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1218 )?;
1219 let out_ids = crate::region_io_designation::parse_designated_id_list(
1220 region
1221 .properties
1222 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1223 )?;
1224 for id in in_ids {
1225 inputs_by_region
1226 .entry(rid.clone())
1227 .or_default()
1228 .insert(id.as_base_64());
1229 }
1230 for id in out_ids {
1231 outputs_by_region
1232 .entry(rid.clone())
1233 .or_default()
1234 .insert(id.as_base_64());
1235 }
1236 }
1237
1238 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1239 for rid in region_ids {
1240 let mut inputs: Vec<String> = inputs_by_region
1241 .remove(&rid)
1242 .unwrap_or_default()
1243 .into_iter()
1244 .collect();
1245 let mut outputs: Vec<String> = outputs_by_region
1246 .remove(&rid)
1247 .unwrap_or_default()
1248 .into_iter()
1249 .collect();
1250
1251 inputs.sort();
1252 outputs.sort();
1253
1254 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1255 BduError::InvalidArea(format!(
1256 "Unable to recompute region IO: region '{}' not found in hierarchy",
1257 rid
1258 ))
1259 })?;
1260
1261 if inputs.is_empty() {
1262 region.properties.remove("inputs");
1263 } else {
1264 region
1265 .properties
1266 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1267 }
1268
1269 if outputs.is_empty() {
1270 region.properties.remove("outputs");
1271 } else {
1272 region
1273 .properties
1274 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1275 }
1276
1277 computed.insert(rid, (inputs, outputs));
1278 }
1279
1280 self.refresh_brain_regions_hash();
1281
1282 Ok(computed)
1283 }
1284
1285 pub fn get_root_region_id(&self) -> Option<String> {
1291 self.brain_regions.get_root_region_id()
1292 }
1293
1294 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1296 self.cortical_idx_to_id.get(&cortical_idx)
1297 }
1298
1299 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1302 self.cortical_idx_to_id
1303 .iter()
1304 .map(|(idx, id)| (*idx, id.as_base_64()))
1305 .collect()
1306 }
1307
1308 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1313 let mut granularities = ahash::AHashMap::new();
1314 for (cortical_id, area) in &self.cortical_areas {
1315 let cortical_idx = self
1316 .cortical_id_to_idx
1317 .get(cortical_id)
1318 .copied()
1319 .unwrap_or(0);
1320
1321 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1324 if let Some(arr) = granularity_json.as_array() {
1325 if arr.len() == 3 {
1326 let x_opt = arr[0]
1327 .as_u64()
1328 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1329 let y_opt = arr[1]
1330 .as_u64()
1331 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1332 let z_opt = arr[2]
1333 .as_u64()
1334 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1335
1336 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1337 let granularity = (x as u32, y as u32, z as u32);
1338 if granularity != (1, 1, 1) {
1340 granularities.insert(cortical_idx, granularity);
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347 granularities
1348 }
1349
1350 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1352 self.cortical_areas.keys().collect()
1353 }
1354
1355 pub fn get_cortical_area_count(&self) -> usize {
1357 self.cortical_areas.len()
1358 }
1359
1360 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1374 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1375 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1376 if let Some(upstream_array) = upstream_prop.as_array() {
1377 return upstream_array
1378 .iter()
1379 .filter_map(|v| v.as_u64().map(|n| n as u32))
1380 .collect();
1381 }
1382 }
1383
1384 warn!(target: "feagi-bdu",
1386 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1387 target_cortical_id.as_base_64()
1388 );
1389 }
1390
1391 Vec::new()
1392 }
1393
1394 pub fn get_episodic_memory_upstream_cortical_areas(
1400 &self,
1401 target_cortical_id: &CorticalID,
1402 ) -> Vec<u32> {
1403 let mut episodic_upstream = Vec::new();
1404 for (src_id, src_area) in &self.cortical_areas {
1405 if src_id == target_cortical_id {
1406 continue;
1407 }
1408 let Some(mapping_dst) = src_area
1409 .properties
1410 .get("cortical_mapping_dst")
1411 .and_then(|v| v.as_object())
1412 else {
1413 continue;
1414 };
1415 let Some(rules) =
1416 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1417 else {
1418 continue;
1419 };
1420 if !Self::mapping_has_episodic_rule(rules) {
1421 continue;
1422 }
1423 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1424 continue;
1425 };
1426 episodic_upstream.push(src_idx);
1427 }
1428
1429 episodic_upstream.sort_unstable();
1430 episodic_upstream.dedup();
1431 episodic_upstream
1432 }
1433
1434 fn mapping_has_episodic_rule(rules: &[serde_json::Value]) -> bool {
1435 rules.iter().any(|rule| {
1436 let morphology_id = rule
1437 .as_object()
1438 .and_then(|obj| obj.get("morphology_id"))
1439 .and_then(|v| v.as_str())
1440 .or_else(|| {
1441 rule.as_array()
1442 .and_then(|arr| arr.first())
1443 .and_then(|v| v.as_str())
1444 });
1445 morphology_id == Some("episodic_memory")
1446 })
1447 }
1448
1449 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1451 upstream
1452 .iter()
1453 .filter_map(|idx| {
1454 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1455 let area = self.cortical_areas.get(cortical_id)?;
1456 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1457 None
1458 } else {
1459 Some(*idx)
1460 }
1461 })
1462 .collect()
1463 }
1464
1465 pub fn refresh_upstream_cortical_areas_from_mappings(
1469 &mut self,
1470 target_cortical_id: &CorticalID,
1471 ) -> Vec<u32> {
1472 use std::collections::HashSet;
1473 let target_id_str = target_cortical_id.as_base_64();
1474 let mut upstream_idxs = HashSet::new();
1475 for (src_id, src_area) in &self.cortical_areas {
1476 if src_id == target_cortical_id {
1477 continue;
1478 }
1479 if let Some(mapping) = src_area
1480 .properties
1481 .get("cortical_mapping_dst")
1482 .and_then(|v| v.as_object())
1483 {
1484 if mapping.contains_key(&target_id_str) {
1485 if let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) {
1486 upstream_idxs.insert(src_idx);
1487 }
1488 }
1489 }
1490 }
1491
1492 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1493 upstream_list.sort_unstable();
1494
1495 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1496 target_area.properties.insert(
1497 "upstream_cortical_areas".to_string(),
1498 serde_json::json!(upstream_list),
1499 );
1500 }
1501
1502 self.get_upstream_cortical_areas(target_cortical_id)
1503 }
1504
1505 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1515 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1516 let upstream_array = area
1517 .properties
1518 .entry("upstream_cortical_areas".to_string())
1519 .or_insert_with(|| serde_json::json!([]));
1520
1521 if let Some(arr) = upstream_array.as_array_mut() {
1522 let src_value = serde_json::json!(src_cortical_idx);
1523 if !arr.contains(&src_value) {
1524 arr.push(src_value);
1525 debug!(target: "feagi-bdu",
1526 "Added upstream area idx={} to cortical area '{}'",
1527 src_cortical_idx, target_cortical_id.as_base_64()
1528 );
1529 }
1530 }
1531 }
1532 }
1533
1534 pub fn get_memory_twin_for_upstream_idx(
1536 &self,
1537 memory_area_idx: u32,
1538 upstream_idx: u32,
1539 ) -> Option<CorticalID> {
1540 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1541 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1542 let area = self.cortical_areas.get(memory_id)?;
1543 let mapping = area
1544 .properties
1545 .get("memory_twin_areas")
1546 .and_then(|v| v.as_object())?;
1547 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1548 CorticalID::try_from_base_64(twin_b64).ok()
1549 }
1550
1551 pub fn diagnose_memory_twin_for_mapping(
1558 &self,
1559 src_area_id: &CorticalID,
1560 dst_area_id: &CorticalID,
1561 ) -> BduResult<MemoryTwinDiagnostic> {
1562 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
1563 BduError::InvalidArea(format!(
1564 "Source area not found: {}",
1565 src_area_id.as_base_64()
1566 ))
1567 })?;
1568 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
1569 BduError::InvalidArea(format!(
1570 "Destination area not found: {}",
1571 dst_area_id.as_base_64()
1572 ))
1573 })?;
1574
1575 let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
1576 let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
1577
1578 let rules_opt = src_area
1579 .properties
1580 .get("cortical_mapping_dst")
1581 .and_then(|v| v.as_object())
1582 .and_then(|mapping_dst| {
1583 Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
1584 });
1585 let mapping_exists = rules_opt.is_some();
1586 let episodic_rule_count = rules_opt
1587 .map(|rules| {
1588 rules
1589 .iter()
1590 .filter(|rule| {
1591 let morphology_id = rule
1592 .as_object()
1593 .and_then(|obj| obj.get("morphology_id"))
1594 .and_then(|v| v.as_str())
1595 .or_else(|| {
1596 rule.as_array()
1597 .and_then(|arr| arr.first())
1598 .and_then(|v| v.as_str())
1599 });
1600 morphology_id == Some("episodic_memory")
1601 })
1602 .count()
1603 })
1604 .unwrap_or(0);
1605
1606 let twin_expected =
1607 mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
1608 let twin_cortical_area = dst_area
1609 .properties
1610 .get("memory_twin_areas")
1611 .and_then(|v| v.as_object())
1612 .and_then(|map| map.get(&src_area_id.as_base_64()))
1613 .and_then(|v| v.as_str())
1614 .map(ToString::to_string);
1615 let twin_present = twin_cortical_area.is_some();
1616
1617 let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
1618 CorticalID::try_from_base_64(twin_b64)
1619 .ok()
1620 .and_then(|twin_id| {
1621 self.cortical_areas.get(&twin_id).and_then(|area| {
1622 area.properties
1623 .get("parent_region_id")
1624 .and_then(|v| v.as_str())
1625 .map(ToString::to_string)
1626 })
1627 })
1628 });
1629 let src_parent_region_id = src_area
1630 .properties
1631 .get("parent_region_id")
1632 .and_then(|v| v.as_str())
1633 .map(ToString::to_string);
1634 let dst_parent_region_id = dst_area
1635 .properties
1636 .get("parent_region_id")
1637 .and_then(|v| v.as_str())
1638 .map(ToString::to_string);
1639 let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
1640 (Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
1641 (None, None) if twin_present => Some(true),
1642 _ if twin_present => Some(false),
1643 _ => None,
1644 };
1645
1646 let reason = if !mapping_exists {
1647 Some("no_mapping_rule_between_src_and_dst".to_string())
1648 } else if episodic_rule_count == 0 {
1649 Some("mapping_exists_but_not_episodic_memory".to_string())
1650 } else if !dst_is_memory {
1651 Some("destination_is_not_memory_area".to_string())
1652 } else if src_is_memory {
1653 Some("upstream_is_memory_area_twin_not_supported".to_string())
1654 } else if !twin_present {
1655 Some("twin_expected_but_missing".to_string())
1656 } else if twin_parent_matches_src_parent == Some(false) {
1657 Some("twin_parent_region_mismatch_with_source".to_string())
1658 } else {
1659 None
1660 };
1661
1662 Ok(MemoryTwinDiagnostic {
1663 src_cortical_area: src_area_id.as_base_64(),
1664 dst_cortical_area: dst_area_id.as_base_64(),
1665 src_cortical_type: src_area.cortical_type.to_string(),
1666 dst_cortical_type: dst_area.cortical_type.to_string(),
1667 mapping_exists,
1668 episodic_rule_count,
1669 twin_expected,
1670 twin_present,
1671 twin_cortical_area,
1672 reason,
1673 src_parent_region_id,
1674 dst_parent_region_id,
1675 twin_parent_region_id,
1676 twin_parent_matches_src_parent,
1677 })
1678 }
1679
1680 pub fn ensure_memory_twin_area(
1682 &mut self,
1683 memory_area_id: &CorticalID,
1684 upstream_area_id: &CorticalID,
1685 ) -> BduResult<CorticalID> {
1686 use crate::models::CorticalAreaExt;
1687
1688 let register_replay_mapping = |manager: &mut ConnectomeManager,
1689 twin_id: &CorticalID|
1690 -> BduResult<()> {
1691 let Some(npu) = manager.npu.as_ref() else {
1692 return Ok(());
1693 };
1694 let memory_area_idx =
1695 *manager
1696 .cortical_id_to_idx
1697 .get(memory_area_id)
1698 .ok_or_else(|| {
1699 BduError::InvalidArea(format!(
1700 "Memory area idx missing for {}",
1701 memory_area_id.as_base_64()
1702 ))
1703 })?;
1704 let upstream_area_idx = *manager
1705 .cortical_id_to_idx
1706 .get(upstream_area_id)
1707 .ok_or_else(|| {
1708 BduError::InvalidArea(format!(
1709 "Upstream area idx missing for {}",
1710 upstream_area_id.as_base_64()
1711 ))
1712 })?;
1713 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1714 BduError::InvalidArea(format!(
1715 "Twin area idx missing for {}",
1716 twin_id.as_base_64()
1717 ))
1718 })?;
1719 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1720 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1721 })?;
1722 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1723 if let Ok(mut npu_lock) = npu.lock() {
1724 npu_lock.register_memory_twin_mapping(
1725 memory_area_idx,
1726 upstream_area_idx,
1727 twin_area_idx,
1728 potential,
1729 );
1730 }
1731 Ok(())
1732 };
1733
1734 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1735 BduError::InvalidArea(format!(
1736 "Memory area {} not found",
1737 memory_area_id.as_base_64()
1738 ))
1739 })?;
1740 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1741 BduError::InvalidArea(format!(
1742 "Upstream area {} not found",
1743 upstream_area_id.as_base_64()
1744 ))
1745 })?;
1746
1747 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1748 return Err(BduError::InvalidArea(format!(
1749 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1750 upstream_area_id.as_base_64()
1751 )));
1752 }
1753
1754 if let Some(existing) = memory_area
1755 .properties
1756 .get("memory_twin_areas")
1757 .and_then(|v| v.as_object())
1758 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1759 .and_then(|v| v.as_str())
1760 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1761 {
1762 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1763 register_replay_mapping(self, &existing)?;
1764 self.refresh_cortical_mappings_hash();
1765 return Ok(existing);
1766 }
1767
1768 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1769 if self.cortical_areas.contains_key(&twin_id) {
1770 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1771 let expected_source = upstream_area_id.as_base_64();
1772 let expected_target = memory_area_id.as_base_64();
1773 let existing_source = existing
1774 .properties
1775 .get("memory_twin_of")
1776 .and_then(|v| v.as_str());
1777 let existing_target = existing
1778 .properties
1779 .get("memory_twin_for")
1780 .and_then(|v| v.as_str());
1781 if existing_source != Some(expected_source.as_str())
1782 || existing_target != Some(expected_target.as_str())
1783 {
1784 warn!(
1785 target: "feagi-bdu",
1786 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1787 upstream_area_id.as_base_64(),
1788 memory_area_id.as_base_64()
1789 );
1790 existing.properties.insert(
1791 "memory_twin_of".to_string(),
1792 serde_json::json!(expected_source),
1793 );
1794 existing.properties.insert(
1795 "memory_twin_for".to_string(),
1796 serde_json::json!(expected_target),
1797 );
1798 }
1799 }
1800 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1801 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1802 register_replay_mapping(self, &twin_id)?;
1803 self.refresh_cortical_mappings_hash();
1804 return Ok(twin_id);
1805 }
1806
1807 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1808 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1809 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1810 let mut twin_area = CorticalArea::new(
1811 twin_id,
1812 0,
1813 twin_name,
1814 upstream_area.dimensions,
1815 twin_position,
1816 twin_type,
1817 )?;
1818 twin_area.properties = self.build_memory_twin_properties(
1819 memory_area,
1820 upstream_area,
1821 memory_area_id,
1822 upstream_area_id,
1823 );
1824
1825 let _twin_idx = self.add_cortical_area(twin_area)?;
1826 let _ = self.create_neurons_for_area(&twin_id);
1827
1828 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1829 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1830 register_replay_mapping(self, &twin_id)?;
1831 self.refresh_cortical_mappings_hash();
1832 Ok(twin_id)
1833 }
1834
1835 fn build_memory_twin_position(
1836 &self,
1837 _memory_area: &CorticalArea,
1838 upstream_area: &CorticalArea,
1839 ) -> GenomeCoordinate3D {
1840 let width = upstream_area.dimensions.width as f32;
1841 let margin = (width * 0.25).ceil() as i32;
1842 let offset = upstream_area.dimensions.width as i32 + margin;
1843 GenomeCoordinate3D::new(
1844 upstream_area.position.x + offset,
1845 upstream_area.position.y,
1846 upstream_area.position.z,
1847 )
1848 }
1849
1850 fn build_memory_twin_id(
1851 &self,
1852 memory_area_id: &CorticalID,
1853 upstream_area_id: &CorticalID,
1854 ) -> BduResult<CorticalID> {
1855 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1856 hasher.write(memory_area_id.as_base_64().as_bytes());
1857 hasher.write(upstream_area_id.as_base_64().as_bytes());
1858 hasher.write(b"memory_twin");
1859 let hash = hasher.finish();
1860 let mut bytes = hash.to_be_bytes();
1861 bytes[0] = b'c';
1862 CorticalID::try_from_bytes(&bytes)
1863 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1864 }
1865
1866 fn build_memory_twin_properties(
1867 &self,
1868 _memory_area: &CorticalArea,
1869 upstream_area: &CorticalArea,
1870 memory_area_id: &CorticalID,
1871 upstream_area_id: &CorticalID,
1872 ) -> HashMap<String, serde_json::Value> {
1873 let mut props = upstream_area.properties.clone();
1874 props.remove("cortical_mapping_dst");
1875 props.remove("upstream_cortical_areas");
1876 props.remove("parent_region_id");
1877 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1878 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1879 props.insert(
1880 "memory_twin_of".to_string(),
1881 serde_json::json!(upstream_area_id.as_base_64()),
1882 );
1883 props.insert(
1884 "memory_twin_for".to_string(),
1885 serde_json::json!(memory_area_id.as_base_64()),
1886 );
1887 if let Some(parent_region_id) = upstream_area
1888 .properties
1889 .get("parent_region_id")
1890 .and_then(|v| v.as_str())
1891 {
1892 props.insert(
1893 "parent_region_id".to_string(),
1894 serde_json::json!(parent_region_id),
1895 );
1896 }
1897 props
1898 }
1899
1900 fn set_memory_twin_mapping(
1901 &mut self,
1902 memory_area_id: &CorticalID,
1903 upstream_area_id: &CorticalID,
1904 twin_id: &CorticalID,
1905 ) {
1906 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1907 let mapping = memory_area
1908 .properties
1909 .entry("memory_twin_areas".to_string())
1910 .or_insert_with(|| serde_json::json!({}));
1911 if let Some(map) = mapping.as_object_mut() {
1912 map.insert(
1913 upstream_area_id.as_base_64(),
1914 serde_json::json!(twin_id.as_base_64()),
1915 );
1916 }
1917 }
1918 }
1919
1920 fn ensure_memory_replay_mapping(
1921 &mut self,
1922 memory_area_id: &CorticalID,
1923 twin_id: &CorticalID,
1924 ) -> BduResult<()> {
1925 if !self.morphology_registry.contains("memory_replay") {
1926 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1927 }
1928 self.refresh_morphologies_hash();
1929 let mapping_data = vec![serde_json::json!({
1930 "morphology_id": "memory_replay",
1931 "morphology_scalar": [1, 1, 1],
1932 "postSynapticCurrent_multiplier": 1,
1933 "plasticity_flag": false,
1934 "plasticity_constant": 0,
1935 "ltp_multiplier": 0,
1936 "ltd_multiplier": 0,
1937 "plasticity_window": 0,
1938 })];
1939 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1940 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1941 self.refresh_cortical_area_hashes(true, false);
1943 Ok(())
1944 }
1945
1946 pub fn teardown_owned_memory_twin_for_mapping(
1951 &mut self,
1952 memory_area_id: &CorticalID,
1953 upstream_area_id: &CorticalID,
1954 ) -> BduResult<Option<CorticalID>> {
1955 let upstream_id = upstream_area_id.as_base_64();
1956 let twin_id = self
1957 .cortical_areas
1958 .get(memory_area_id)
1959 .and_then(|memory_area| memory_area.properties.get("memory_twin_areas"))
1960 .and_then(|value| value.as_object())
1961 .and_then(|twins| twins.get(&upstream_id))
1962 .and_then(|value| value.as_str())
1963 .map(CorticalID::try_from_base_64)
1964 .transpose()
1965 .map_err(|error| {
1966 BduError::InvalidArea(format!(
1967 "Invalid owned memory twin ID for {} -> {}: {}",
1968 upstream_area_id.as_base_64(),
1969 memory_area_id.as_base_64(),
1970 error
1971 ))
1972 })?;
1973 let Some(twin_id) = twin_id else {
1974 return Ok(None);
1975 };
1976
1977 let twin = self.cortical_areas.get(&twin_id).ok_or_else(|| {
1978 BduError::InvalidArea(format!(
1979 "Owned memory twin {} for {} -> {} does not exist",
1980 twin_id.as_base_64(),
1981 upstream_area_id.as_base_64(),
1982 memory_area_id.as_base_64()
1983 ))
1984 })?;
1985 let owned_by_source = twin
1986 .properties
1987 .get("memory_twin_of")
1988 .and_then(|value| value.as_str())
1989 == Some(upstream_id.as_str());
1990 let owned_by_memory = twin
1991 .properties
1992 .get("memory_twin_for")
1993 .and_then(|value| value.as_str())
1994 == Some(memory_area_id.as_base_64().as_str());
1995 if !owned_by_source || !owned_by_memory {
1996 return Err(BduError::InvalidArea(format!(
1997 "Cortical area {} is not the generated twin owned by {} -> {}",
1998 twin_id.as_base_64(),
1999 upstream_area_id.as_base_64(),
2000 memory_area_id.as_base_64()
2001 )));
2002 }
2003
2004 let memory_idx = *self.cortical_id_to_idx.get(memory_area_id).ok_or_else(|| {
2005 BduError::InvalidArea(format!(
2006 "Memory area idx missing for {}",
2007 memory_area_id.as_base_64()
2008 ))
2009 })?;
2010 let upstream_idx = *self
2011 .cortical_id_to_idx
2012 .get(upstream_area_id)
2013 .ok_or_else(|| {
2014 BduError::InvalidArea(format!(
2015 "Upstream area idx missing for {}",
2016 upstream_area_id.as_base_64()
2017 ))
2018 })?;
2019
2020 self.update_cortical_mapping(memory_area_id, &twin_id, Vec::new())?;
2021 let _ = self.regenerate_synapses_for_mapping(memory_area_id, &twin_id)?;
2022 if let Some(npu) = &self.npu {
2023 npu.lock()
2024 .unwrap()
2025 .unregister_memory_twin_mapping(memory_idx, upstream_idx);
2026 }
2027
2028 let memory_area = self.cortical_areas.get_mut(memory_area_id).ok_or_else(|| {
2029 BduError::InvalidArea(format!(
2030 "Memory area {} not found",
2031 memory_area_id.as_base_64()
2032 ))
2033 })?;
2034 let twins = memory_area
2035 .properties
2036 .get_mut("memory_twin_areas")
2037 .and_then(|value| value.as_object_mut())
2038 .ok_or_else(|| {
2039 BduError::InvalidArea(format!(
2040 "Memory area {} has invalid memory_twin_areas metadata",
2041 memory_area_id.as_base_64()
2042 ))
2043 })?;
2044 twins.remove(&upstream_id);
2045 if twins.is_empty() {
2046 memory_area.properties.remove("memory_twin_areas");
2047 }
2048
2049 for region_id in self
2050 .get_brain_region_ids()
2051 .into_iter()
2052 .cloned()
2053 .collect::<Vec<_>>()
2054 {
2055 if let Some(region) = self.get_brain_region_mut(®ion_id) {
2056 region.remove_area(&twin_id);
2057 }
2058 }
2059 self.remove_cortical_area(&twin_id)?;
2060 self.refresh_cortical_mappings_hash();
2061 Ok(Some(twin_id))
2062 }
2063
2064 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
2074 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
2075 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
2076 if let Some(arr) = upstream_prop.as_array_mut() {
2077 let src_value = serde_json::json!(src_cortical_idx);
2078 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
2079 arr.remove(pos);
2080 debug!(target: "feagi-bdu",
2081 "Removed upstream area idx={} from cortical area '{}'",
2082 src_cortical_idx, target_cortical_id.as_base_64()
2083 );
2084 }
2085 }
2086 }
2087 }
2088 }
2089
2090 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
2092 self.cortical_areas.contains_key(cortical_id)
2093 }
2094
2095 pub fn is_initialized(&self) -> bool {
2097 self.initialized && !self.cortical_areas.is_empty()
2098 }
2099
2100 pub fn add_brain_region(
2106 &mut self,
2107 region: BrainRegion,
2108 parent_id: Option<String>,
2109 ) -> BduResult<()> {
2110 self.brain_regions.add_region(region, parent_id)?;
2111 self.refresh_brain_regions_hash();
2112 Ok(())
2113 }
2114
2115 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
2117 self.brain_regions.remove_region(region_id)?;
2118 self.refresh_brain_regions_hash();
2119 Ok(())
2120 }
2121
2122 pub fn clear_brain_regions(&mut self) {
2127 self.brain_regions.clear();
2128 self.refresh_brain_regions_hash();
2129 }
2130
2131 pub fn change_brain_region_parent(
2133 &mut self,
2134 region_id: &str,
2135 new_parent_id: &str,
2136 ) -> BduResult<()> {
2137 self.brain_regions.change_parent(region_id, new_parent_id)?;
2138 self.refresh_brain_regions_hash();
2139 Ok(())
2140 }
2141
2142 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
2144 self.brain_regions.get_region(region_id)
2145 }
2146
2147 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
2149 self.brain_regions.get_region_mut(region_id)
2150 }
2151
2152 pub fn get_brain_region_ids(&self) -> Vec<&String> {
2154 self.brain_regions.get_all_region_ids()
2155 }
2156
2157 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
2159 &self.brain_regions
2160 }
2161
2162 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
2168 &self.morphology_registry
2169 }
2170
2171 pub fn get_morphology_count(&self) -> usize {
2173 self.morphology_registry.count()
2174 }
2175
2176 pub fn upsert_morphology(
2181 &mut self,
2182 morphology_id: String,
2183 morphology: feagi_evolutionary::Morphology,
2184 ) {
2185 self.morphology_registry
2186 .add_morphology(morphology_id, morphology);
2187 self.refresh_morphologies_hash();
2188 }
2189
2190 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
2196 let removed = self.morphology_registry.remove_morphology(morphology_id);
2197 if removed {
2198 self.refresh_morphologies_hash();
2199 }
2200 removed
2201 }
2202
2203 pub fn update_cortical_mapping(
2221 &mut self,
2222 src_area_id: &CorticalID,
2223 dst_area_id: &CorticalID,
2224 mapping_data: Vec<serde_json::Value>,
2225 ) -> BduResult<()> {
2226 use tracing::info;
2227
2228 self.validate_memory_outbound_mapping_contract(src_area_id, dst_area_id, &mapping_data)?;
2229 crate::region_io_designation::validate_cross_region_mapping_proposal(
2230 self,
2231 src_area_id,
2232 dst_area_id,
2233 &mapping_data,
2234 )?;
2235
2236 debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
2237
2238 {
2239 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
2241 crate::types::BduError::InvalidArea(format!(
2242 "Source area not found: {}",
2243 src_area_id
2244 ))
2245 })?;
2246
2247 let cortical_mapping_dst =
2249 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
2250 existing.as_object_mut().ok_or_else(|| {
2251 crate::types::BduError::InvalidMorphology(
2252 "cortical_mapping_dst is not an object".to_string(),
2253 )
2254 })?
2255 } else {
2256 src_area
2258 .properties
2259 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
2260 src_area
2261 .properties
2262 .get_mut("cortical_mapping_dst")
2263 .unwrap()
2264 .as_object_mut()
2265 .unwrap()
2266 };
2267
2268 if mapping_data.is_empty() {
2270 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
2272 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
2273 } else {
2274 cortical_mapping_dst.insert(
2275 dst_area_id.as_base_64(),
2276 serde_json::Value::Array(mapping_data.clone()),
2277 );
2278 debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
2279 src_area_id, dst_area_id, mapping_data.len());
2280 }
2281 }
2282
2283 self.refresh_cortical_mappings_hash();
2284
2285 Ok(())
2286 }
2287
2288 pub fn validate_memory_outbound_mapping_contract(
2295 &self,
2296 src_area_id: &CorticalID,
2297 dst_area_id: &CorticalID,
2298 mapping_data: &[serde_json::Value],
2299 ) -> BduResult<()> {
2300 if mapping_data.is_empty() {
2301 return Ok(());
2302 }
2303 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2304 BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2305 })?;
2306 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2307 BduError::InvalidArea(format!("Destination area not found: {}", dst_area_id))
2308 })?;
2309 if !matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2310 || matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2311 {
2312 return Ok(());
2313 }
2314
2315 for rule in mapping_data {
2316 let morphology_id = rule
2317 .as_object()
2318 .and_then(|rule_obj| rule_obj.get("morphology_id"))
2319 .and_then(|value| value.as_str())
2320 .or_else(|| {
2321 rule.as_array()
2322 .and_then(|rule_array| rule_array.first())
2323 .and_then(|value| value.as_str())
2324 });
2325 let is_replay_edge_to_own_twin = morphology_id == Some("memory_replay")
2326 && dst_area
2327 .properties
2328 .get("memory_twin_for")
2329 .and_then(|value| value.as_str())
2330 == Some(src_area_id.as_base_64().as_str());
2331 if morphology_id != Some("associative_memory") && !is_replay_edge_to_own_twin {
2332 return Err(BduError::InvalidMorphology(format!(
2333 "Memory-to-non-memory mapping {} -> {} only supports associative_memory \
2334 (or memory_replay to its own twin)",
2335 src_area_id, dst_area_id
2336 )));
2337 }
2338 }
2339 Ok(())
2340 }
2341
2342 pub fn regenerate_synapses_for_mapping(
2355 &mut self,
2356 src_area_id: &CorticalID,
2357 dst_area_id: &CorticalID,
2358 ) -> BduResult<usize> {
2359 use tracing::info;
2360
2361 debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
2362
2363 let mapping_rules_len = self
2364 .cortical_areas
2365 .get(src_area_id)
2366 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2367 .and_then(|v| v.as_object())
2368 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2369 .and_then(|v| v.as_array())
2370 .map(|arr| arr.len())
2371 .unwrap_or(0);
2372 tracing::debug!(
2373 target: "feagi-bdu",
2374 "Mapping rules for {} -> {}: {}",
2375 src_area_id,
2376 dst_area_id,
2377 mapping_rules_len
2378 );
2379
2380 let Some(npu_arc) = self.npu.clone() else {
2382 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
2383 return Ok(0);
2384 };
2385
2386 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2396 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
2397 })?;
2398 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2399 BduError::InvalidArea(format!(
2400 "No cortical idx for destination area {}",
2401 dst_area_id
2402 ))
2403 })?;
2404
2405 let mut pruned_synapse_count: usize = 0;
2408 use std::time::Instant;
2409 let start = Instant::now();
2410
2411 let (sources, targets) = {
2417 let lock_start = std::time::Instant::now();
2418 let npu = npu_arc.lock().unwrap();
2419 let lock_wait = lock_start.elapsed();
2420 tracing::debug!(
2421 target: "feagi-bdu",
2422 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
2423 lock_wait.as_secs_f64() * 1000.0,
2424 src_area_id,
2425 dst_area_id
2426 );
2427 let sources: Vec<NeuronId> = npu
2428 .get_neurons_in_cortical_area(src_idx)
2429 .into_iter()
2430 .map(NeuronId)
2431 .collect();
2432 let targets: Vec<NeuronId> = npu
2433 .get_neurons_in_cortical_area(dst_idx)
2434 .into_iter()
2435 .map(NeuronId)
2436 .collect();
2437 (sources, targets)
2438 };
2439
2440 tracing::debug!(
2441 target: "feagi-bdu",
2442 "Prune synapses: {} sources, {} targets",
2443 sources.len(),
2444 targets.len()
2445 );
2446
2447 if !sources.is_empty() && !targets.is_empty() {
2448 let remove_start = Instant::now();
2449 pruned_synapse_count = {
2450 let lock_start = std::time::Instant::now();
2451 let mut npu = npu_arc.lock().unwrap();
2452 let lock_wait = lock_start.elapsed();
2453 tracing::debug!(
2454 target: "feagi-bdu",
2455 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
2456 lock_wait.as_secs_f64() * 1000.0,
2457 src_area_id,
2458 dst_area_id
2459 );
2460 npu.remove_synapses_between(sources, targets)
2464 };
2465 let remove_time = remove_start.elapsed();
2466 let total_time = start.elapsed();
2467
2468 info!(
2469 target: "feagi-bdu",
2470 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2471 pruned_synapse_count,
2472 src_area_id,
2473 dst_area_id,
2474 total_time.as_millis(),
2475 remove_time.as_millis()
2476 );
2477
2478 if pruned_synapse_count > 0 {
2480 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2481 BduError::Internal(format!(
2482 "Pruned synapse count overflow (usize -> u32): {}",
2483 pruned_synapse_count
2484 ))
2485 })?;
2486 let state_manager = StateManager::instance();
2487 let state_manager = state_manager.read();
2488 let core_state = state_manager.get_core_state();
2489 core_state.subtract_synapse_count(pruned_u32);
2490 state_manager.subtract_cortical_area_outgoing_synapses(
2491 &src_area_id.as_base_64(),
2492 pruned_synapse_count,
2493 );
2494 state_manager.subtract_cortical_area_incoming_synapses(
2495 &dst_area_id.as_base_64(),
2496 pruned_synapse_count,
2497 );
2498
2499 {
2503 let mut cache = self.cached_synapse_counts_per_area.write();
2504 let entry = cache
2505 .entry(*src_area_id)
2506 .or_insert_with(|| AtomicUsize::new(0));
2507 let mut current = entry.load(Ordering::Relaxed);
2508 loop {
2509 let next = current.saturating_sub(pruned_synapse_count);
2510 match entry.compare_exchange(
2511 current,
2512 next,
2513 Ordering::Relaxed,
2514 Ordering::Relaxed,
2515 ) {
2516 Ok(_) => break,
2517 Err(v) => current = v,
2518 }
2519 }
2520 }
2521 }
2522 }
2523
2524 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2531 tracing::debug!(
2532 target: "feagi-bdu",
2533 "Synaptogenesis created {} synapses for {} -> {}",
2534 synapse_count,
2535 src_area_id,
2536 dst_area_id
2537 );
2538
2539 if synapse_count > 0 {
2542 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2543 BduError::Internal(format!(
2544 "Created synapse count overflow (usize -> u32): {}",
2545 synapse_count
2546 ))
2547 })?;
2548
2549 {
2551 let mut cache = self.cached_synapse_counts_per_area.write();
2552 cache
2553 .entry(*src_area_id)
2554 .or_insert_with(|| AtomicUsize::new(0))
2555 .fetch_add(synapse_count, Ordering::Relaxed);
2556 }
2557
2558 let state_manager = StateManager::instance();
2560 let state_manager = state_manager.read();
2561 let core_state = state_manager.get_core_state();
2562 core_state.add_synapse_count(created_u32);
2563 state_manager
2564 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2565 state_manager
2566 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2567 }
2568
2569 let src_idx_for_upstream = src_idx;
2572
2573 let has_mapping = self
2575 .cortical_areas
2576 .get(src_area_id)
2577 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2578 .and_then(|v| v.as_object())
2579 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2580 .is_some();
2581
2582 debug!(target: "feagi-bdu",
2583 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2584 synapse_count,
2585 src_area_id.as_base_64(),
2586 dst_area_id.as_base_64(),
2587 has_mapping,
2588 if has_mapping { "" } else { "NOT " }
2589 );
2590
2591 if has_mapping {
2592 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2594
2595 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2596 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2597 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2598 warn!(
2599 target: "feagi-bdu",
2600 "Failed to ensure memory twin for {} -> {}: {}",
2601 src_area_id.as_base_64(),
2602 dst_area_id.as_base_64(),
2603 e
2604 );
2605 }
2606 }
2607 }
2608
2609 #[cfg(feature = "plasticity")]
2611 if let Some(ref executor) = self.plasticity_executor {
2612 use feagi_evolutionary::extract_memory_properties;
2613
2614 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2615 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2616 let upstream_areas =
2617 self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
2618 debug!(
2619 target: "feagi-bdu",
2620 "Registering memory area idx={} id={} upstream={} depth={}",
2621 dst_area.cortical_idx,
2622 dst_area_id.as_base_64(),
2623 upstream_areas.len(),
2624 mem_props.temporal_depth
2625 );
2626
2627 if let Some(ref npu_arc) = self.npu {
2630 if let Ok(mut npu) = npu_arc.lock() {
2631 let existing_configs = npu.get_all_fire_ledger_configs();
2632 for &upstream_idx in &upstream_areas {
2633 let existing = existing_configs
2634 .iter()
2635 .find(|(idx, _)| *idx == upstream_idx)
2636 .map(|(_, w)| *w)
2637 .unwrap_or(0);
2638
2639 let desired = mem_props.temporal_depth as usize;
2640 let resolved = existing.max(desired);
2641 if resolved != existing {
2642 if let Err(e) =
2643 npu.configure_fire_ledger_window(upstream_idx, resolved)
2644 {
2645 warn!(
2646 target: "feagi-bdu",
2647 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2648 upstream_idx,
2649 resolved,
2650 e
2651 );
2652 }
2653 }
2654 }
2655 } else {
2656 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2657 }
2658 }
2659
2660 if let Ok(exec) = executor.lock() {
2661 use feagi_npu_plasticity::{
2662 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2663 };
2664
2665 let lifecycle_config = MemoryNeuronLifecycleConfig {
2666 initial_lifespan: mem_props.init_lifespan,
2667 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2668 longterm_threshold: mem_props.longterm_threshold,
2669 max_reactivations: 1000,
2670 };
2671
2672 exec.register_memory_area(
2673 dst_area.cortical_idx,
2674 dst_area_id.as_base_64(),
2675 mem_props.temporal_depth,
2676 upstream_areas.clone(),
2677 Some(lifecycle_config),
2678 mem_props.mp_learning_enabled,
2679 );
2680 } else {
2681 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2682 }
2683 } else {
2684 debug!(
2685 target: "feagi-bdu",
2686 "Skipping plasticity registration: no memory properties for area {}",
2687 dst_area_id.as_base_64()
2688 );
2689 }
2690 } else {
2691 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2692 }
2693 } else {
2694 warn!(
2695 target: "feagi-bdu",
2696 "PlasticityExecutor not available; memory area {} not registered",
2697 dst_area_id.as_base_64()
2698 );
2699 }
2700
2701 #[cfg(not(feature = "plasticity"))]
2702 {
2703 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2704 }
2705 } else {
2706 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2708
2709 let destination_is_memory = self
2710 .cortical_areas
2711 .get(dst_area_id)
2712 .is_some_and(|area| matches!(area.cortical_type, CorticalAreaType::Memory(_)));
2713 if destination_is_memory {
2714 self.teardown_owned_memory_twin_for_mapping(dst_area_id, src_area_id)?;
2715 }
2716
2717 let mut npu = npu_arc.lock().unwrap();
2719 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2720 }
2721
2722 debug!(
2723 target: "feagi-bdu",
2724 "Created {} new synapses: {} -> {}",
2725 synapse_count,
2726 src_area_id,
2727 dst_area_id
2728 );
2729
2730 if pruned_synapse_count > 0 || synapse_count == 0 {
2733 let mut npu = npu_arc.lock().unwrap();
2734 npu.rebuild_synapse_index();
2735 info!(
2736 target: "feagi-bdu",
2737 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2738 src_area_id,
2739 dst_area_id,
2740 pruned_synapse_count,
2741 synapse_count
2742 );
2743 } else {
2744 debug!(
2745 target: "feagi-bdu",
2746 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2747 src_area_id,
2748 dst_area_id,
2749 synapse_count
2750 );
2751 }
2752
2753 {
2755 let npu = npu_arc.lock().unwrap();
2756 let fresh_count = npu.get_synapse_count();
2757 self.cached_synapse_count
2758 .store(fresh_count, Ordering::Relaxed);
2759 }
2760
2761 Ok(synapse_count)
2762 }
2763
2764 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2766 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2767 }
2768
2769 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2770 v.as_u64()
2771 .map(|n| n as usize)
2772 .or_else(|| v.as_f64().map(|f| f as usize))
2773 }
2774
2775 #[allow(clippy::too_many_arguments)]
2777 fn register_stdp_mapping_for_rule(
2778 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2779 src_area_id: &CorticalID,
2780 dst_area_id: &CorticalID,
2781 src_cortical_idx: u32,
2782 dst_cortical_idx: u32,
2783 rule_obj: &serde_json::Map<String, serde_json::Value>,
2784 bidirectional_stdp: bool,
2785 synapse_psp: f32,
2786 synapse_type: feagi_npu_neural::SynapseType,
2787 ) -> BduResult<()> {
2788 let plasticity_window = rule_obj
2789 .get("plasticity_window")
2790 .and_then(Self::json_number_as_usize_for_stdp)
2791 .ok_or_else(|| {
2792 BduError::Internal(format!(
2793 "Missing plasticity_window in plastic mapping rule {} -> {}",
2794 src_area_id, dst_area_id
2795 ))
2796 })?;
2797 let plasticity_constant = rule_obj
2798 .get("plasticity_constant")
2799 .and_then(Self::json_number_as_i64_for_stdp)
2800 .ok_or_else(|| {
2801 BduError::Internal(format!(
2802 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2803 src_area_id, dst_area_id
2804 ))
2805 })?;
2806 let ltp_i64 = rule_obj
2807 .get("ltp_multiplier")
2808 .and_then(Self::json_number_as_i64_for_stdp)
2809 .ok_or_else(|| {
2810 BduError::Internal(format!(
2811 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2812 src_area_id, dst_area_id
2813 ))
2814 })?;
2815 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
2816 BduError::Internal(format!(
2817 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2818 i8::MIN,
2819 i8::MAX,
2820 ltp_i64,
2821 src_area_id,
2822 dst_area_id
2823 ))
2824 })?;
2825 let ltd_i64 = rule_obj
2826 .get("ltd_multiplier")
2827 .and_then(Self::json_number_as_i64_for_stdp)
2828 .ok_or_else(|| {
2829 BduError::Internal(format!(
2830 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2831 src_area_id, dst_area_id
2832 ))
2833 })?;
2834 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
2835 BduError::Internal(format!(
2836 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2837 i8::MIN,
2838 i8::MAX,
2839 ltd_i64,
2840 src_area_id,
2841 dst_area_id
2842 ))
2843 })?;
2844
2845 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
2849 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
2850 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2851 }
2852 Some(s) if s.eq_ignore_ascii_case("stdp") => {
2853 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
2854 }
2855 Some(s) if s.eq_ignore_ascii_case("off") => {
2856 feagi_npu_burst_engine::npu::PlasticityMode::Off
2857 }
2858 Some(other) => {
2859 return Err(BduError::Internal(format!(
2860 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
2861 other, src_area_id, dst_area_id
2862 )));
2863 }
2864 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
2865 };
2866
2867 let eligibility_decay_bursts = rule_obj
2869 .get("eligibility_decay_bursts")
2870 .and_then(|v| v.as_u64())
2871 .map(|n| n as u32)
2872 .unwrap_or(0);
2873 let reward_source_area_id = rule_obj
2874 .get("reward_source_area")
2875 .and_then(|v| v.as_str())
2876 .map(str::to_string);
2877 let punishment_source_area_id = rule_obj
2878 .get("punishment_source_area")
2879 .and_then(|v| v.as_str())
2880 .map(str::to_string);
2881
2882 let max_weight_provided = rule_obj.get("max_weight").is_some()
2887 && !rule_obj
2888 .get("max_weight")
2889 .map(|v| v.is_null())
2890 .unwrap_or(true);
2891 let max_weight: f32 = if max_weight_provided {
2892 let raw = rule_obj
2893 .get("max_weight")
2894 .and_then(|v| v.as_f64())
2895 .ok_or_else(|| {
2896 BduError::Internal(format!(
2897 "max_weight must be a number on mapping {} -> {}",
2898 src_area_id, dst_area_id
2899 ))
2900 })?;
2901 if raw.is_nan() || raw <= 0.0 {
2902 return Err(BduError::Internal(format!(
2903 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
2904 raw, src_area_id, dst_area_id
2905 )));
2906 }
2907 raw as f32
2908 } else {
2909 f32::INFINITY
2910 };
2911
2912 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
2915 && !rule_obj
2916 .get("plasticity_eta")
2917 .map(|v| v.is_null())
2918 .unwrap_or(true);
2919 let plasticity_eta: f32 = if plasticity_eta_provided {
2920 let raw = rule_obj
2921 .get("plasticity_eta")
2922 .and_then(|v| v.as_f64())
2923 .ok_or_else(|| {
2924 BduError::Internal(format!(
2925 "plasticity_eta must be a number on mapping {} -> {}",
2926 src_area_id, dst_area_id
2927 ))
2928 })?;
2929 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
2930 return Err(BduError::Internal(format!(
2931 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
2932 raw, src_area_id, dst_area_id
2933 )));
2934 }
2935 raw as f32
2936 } else {
2937 1.0
2938 };
2939
2940 if !matches!(
2942 plasticity_mode,
2943 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2944 ) && (reward_source_area_id.is_some()
2945 || punishment_source_area_id.is_some()
2946 || eligibility_decay_bursts != 0)
2947 {
2948 return Err(BduError::Internal(format!(
2949 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
2950 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
2951 src_area_id, dst_area_id
2952 )));
2953 }
2954
2955 if matches!(
2959 plasticity_mode,
2960 feagi_npu_burst_engine::npu::PlasticityMode::Off
2961 ) && max_weight_provided
2962 {
2963 return Err(BduError::Internal(format!(
2964 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2965 src_area_id, dst_area_id
2966 )));
2967 }
2968
2969 if matches!(
2970 plasticity_mode,
2971 feagi_npu_burst_engine::npu::PlasticityMode::Off
2972 ) && plasticity_eta_provided
2973 {
2974 return Err(BduError::Internal(format!(
2975 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2976 src_area_id, dst_area_id
2977 )));
2978 }
2979
2980 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2981 let mut npu_lock = npu
2982 .lock()
2983 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2984 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2985
2986 let resolve_optional_area =
2991 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
2992 let Some(name) = name_opt else {
2993 return Ok(None);
2994 };
2995 match npu_lock.get_cortical_area_id(name.as_str()) {
2996 Some(idx) => Ok(Some(idx)),
2997 None => Err(BduError::Internal(format!(
2998 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
2999 label, name, src_area_id, dst_area_id
3000 ))),
3001 }
3002 };
3003 let reward_source_area =
3004 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
3005 let punishment_source_area =
3006 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
3007
3008 if matches!(
3010 plasticity_mode,
3011 feagi_npu_burst_engine::npu::PlasticityMode::Off
3012 ) {
3013 return Ok(());
3014 }
3015
3016 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
3017 plasticity_window,
3018 plasticity_constant,
3019 ltp_multiplier,
3020 ltd_multiplier,
3021 bidirectional_stdp,
3022 associative_memory_mapping: rule_obj
3023 .get("morphology_id")
3024 .and_then(|value| value.as_str())
3025 == Some("associative_memory"),
3026 synapse_psp,
3027 synapse_type,
3028 plasticity_mode,
3029 eligibility_decay_bursts,
3030 reward_source_area,
3031 punishment_source_area,
3032 max_weight,
3033 plasticity_eta,
3034 };
3035
3036 npu_lock
3037 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
3038 .map_err(|e| {
3039 BduError::Internal(format!(
3040 "Failed to register STDP mapping {} -> {}: {}",
3041 src_area_id, dst_area_id, e
3042 ))
3043 })?;
3044
3045 let mut areas_to_track: Vec<(u32, usize)> = vec![
3049 (src_cortical_idx, plasticity_window),
3050 (dst_cortical_idx, plasticity_window),
3051 ];
3052 if let Some(area) = reward_source_area {
3053 areas_to_track.push((area, 1));
3054 }
3055 if let Some(area) = punishment_source_area {
3056 areas_to_track.push((area, 1));
3057 }
3058
3059 let existing_configs = npu_lock.get_all_fire_ledger_configs();
3060 for (area_idx, required_depth) in areas_to_track {
3061 let existing = existing_configs
3062 .iter()
3063 .find(|(idx, _)| *idx == area_idx)
3064 .map(|(_, w)| *w)
3065 .unwrap_or(0);
3066 let resolved = existing.max(required_depth);
3067 if resolved != existing {
3068 npu_lock
3069 .configure_fire_ledger_window(area_idx, resolved)
3070 .map_err(|e| {
3071 BduError::Internal(format!(
3072 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
3073 area_idx, resolved, e
3074 ))
3075 })?;
3076 }
3077 }
3078
3079 Ok(())
3080 }
3081
3082 fn resolve_synapse_params_for_rule(
3084 &self,
3085 src_area_id: &CorticalID,
3086 rule: &serde_json::Value,
3087 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
3088 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3090 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3091 })?;
3092
3093 let (weight, synapse_type) = {
3095 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
3096 if let Some(i) = v.as_i64() {
3097 return Some(i as f64);
3098 }
3099 v.as_f64()
3100 };
3101
3102 let mult: f64 = if let Some(obj) = rule.as_object() {
3103 obj.get("postSynapticCurrent_multiplier")
3104 .and_then(parse_f64)
3105 .unwrap_or(1.0)
3106 } else if let Some(arr) = rule.as_array() {
3107 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
3108 } else {
3109 128.0
3110 };
3111
3112 if mult < 0.0 {
3113 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
3114 } else {
3115 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
3116 }
3117 };
3118
3119 use crate::models::cortical_area::CorticalAreaExt;
3121 let psp_f32 = src_area.postsynaptic_current();
3122
3123 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
3124 obj.get("synaptic_delay_bursts")
3125 .and_then(|v| v.as_u64())
3126 .map(|d| u8::try_from(d).unwrap_or(1))
3127 .unwrap_or(1)
3128 } else if let Some(arr) = rule.as_array() {
3129 arr.get(8)
3130 .and_then(|v| v.as_u64())
3131 .map(|d| u8::try_from(d).unwrap_or(1))
3132 .unwrap_or(1)
3133 } else {
3134 1
3135 };
3136 if delay_bursts < 1 {
3137 return Err(crate::types::BduError::Internal(format!(
3138 "synaptic_delay_bursts must be >= 1 (src area {})",
3139 src_area_id.as_base_64()
3140 )));
3141 }
3142
3143 tracing::debug!(
3144 target: "feagi-bdu",
3145 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
3146 src_area_id.as_base_64(),
3147 weight,
3148 psp_f32,
3149 synapse_type,
3150 delay_bursts
3151 );
3152
3153 Ok((weight, psp_f32, synapse_type, delay_bursts))
3154 }
3155
3156 fn apply_cortical_mapping_for_pair(
3158 &mut self,
3159 src_area_id: &CorticalID,
3160 dst_area_id: &CorticalID,
3161 ) -> BduResult<usize> {
3162 let rules = {
3168 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3169 crate::types::BduError::InvalidArea(format!(
3170 "Source area not found: {}",
3171 src_area_id
3172 ))
3173 })?;
3174
3175 let Some(mapping_dst) = src_area
3176 .properties
3177 .get("cortical_mapping_dst")
3178 .and_then(|v| v.as_object())
3179 else {
3180 return Ok(0);
3181 };
3182
3183 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
3184 else {
3185 return Ok(0);
3186 };
3187
3188 rules.clone()
3189 }; if rules.is_empty() {
3192 return Ok(0);
3193 }
3194
3195 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3197 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
3198 })?;
3199 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3200 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
3201 })?;
3202
3203 let npu_arc = self
3205 .npu
3206 .as_ref()
3207 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
3208 .clone();
3209
3210 tracing::debug!(
3211 target: "feagi-bdu",
3212 "Applying {} mapping rule(s) for {} -> {}",
3213 rules.len(),
3214 src_area_id,
3215 dst_area_id
3216 );
3217 let mut total_synapses = 0;
3219 for rule in &rules {
3220 let morphology_id = if let Some(rule_obj) = rule.as_object() {
3221 rule_obj
3222 .get("morphology_id")
3223 .and_then(|v| v.as_str())
3224 .unwrap_or("unknown")
3225 .to_string()
3226 } else if let Some(rule_arr) = rule.as_array() {
3227 rule_arr
3228 .first()
3229 .and_then(|v| v.as_str())
3230 .unwrap_or("unknown")
3231 .to_string()
3232 } else {
3233 "unknown".to_string()
3234 };
3235
3236 let rule_keys: Vec<String> = rule
3237 .as_object()
3238 .map(|obj| obj.keys().cloned().collect())
3239 .unwrap_or_default();
3240
3241 let mut plasticity_flag = rule
3243 .as_object()
3244 .and_then(|obj| obj.get("plasticity_flag"))
3245 .and_then(|v| v.as_bool())
3246 .unwrap_or(false);
3247 if morphology_id == "associative_memory" {
3248 plasticity_flag = true;
3249 }
3250 if plasticity_flag {
3251 let Some(rule_obj) = rule.as_object() else {
3252 return Err(crate::types::BduError::InvalidMorphology(
3253 "Plasticity mapping rule must be an object format".to_string(),
3254 ));
3255 };
3256 let (_weight, psp, synapse_type, _delay_bursts) =
3257 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3258 let bidirectional_stdp = false;
3261 if let Err(e) = Self::register_stdp_mapping_for_rule(
3262 &npu_arc,
3263 src_area_id,
3264 dst_area_id,
3265 src_cortical_idx,
3266 dst_cortical_idx,
3267 rule_obj,
3268 bidirectional_stdp,
3269 psp,
3270 synapse_type,
3271 ) {
3272 tracing::error!(
3273 target: "feagi-bdu",
3274 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
3275 src_area_id,
3276 dst_area_id,
3277 morphology_id,
3278 rule_keys,
3279 e
3280 );
3281 return Err(e);
3282 }
3283 }
3284
3285 if let Some(gate_area_str) = rule
3289 .as_object()
3290 .and_then(|obj| obj.get("gate_source_area"))
3291 .and_then(|v| v.as_str())
3292 {
3293 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
3294 crate::types::BduError::Internal(format!(
3295 "Invalid gate_source_area '{}' on mapping {} -> {}",
3296 gate_area_str, src_area_id, dst_area_id
3297 ))
3298 })?;
3299 let gate_cortical_idx =
3300 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
3301 crate::types::BduError::Internal(format!(
3302 "Unknown gate_source_area '{}' on mapping {} -> {}",
3303 gate_area_str, src_area_id, dst_area_id
3304 ))
3305 })?;
3306
3307 let mut npu_lock = npu_arc.lock().map_err(|_| {
3308 crate::types::BduError::Internal(
3309 "Failed to acquire NPU lock for gate registration".to_string(),
3310 )
3311 })?;
3312 if let Err(e) = npu_lock.register_gate_mapping(
3313 src_cortical_idx,
3314 dst_cortical_idx,
3315 *gate_cortical_idx,
3316 ) {
3317 tracing::error!(
3318 target: "feagi-bdu",
3319 "Gate mapping registration failed for {} -> {} (gate={}): {}",
3320 src_area_id,
3321 dst_area_id,
3322 gate_area_str,
3323 e
3324 );
3325 return Err(crate::types::BduError::Internal(format!(
3326 "Gate registration failed: {}",
3327 e
3328 )));
3329 }
3330 }
3331
3332 let synapse_count = match self.apply_single_morphology_rule(
3334 src_area_id,
3335 dst_area_id,
3336 rule,
3337 ) {
3338 Ok(count) => count,
3339 Err(e) => {
3340 tracing::error!(
3341 target: "feagi-bdu",
3342 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
3343 src_area_id,
3344 dst_area_id,
3345 morphology_id,
3346 rule_keys,
3347 e
3348 );
3349 return Err(e);
3350 }
3351 };
3352 total_synapses += synapse_count;
3353 tracing::debug!(
3354 target: "feagi-bdu",
3355 "Rule {} created {} synapses for {} -> {}",
3356 morphology_id,
3357 synapse_count,
3358 src_area_id,
3359 dst_area_id
3360 );
3361 }
3362
3363 Ok(total_synapses)
3364 }
3365
3366 #[allow(clippy::too_many_arguments)]
3380 fn apply_function_morphology(
3381 &self,
3382 morphology_id: &str,
3383 rule: &serde_json::Value,
3384 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3385 npu: &mut feagi_npu_burst_engine::DynamicNPU,
3386 src_area_id: &CorticalID,
3387 dst_area_id: &CorticalID,
3388 src_idx: u32,
3389 dst_idx: u32,
3390 weight: f32,
3391 psp: f32,
3392 synapse_attractivity: u8,
3393 synapse_type: feagi_npu_neural::SynapseType,
3394 delay_bursts: u8,
3395 ) -> BduResult<usize> {
3396 match morphology_id {
3397 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
3398 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3400 crate::types::BduError::InvalidArea(format!(
3401 "Source area not found: {}",
3402 src_area_id
3403 ))
3404 })?;
3405 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3406 crate::types::BduError::InvalidArea(format!(
3407 "Destination area not found: {}",
3408 dst_area_id
3409 ))
3410 })?;
3411
3412 let src_dimensions = (
3413 src_area.dimensions.width as usize,
3414 src_area.dimensions.height as usize,
3415 src_area.dimensions.depth as usize,
3416 );
3417 let dst_dimensions = (
3418 dst_area.dimensions.width as usize,
3419 dst_area.dimensions.height as usize,
3420 dst_area.dimensions.depth as usize,
3421 );
3422
3423 let transpose = match morphology_id {
3426 "transpose_xy" => Some((1, 0, 2)),
3427 "transpose_yz" => Some((0, 2, 1)),
3428 "transpose_xz" => Some((2, 1, 0)),
3429 _ => None,
3430 };
3431
3432 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3433 let count = apply_projector_morphology_with_dimensions(
3434 npu,
3435 src_idx,
3436 dst_idx,
3437 src_dimensions,
3438 dst_dimensions,
3439 transpose,
3440 None, weight,
3442 psp,
3443 synapse_attractivity,
3444 synapse_type,
3445 0,
3446 delay_bursts,
3447 )?;
3448 npu.rebuild_synapse_index();
3450 Ok(count as usize)
3451 }
3452 "centered_projector" => {
3453 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3454 crate::types::BduError::InvalidArea(format!(
3455 "Source area not found: {}",
3456 src_area_id
3457 ))
3458 })?;
3459 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3460 crate::types::BduError::InvalidArea(format!(
3461 "Destination area not found: {}",
3462 dst_area_id
3463 ))
3464 })?;
3465
3466 let src_dimensions = (
3467 src_area.dimensions.width as usize,
3468 src_area.dimensions.height as usize,
3469 src_area.dimensions.depth as usize,
3470 );
3471 let dst_dimensions = (
3472 dst_area.dimensions.width as usize,
3473 dst_area.dimensions.height as usize,
3474 dst_area.dimensions.depth as usize,
3475 );
3476
3477 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
3478 npu,
3479 src_idx,
3480 dst_idx,
3481 src_dimensions,
3482 dst_dimensions,
3483 weight,
3484 psp,
3485 synapse_attractivity,
3486 synapse_type,
3487 delay_bursts,
3488 )?;
3489 if count > 0 {
3490 npu.rebuild_synapse_index();
3491 }
3492 Ok(count as usize)
3493 }
3494 "episodic_memory" => {
3495 use tracing::trace;
3498 trace!(
3499 target: "feagi-bdu",
3500 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
3501 src_idx, dst_idx
3502 );
3503 Ok(0)
3504 }
3505 "memory_replay" => {
3506 use tracing::trace;
3508 trace!(
3509 target: "feagi-bdu",
3510 "Memory replay morphology: {} -> {} (no physical synapses)",
3511 src_idx, dst_idx
3512 );
3513 Ok(0)
3514 }
3515 "associative_memory" => {
3516 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3520 crate::types::BduError::InvalidArea(format!(
3521 "Source area not found: {}",
3522 src_area_id
3523 ))
3524 })?;
3525 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3526 crate::types::BduError::InvalidArea(format!(
3527 "Destination area not found: {}",
3528 dst_area_id
3529 ))
3530 })?;
3531
3532 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3533 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3534 {
3535 let src_dimensions = (
3536 src_area.dimensions.width as usize,
3537 src_area.dimensions.height as usize,
3538 src_area.dimensions.depth as usize,
3539 );
3540 let dst_dimensions = (
3541 dst_area.dimensions.width as usize,
3542 dst_area.dimensions.height as usize,
3543 dst_area.dimensions.depth as usize,
3544 );
3545 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3546 let count = apply_projector_morphology_with_dimensions(
3547 npu,
3548 src_idx,
3549 dst_idx,
3550 src_dimensions,
3551 dst_dimensions,
3552 None,
3553 None,
3554 weight,
3555 psp,
3556 synapse_attractivity,
3557 synapse_type,
3558 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
3559 delay_bursts,
3560 )?;
3561 npu.rebuild_synapse_index();
3562 Ok(count as usize)
3563 } else {
3564 Ok(0)
3565 }
3566 }
3567 "block_to_block" => {
3568 tracing::warn!(
3569 target: "feagi-bdu",
3570 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
3571 src_idx, dst_idx
3572 );
3573 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3575 crate::types::BduError::InvalidArea(format!(
3576 "Source area not found: {}",
3577 src_area_id
3578 ))
3579 })?;
3580 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3581 crate::types::BduError::InvalidArea(format!(
3582 "Destination area not found: {}",
3583 dst_area_id
3584 ))
3585 })?;
3586
3587 let src_dimensions = (
3588 src_area.dimensions.width as usize,
3589 src_area.dimensions.height as usize,
3590 src_area.dimensions.depth as usize,
3591 );
3592 let dst_dimensions = (
3593 dst_area.dimensions.width as usize,
3594 dst_area.dimensions.height as usize,
3595 dst_area.dimensions.depth as usize,
3596 );
3597
3598 let scalar = if let Some(obj) = rule.as_object() {
3600 if let Some(scalar_arr) =
3602 obj.get("morphology_scalar").and_then(|v| v.as_array())
3603 {
3604 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
3606 } else {
3607 1 }
3609 } else if let Some(arr) = rule.as_array() {
3610 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
3612 } else {
3613 1 };
3615
3616 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
3619 let count = if estimated_neurons > 100_000 {
3620 let _ = npu;
3622
3623 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
3624 npu_arc,
3625 src_idx,
3626 dst_idx,
3627 src_dimensions,
3628 dst_dimensions,
3629 scalar, weight,
3631 psp,
3632 synapse_attractivity,
3633 synapse_type,
3634 delay_bursts,
3635 )? as usize
3636 } else {
3637 tracing::warn!(
3639 target: "feagi-bdu",
3640 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
3641 src_idx, dst_idx, src_dimensions, dst_dimensions
3642 );
3643 let count =
3644 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
3645 npu,
3646 src_idx,
3647 dst_idx,
3648 src_dimensions,
3649 dst_dimensions,
3650 scalar, weight,
3652 psp,
3653 synapse_attractivity,
3654 synapse_type,
3655 delay_bursts,
3656 )? as usize;
3657 tracing::warn!(
3658 target: "feagi-bdu",
3659 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
3660 count
3661 );
3662 if count > 0 {
3664 npu.rebuild_synapse_index();
3665 }
3666 count
3667 };
3668
3669 if count > 0 && estimated_neurons > 100_000 {
3671 let mut npu_lock = npu_arc.lock().unwrap();
3672 npu_lock.rebuild_synapse_index();
3673 }
3674
3675 Ok(count)
3676 }
3677 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
3678 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
3679 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3680 crate::types::BduError::InvalidArea(format!(
3681 "Source area not found: {}",
3682 src_area_id
3683 ))
3684 })?;
3685 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3686 crate::types::BduError::InvalidArea(format!(
3687 "Destination area not found: {}",
3688 dst_area_id
3689 ))
3690 })?;
3691
3692 let src_dimensions = (
3693 src_area.dimensions.width as usize,
3694 src_area.dimensions.height as usize,
3695 src_area.dimensions.depth as usize,
3696 );
3697 let dst_dimensions = (
3698 dst_area.dimensions.width as usize,
3699 dst_area.dimensions.height as usize,
3700 dst_area.dimensions.depth as usize,
3701 );
3702
3703 let (axis, mode) = match morphology_id {
3704 "bitmask_encoder_x" => (
3705 crate::connectivity::core_morphologies::BitmaskAxis::X,
3706 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3707 ),
3708 "bitmask_encoder_y" => (
3709 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3710 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3711 ),
3712 "bitmask_encoder_z" => (
3713 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3714 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3715 ),
3716 "bitmask_decoder_x" => (
3717 crate::connectivity::core_morphologies::BitmaskAxis::X,
3718 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3719 ),
3720 "bitmask_decoder_y" => (
3721 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3722 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3723 ),
3724 "bitmask_decoder_z" => (
3725 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3726 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3727 ),
3728 _ => unreachable!("matched bitmask morphology above"),
3729 };
3730
3731 let count =
3732 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
3733 npu,
3734 src_idx,
3735 dst_idx,
3736 src_dimensions,
3737 dst_dimensions,
3738 axis,
3739 mode,
3740 weight,
3741 psp,
3742 synapse_attractivity,
3743 synapse_type,
3744 delay_bursts,
3745 )?;
3746 if count > 0 {
3747 npu.rebuild_synapse_index();
3748 }
3749 Ok(count as usize)
3750 }
3751 "sweeper" => {
3752 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3753 crate::types::BduError::InvalidArea(format!(
3754 "Destination area not found: {}",
3755 dst_area_id
3756 ))
3757 })?;
3758 let dst_dimensions = (
3759 dst_area.dimensions.width as usize,
3760 dst_area.dimensions.height as usize,
3761 dst_area.dimensions.depth as usize,
3762 );
3763
3764 let count =
3765 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3766 npu,
3767 src_idx,
3768 dst_idx,
3769 dst_dimensions,
3770 weight,
3771 psp,
3772 synapse_attractivity,
3773 synapse_type,
3774 delay_bursts,
3775 )?;
3776 if count > 0 {
3777 npu.rebuild_synapse_index();
3778 }
3779 Ok(count as usize)
3780 }
3781 "last_to_first" => {
3782 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3783 crate::types::BduError::InvalidArea(format!(
3784 "Source area not found: {}",
3785 src_area_id
3786 ))
3787 })?;
3788 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3789 crate::types::BduError::InvalidArea(format!(
3790 "Destination area not found: {}",
3791 dst_area_id
3792 ))
3793 })?;
3794 let src_dimensions = (
3795 src_area.dimensions.width as usize,
3796 src_area.dimensions.height as usize,
3797 src_area.dimensions.depth as usize,
3798 );
3799 let dst_dimensions = (
3800 dst_area.dimensions.width as usize,
3801 dst_area.dimensions.height as usize,
3802 dst_area.dimensions.depth as usize,
3803 );
3804
3805 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3806 npu,
3807 src_idx,
3808 dst_idx,
3809 src_dimensions,
3810 dst_dimensions,
3811 weight,
3812 psp,
3813 synapse_attractivity,
3814 synapse_type,
3815 delay_bursts,
3816 )?;
3817 if count > 0 {
3818 npu.rebuild_synapse_index();
3819 }
3820 Ok(count as usize)
3821 }
3822 "first_to_last" => {
3823 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3824 crate::types::BduError::InvalidArea(format!(
3825 "Source area not found: {}",
3826 src_area_id
3827 ))
3828 })?;
3829 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3830 crate::types::BduError::InvalidArea(format!(
3831 "Destination area not found: {}",
3832 dst_area_id
3833 ))
3834 })?;
3835 let src_dimensions = (
3836 src_area.dimensions.width as usize,
3837 src_area.dimensions.height as usize,
3838 src_area.dimensions.depth as usize,
3839 );
3840 let dst_dimensions = (
3841 dst_area.dimensions.width as usize,
3842 dst_area.dimensions.height as usize,
3843 dst_area.dimensions.depth as usize,
3844 );
3845
3846 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
3847 npu,
3848 src_idx,
3849 dst_idx,
3850 src_dimensions,
3851 dst_dimensions,
3852 weight,
3853 psp,
3854 synapse_attractivity,
3855 synapse_type,
3856 delay_bursts,
3857 )?;
3858 if count > 0 {
3859 npu.rebuild_synapse_index();
3860 }
3861 Ok(count as usize)
3862 }
3863 "rotator_z" => {
3864 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3865 crate::types::BduError::InvalidArea(format!(
3866 "Source area not found: {}",
3867 src_area_id
3868 ))
3869 })?;
3870 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3871 crate::types::BduError::InvalidArea(format!(
3872 "Destination area not found: {}",
3873 dst_area_id
3874 ))
3875 })?;
3876 let src_dimensions = (
3877 src_area.dimensions.width as usize,
3878 src_area.dimensions.height as usize,
3879 src_area.dimensions.depth as usize,
3880 );
3881 let dst_dimensions = (
3882 dst_area.dimensions.width as usize,
3883 dst_area.dimensions.height as usize,
3884 dst_area.dimensions.depth as usize,
3885 );
3886
3887 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3888 npu,
3889 src_idx,
3890 dst_idx,
3891 src_dimensions,
3892 dst_dimensions,
3893 weight,
3894 psp,
3895 synapse_attractivity,
3896 synapse_type,
3897 delay_bursts,
3898 )?;
3899 if count > 0 {
3900 npu.rebuild_synapse_index();
3901 }
3902 Ok(count as usize)
3903 }
3904 _ => {
3905 use tracing::debug;
3908 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3909 Ok(0)
3910 }
3911 }
3912 }
3913
3914 fn apply_single_morphology_rule(
3916 &mut self,
3917 src_area_id: &CorticalID,
3918 dst_area_id: &CorticalID,
3919 rule: &serde_json::Value,
3920 ) -> BduResult<usize> {
3921 let morphology_id = if let Some(arr) = rule.as_array() {
3923 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3924 } else if let Some(obj) = rule.as_object() {
3925 obj.get("morphology_id")
3926 .and_then(|v| v.as_str())
3927 .unwrap_or("")
3928 } else {
3929 return Ok(0);
3930 };
3931
3932 if morphology_id.is_empty() {
3933 return Ok(0);
3934 }
3935
3936 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3938 crate::types::BduError::InvalidMorphology(format!(
3939 "Morphology not found: {}",
3940 morphology_id
3941 ))
3942 })?;
3943
3944 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3946 crate::types::BduError::InvalidArea(format!(
3947 "Source area ID not found: {}",
3948 src_area_id
3949 ))
3950 })?;
3951 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3952 crate::types::BduError::InvalidArea(format!(
3953 "Destination area ID not found: {}",
3954 dst_area_id
3955 ))
3956 })?;
3957
3958 if let Some(ref npu_arc) = self.npu {
3960 let lock_start = std::time::Instant::now();
3961 let mut npu = npu_arc.lock().unwrap();
3962 let lock_wait = lock_start.elapsed();
3963 tracing::debug!(
3964 target: "feagi-bdu",
3965 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3966 lock_wait.as_secs_f64() * 1000.0,
3967 src_area_id,
3968 dst_area_id,
3969 morphology_id
3970 );
3971
3972 let (weight, psp, synapse_type, delay_bursts) =
3973 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3974
3975 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3977 obj.get("synapse_attractivity")
3978 .and_then(|v| v.as_u64())
3979 .unwrap_or(100) as u8
3980 } else {
3981 100 };
3983
3984 match morphology.morphology_type {
3985 feagi_evolutionary::MorphologyType::Functions => {
3986 tracing::debug!(
3987 target: "feagi-bdu",
3988 "apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3989 morphology_id
3990 );
3991 self.apply_function_morphology(
3994 morphology_id,
3995 rule,
3996 npu_arc,
3997 &mut npu,
3998 src_area_id,
3999 dst_area_id,
4000 *src_idx,
4001 *dst_idx,
4002 weight,
4003 psp,
4004 synapse_attractivity,
4005 synapse_type,
4006 delay_bursts,
4007 )
4008 }
4009 feagi_evolutionary::MorphologyType::Vectors => {
4010 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
4011
4012 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4014 crate::types::BduError::InvalidArea(format!(
4015 "Destination area not found: {}",
4016 dst_area_id
4017 ))
4018 })?;
4019
4020 let dst_dimensions = (
4021 dst_area.dimensions.width as usize,
4022 dst_area.dimensions.height as usize,
4023 dst_area.dimensions.depth as usize,
4024 );
4025
4026 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
4027 morphology.parameters
4028 {
4029 let vectors_tuples: Vec<(i32, i32, i32)> =
4031 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
4032
4033 let count = apply_vectors_morphology_with_dimensions(
4034 &mut npu,
4035 *src_idx,
4036 *dst_idx,
4037 vectors_tuples,
4038 dst_dimensions,
4039 weight, psp, synapse_attractivity, synapse_type,
4043 delay_bursts,
4044 )?;
4045 npu.rebuild_synapse_index();
4048 Ok(count as usize)
4049 } else {
4050 Ok(0)
4051 }
4052 }
4053 feagi_evolutionary::MorphologyType::Patterns => {
4054 use crate::connectivity::core_morphologies::apply_patterns_morphology;
4055 use crate::connectivity::rules::patterns::{
4056 Pattern3D, PatternElement as RulePatternElement,
4057 };
4058 use feagi_evolutionary::PatternElement as EvoPatternElement;
4059
4060 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
4061 morphology.parameters
4062 else {
4063 return Ok(0);
4064 };
4065
4066 let convert_element =
4067 |element: &EvoPatternElement|
4068 -> crate::types::BduResult<RulePatternElement> {
4069 match element {
4070 EvoPatternElement::Value(value) => {
4071 if *value < 0 {
4072 return Err(crate::types::BduError::InvalidMorphology(
4073 format!(
4074 "Pattern morphology {} contains negative voxel coordinate {}",
4075 morphology_id, value
4076 ),
4077 ));
4078 }
4079 Ok(RulePatternElement::Exact(*value))
4080 }
4081 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
4082 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
4083 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
4084 EvoPatternElement::DirectionPositive => {
4085 Ok(RulePatternElement::DirectionExclusive(
4086 crate::connectivity::rules::patterns::Direction::Positive,
4087 ))
4088 }
4089 EvoPatternElement::DirectionNegative => {
4090 Ok(RulePatternElement::DirectionExclusive(
4091 crate::connectivity::rules::patterns::Direction::Negative,
4092 ))
4093 }
4094 EvoPatternElement::DirectionPositiveInclusive => {
4095 Ok(RulePatternElement::DirectionInclusive(
4096 crate::connectivity::rules::patterns::Direction::Positive,
4097 ))
4098 }
4099 EvoPatternElement::DirectionNegativeInclusive => {
4100 Ok(RulePatternElement::DirectionInclusive(
4101 crate::connectivity::rules::patterns::Direction::Negative,
4102 ))
4103 }
4104 EvoPatternElement::Offset(off) => {
4105 Ok(RulePatternElement::Offset(*off))
4106 }
4107 EvoPatternElement::Range(lo, hi) => {
4108 Ok(RulePatternElement::Range(*lo, *hi))
4109 }
4110 }
4111 };
4112
4113 let mut converted_patterns = Vec::with_capacity(patterns.len());
4114 for pattern_pair in patterns {
4115 if pattern_pair.len() != 2 {
4116 return Err(crate::types::BduError::InvalidMorphology(format!(
4117 "Pattern morphology {} must contain [src, dst] pairs",
4118 morphology_id
4119 )));
4120 }
4121
4122 let src_pattern = &pattern_pair[0];
4123 let dst_pattern = &pattern_pair[1];
4124
4125 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
4126 return Err(crate::types::BduError::InvalidMorphology(format!(
4127 "Pattern morphology {} requires 3-axis patterns",
4128 morphology_id
4129 )));
4130 }
4131
4132 let src: Pattern3D = (
4133 convert_element(&src_pattern[0])?,
4134 convert_element(&src_pattern[1])?,
4135 convert_element(&src_pattern[2])?,
4136 );
4137 let dst: Pattern3D = (
4138 convert_element(&dst_pattern[0])?,
4139 convert_element(&dst_pattern[1])?,
4140 convert_element(&dst_pattern[2])?,
4141 );
4142
4143 converted_patterns.push((src, dst));
4144 }
4145
4146 let count = apply_patterns_morphology(
4147 &mut npu,
4148 *src_idx,
4149 *dst_idx,
4150 converted_patterns,
4151 weight,
4152 psp,
4153 synapse_attractivity,
4154 synapse_type,
4155 delay_bursts,
4156 )?;
4157 if count > 0 {
4158 npu.rebuild_synapse_index();
4159 }
4160 Ok(count as usize)
4161 }
4162 feagi_evolutionary::MorphologyType::Composite => {
4163 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
4164 morphology.parameters
4165 else {
4166 return Ok(0);
4167 };
4168
4169 if morphology_id != "tile" {
4170 use tracing::debug;
4171 debug!(
4172 target: "feagi-bdu",
4173 "Composite morphology {} not yet implemented",
4174 morphology_id
4175 );
4176 return Ok(0);
4177 }
4178
4179 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4180 crate::types::BduError::InvalidArea(format!(
4181 "Source area not found: {}",
4182 src_area_id
4183 ))
4184 })?;
4185 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4186 crate::types::BduError::InvalidArea(format!(
4187 "Destination area not found: {}",
4188 dst_area_id
4189 ))
4190 })?;
4191 let src_dimensions = (
4192 src_area.dimensions.width as usize,
4193 src_area.dimensions.height as usize,
4194 src_area.dimensions.depth as usize,
4195 );
4196 let dst_dimensions = (
4197 dst_area.dimensions.width as usize,
4198 dst_area.dimensions.height as usize,
4199 dst_area.dimensions.depth as usize,
4200 );
4201
4202 let count =
4203 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
4204 &mut npu,
4205 *src_idx,
4206 *dst_idx,
4207 src_dimensions,
4208 dst_dimensions,
4209 weight,
4210 psp,
4211 synapse_attractivity,
4212 synapse_type,
4213 delay_bursts,
4214 )?;
4215 if count > 0 {
4216 npu.rebuild_synapse_index();
4217 }
4218 Ok(count as usize)
4219 }
4220 }
4221 } else {
4222 Ok(0) }
4224 }
4225
4226 pub fn set_npu(
4239 &mut self,
4240 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4241 ) {
4242 self.npu = Some(Arc::clone(&npu));
4243 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
4244
4245 #[cfg(not(feature = "wasm"))]
4248 {
4249 use feagi_state_manager::StateManager;
4250 let state_manager = StateManager::instance();
4251 let state_manager = state_manager.read();
4252 let core_state = state_manager.get_core_state();
4253 core_state.set_neuron_capacity(self.config.max_neurons as u32);
4255 core_state.set_synapse_capacity(self.config.max_synapses as u32);
4256 info!(
4257 target: "feagi-bdu",
4258 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
4259 self.config.max_neurons, self.config.max_synapses
4260 );
4261 }
4262
4263 let existing_area_count = self.cortical_id_to_idx.len();
4270 if existing_area_count > 0 {
4271 match npu.lock() {
4272 Ok(mut npu_lock) => {
4273 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
4274 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
4275 }
4276 info!(
4277 target: "feagi-bdu",
4278 "🔁 Backfilled {} cortical area registrations into NPU",
4279 existing_area_count
4280 );
4281 }
4282 Err(e) => {
4283 warn!(
4284 target: "feagi-bdu",
4285 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
4286 e
4287 );
4288 }
4289 }
4290 }
4291
4292 self.update_all_cached_stats();
4294 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
4295 self.get_neuron_count(), self.get_synapse_count());
4296 }
4297
4298 pub fn has_npu(&self) -> bool {
4300 self.npu.is_some()
4301 }
4302
4303 pub fn get_npu(
4310 &self,
4311 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
4312 {
4313 self.npu.as_ref()
4314 }
4315
4316 pub fn rebind_npu_runtime_after_connectome_apply(&mut self) -> BduResult<()> {
4322 #[cfg(feature = "plasticity")]
4323 if let Some(executor) = self.plasticity_executor.as_ref() {
4324 executor
4325 .lock()
4326 .map_err(|_| {
4327 BduError::Internal(
4328 "Failed to lock PlasticityExecutor for registration reset".to_string(),
4329 )
4330 })?
4331 .clear_memory_area_registrations()
4332 .map_err(BduError::Internal)?;
4333 }
4334 self.refresh_all_upstream_cortical_areas_from_mappings();
4335 self.rebuild_memory_twin_mappings()?;
4336 self.rebind_memory_twin_mappings_to_npu()?;
4337 self.rebind_stdp_mappings_to_npu()?;
4338 #[cfg(feature = "plasticity")]
4339 self.reregister_memory_areas_with_plasticity()?;
4340 Ok(())
4341 }
4342
4343 pub fn refresh_all_upstream_cortical_areas_from_mappings(&mut self) {
4349 let area_ids: Vec<CorticalID> = self.cortical_areas.keys().copied().collect();
4350 for area_id in area_ids {
4351 self.refresh_upstream_cortical_areas_from_mappings(&area_id);
4352 }
4353 }
4354
4355 pub fn rebuild_memory_twin_mappings(&mut self) -> BduResult<usize> {
4365 let jobs = self.collect_memory_twin_rebuild_jobs()?;
4366 let mut restored = 0usize;
4367 for (memory_id, upstream_id, known_twin) in jobs {
4368 match known_twin {
4369 Some(twin_id) => {
4370 self.restore_memory_twin_index(&memory_id, &upstream_id, &twin_id)?;
4371 restored += 1;
4372 }
4373 None => {
4374 self.ensure_memory_twin_area(&memory_id, &upstream_id)?;
4375 restored += 1;
4376 }
4377 }
4378 }
4379 if restored > 0 {
4380 info!(
4381 target: "feagi-bdu",
4382 "Rebuilt {} memory twin mapping(s) after connectome apply",
4383 restored
4384 );
4385 self.refresh_cortical_mappings_hash();
4386 }
4387 Ok(restored)
4388 }
4389
4390 fn collect_memory_twin_rebuild_jobs(
4391 &self,
4392 ) -> BduResult<Vec<(CorticalID, CorticalID, Option<CorticalID>)>> {
4393 let mut jobs: Vec<(CorticalID, CorticalID, Option<CorticalID>)> = Vec::new();
4394 let mut seen: HashSet<(CorticalID, CorticalID)> = HashSet::new();
4395
4396 for (memory_id, memory_area) in &self.cortical_areas {
4397 if !Self::area_is_memory(memory_area) {
4398 continue;
4399 }
4400 let Some(dstmap) = memory_area
4401 .properties
4402 .get("cortical_mapping_dst")
4403 .and_then(|value| value.as_object())
4404 else {
4405 continue;
4406 };
4407 for (dst_b64, rules) in dstmap {
4408 if !Self::mapping_rules_use_morphology(rules, "memory_replay") {
4409 continue;
4410 }
4411 let twin_id = match CorticalID::try_from_base_64(dst_b64) {
4412 Ok(id) => id,
4413 Err(_) => continue,
4414 };
4415 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
4416 continue;
4417 };
4418 let Some(upstream_b64) = twin_area
4419 .properties
4420 .get("memory_twin_of")
4421 .and_then(|value| value.as_str())
4422 else {
4423 continue;
4424 };
4425 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
4426 BduError::InvalidArea(format!(
4427 "Invalid memory_twin_of '{}' on {}: {}",
4428 upstream_b64,
4429 twin_id.as_base_64(),
4430 error
4431 ))
4432 })?;
4433 if seen.insert((*memory_id, upstream_id)) {
4434 jobs.push((*memory_id, upstream_id, Some(twin_id)));
4435 }
4436 }
4437 }
4438
4439 for (twin_id, twin_area) in &self.cortical_areas {
4440 let Some(upstream_b64) = twin_area
4441 .properties
4442 .get("memory_twin_of")
4443 .and_then(|value| value.as_str())
4444 else {
4445 continue;
4446 };
4447 let Some(memory_b64) = twin_area
4448 .properties
4449 .get("memory_twin_for")
4450 .and_then(|value| value.as_str())
4451 else {
4452 continue;
4453 };
4454 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
4455 BduError::InvalidArea(format!(
4456 "Invalid memory_twin_of '{}' on {}: {}",
4457 upstream_b64,
4458 twin_id.as_base_64(),
4459 error
4460 ))
4461 })?;
4462 let memory_id = CorticalID::try_from_base_64(memory_b64).map_err(|error| {
4463 BduError::InvalidArea(format!(
4464 "Invalid memory_twin_for '{}' on {}: {}",
4465 memory_b64,
4466 twin_id.as_base_64(),
4467 error
4468 ))
4469 })?;
4470 if !self.cortical_areas.contains_key(&memory_id)
4471 || !self.cortical_areas.contains_key(&upstream_id)
4472 {
4473 continue;
4474 }
4475 if seen.insert((memory_id, upstream_id)) {
4476 jobs.push((memory_id, upstream_id, Some(*twin_id)));
4477 }
4478 }
4479
4480 let mut episodic_pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
4481 for (src_id, src_area) in &self.cortical_areas {
4482 if Self::area_is_memory(src_area) {
4483 continue;
4484 }
4485 let Some(dstmap) = src_area
4486 .properties
4487 .get("cortical_mapping_dst")
4488 .and_then(|value| value.as_object())
4489 else {
4490 continue;
4491 };
4492 for (dst_b64, rules) in dstmap {
4493 if !Self::mapping_rules_use_morphology(rules, "episodic_memory") {
4494 continue;
4495 }
4496 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
4497 continue;
4498 };
4499 let Some(memory_area) = self.cortical_areas.get(&memory_id) else {
4500 continue;
4501 };
4502 if !Self::area_is_memory(memory_area) {
4503 continue;
4504 }
4505 episodic_pairs.push((memory_id, *src_id));
4506 }
4507 }
4508
4509 for (memory_id, upstream_id) in episodic_pairs {
4510 if !seen.insert((memory_id, upstream_id)) {
4511 continue;
4512 }
4513 let indexed_twin = self
4514 .cortical_areas
4515 .get(&memory_id)
4516 .and_then(|area| area.properties.get("memory_twin_areas"))
4517 .and_then(|value| value.as_object())
4518 .and_then(|map| map.get(&upstream_id.as_base_64()))
4519 .and_then(|value| value.as_str())
4520 .and_then(|twin_b64| CorticalID::try_from_base_64(twin_b64).ok());
4521 if let Some(twin_id) = indexed_twin {
4522 jobs.push((memory_id, upstream_id, Some(twin_id)));
4523 continue;
4524 }
4525 if let Ok(hash_twin_id) = self.build_memory_twin_id(&memory_id, &upstream_id) {
4526 if self.cortical_areas.contains_key(&hash_twin_id) {
4527 jobs.push((memory_id, upstream_id, Some(hash_twin_id)));
4528 continue;
4529 }
4530 }
4531 jobs.push((memory_id, upstream_id, None));
4532 }
4533
4534 Ok(jobs)
4535 }
4536
4537 fn restore_memory_twin_index(
4538 &mut self,
4539 memory_area_id: &CorticalID,
4540 upstream_area_id: &CorticalID,
4541 twin_id: &CorticalID,
4542 ) -> BduResult<()> {
4543 let expected_source = upstream_area_id.as_base_64();
4544 let expected_target = memory_area_id.as_base_64();
4545 let Some(existing) = self.cortical_areas.get_mut(twin_id) else {
4546 return Err(BduError::InvalidArea(format!(
4547 "Twin area {} not found while restoring memory twin index",
4548 twin_id.as_base_64()
4549 )));
4550 };
4551 let existing_source = existing
4552 .properties
4553 .get("memory_twin_of")
4554 .and_then(|value| value.as_str())
4555 .map(ToString::to_string);
4556 let existing_target = existing
4557 .properties
4558 .get("memory_twin_for")
4559 .and_then(|value| value.as_str())
4560 .map(ToString::to_string);
4561 if existing_source.as_deref() != Some(expected_source.as_str())
4562 || existing_target.as_deref() != Some(expected_target.as_str())
4563 {
4564 existing.properties.insert(
4565 "memory_twin_of".to_string(),
4566 serde_json::json!(expected_source),
4567 );
4568 existing.properties.insert(
4569 "memory_twin_for".to_string(),
4570 serde_json::json!(expected_target),
4571 );
4572 }
4573 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, twin_id);
4574
4575 let has_replay = self
4576 .cortical_areas
4577 .get(memory_area_id)
4578 .and_then(|area| area.properties.get("cortical_mapping_dst"))
4579 .and_then(|value| value.as_object())
4580 .and_then(|map| map.get(&twin_id.as_base_64()))
4581 .is_some_and(|rules| Self::mapping_rules_use_morphology(rules, "memory_replay"));
4582 if !has_replay {
4583 self.ensure_memory_replay_mapping(memory_area_id, twin_id)?;
4584 }
4585 Ok(())
4586 }
4587
4588 fn area_is_memory(area: &CorticalArea) -> bool {
4589 matches!(area.cortical_type, CorticalAreaType::Memory(_))
4590 || area
4591 .properties
4592 .get("is_mem_type")
4593 .and_then(|value| value.as_bool())
4594 == Some(true)
4595 }
4596
4597 fn mapping_rules_use_morphology(rules: &serde_json::Value, morphology_id: &str) -> bool {
4598 rules.as_array().is_some_and(|arr| {
4599 arr.iter().any(|rule| {
4600 rule.as_object()
4601 .and_then(|obj| obj.get("morphology_id"))
4602 .and_then(|value| value.as_str())
4603 == Some(morphology_id)
4604 })
4605 })
4606 }
4607
4608 fn rebind_memory_twin_mappings_to_npu(&mut self) -> BduResult<()> {
4609 use crate::models::CorticalAreaExt;
4610
4611 let Some(npu_arc) = self.npu.clone() else {
4612 return Ok(());
4613 };
4614 let mut bindings: Vec<(u32, u32, u32, f32)> = Vec::new();
4615 for (memory_id, area) in &self.cortical_areas {
4616 if !matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
4617 continue;
4618 }
4619 let Some(twins) = area
4620 .properties
4621 .get("memory_twin_areas")
4622 .and_then(|value| value.as_object())
4623 else {
4624 continue;
4625 };
4626 let Some(&memory_idx) = self.cortical_id_to_idx.get(memory_id) else {
4627 continue;
4628 };
4629 for (upstream_b64, twin_val) in twins {
4630 let Some(twin_b64) = twin_val.as_str() else {
4631 return Err(BduError::InvalidArea(format!(
4632 "memory_twin_areas entry for {} is not a string",
4633 upstream_b64
4634 )));
4635 };
4636 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
4637 BduError::InvalidArea(format!(
4638 "Invalid upstream cortical ID {}: {}",
4639 upstream_b64, error
4640 ))
4641 })?;
4642 let twin_id = CorticalID::try_from_base_64(twin_b64).map_err(|error| {
4643 BduError::InvalidArea(format!(
4644 "Invalid twin cortical ID {}: {}",
4645 twin_b64, error
4646 ))
4647 })?;
4648 let Some(&upstream_idx) = self.cortical_id_to_idx.get(&upstream_id) else {
4649 continue;
4650 };
4651 let Some(&twin_idx) = self.cortical_id_to_idx.get(&twin_id) else {
4652 continue;
4653 };
4654 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
4655 continue;
4656 };
4657 let potential =
4658 twin_area.firing_threshold() + twin_area.firing_threshold_increment();
4659 bindings.push((memory_idx, upstream_idx, twin_idx, potential));
4660 }
4661 }
4662 let mut npu = npu_arc.lock().unwrap();
4663 for (memory_idx, upstream_idx, twin_idx, potential) in bindings {
4664 npu.register_memory_twin_mapping(memory_idx, upstream_idx, twin_idx, potential);
4665 }
4666 Ok(())
4667 }
4668
4669 fn rebind_stdp_mappings_to_npu(&mut self) -> BduResult<()> {
4670 let Some(npu_arc) = self.npu.clone() else {
4671 return Ok(());
4672 };
4673 let mut jobs: Vec<(CorticalID, CorticalID, u32, u32, Vec<serde_json::Value>)> = Vec::new();
4674 for (src_id, src_area) in &self.cortical_areas {
4675 let Some(dst_map) = src_area
4676 .properties
4677 .get("cortical_mapping_dst")
4678 .and_then(|value| value.as_object())
4679 else {
4680 continue;
4681 };
4682 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
4683 continue;
4684 };
4685 for (dst_b64, rules) in dst_map {
4686 let dst_id = CorticalID::try_from_base_64(dst_b64).map_err(|error| {
4687 BduError::InvalidArea(format!(
4688 "Invalid destination cortical ID {}: {}",
4689 dst_b64, error
4690 ))
4691 })?;
4692 let Some(&dst_idx) = self.cortical_id_to_idx.get(&dst_id) else {
4693 continue;
4694 };
4695 let Some(rules_arr) = rules.as_array() else {
4696 continue;
4697 };
4698 jobs.push((*src_id, dst_id, src_idx, dst_idx, rules_arr.clone()));
4699 }
4700 }
4701
4702 for (src_id, dst_id, src_idx, dst_idx, rules) in jobs {
4703 for rule in &rules {
4704 let morphology_id = rule
4705 .as_object()
4706 .and_then(|obj| obj.get("morphology_id"))
4707 .and_then(|value| value.as_str())
4708 .unwrap_or("");
4709 let mut plasticity_flag = rule
4710 .as_object()
4711 .and_then(|obj| obj.get("plasticity_flag"))
4712 .and_then(|value| value.as_bool())
4713 .unwrap_or(false);
4714 if morphology_id == "associative_memory" {
4715 plasticity_flag = true;
4716 }
4717 if !plasticity_flag {
4718 continue;
4719 }
4720 let Some(rule_obj) = rule.as_object() else {
4721 return Err(BduError::InvalidMorphology(
4722 "Plasticity mapping rule must be an object format".to_string(),
4723 ));
4724 };
4725 let (_weight, psp, synapse_type, _delay) =
4726 self.resolve_synapse_params_for_rule(&src_id, rule)?;
4727 Self::register_stdp_mapping_for_rule(
4728 &npu_arc,
4729 &src_id,
4730 &dst_id,
4731 src_idx,
4732 dst_idx,
4733 rule_obj,
4734 false,
4735 psp,
4736 synapse_type,
4737 )?;
4738 }
4739 }
4740 Ok(())
4741 }
4742
4743 #[cfg(feature = "plasticity")]
4744 fn reregister_memory_areas_with_plasticity(&mut self) -> BduResult<()> {
4745 use feagi_evolutionary::extract_memory_properties;
4746 use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
4747
4748 let Some(executor) = self.plasticity_executor.clone() else {
4749 return Ok(());
4750 };
4751 let memory_ids: Vec<CorticalID> = self
4752 .cortical_areas
4753 .iter()
4754 .filter(|(_, area)| matches!(area.cortical_type, CorticalAreaType::Memory(_)))
4755 .map(|(id, _)| *id)
4756 .collect();
4757
4758 for memory_id in memory_ids {
4759 let Some(area) = self.cortical_areas.get(&memory_id) else {
4760 continue;
4761 };
4762 let Some(mem_props) = extract_memory_properties(&area.properties) else {
4763 continue;
4764 };
4765 let Some(&area_idx) = self.cortical_id_to_idx.get(&memory_id) else {
4766 continue;
4767 };
4768 let area_name = memory_id.as_base_64();
4769 let upstream_areas = self.get_episodic_memory_upstream_cortical_areas(&memory_id);
4770 let lifecycle_config = MemoryNeuronLifecycleConfig {
4771 initial_lifespan: mem_props.init_lifespan,
4772 lifespan_growth_rate: mem_props.lifespan_growth_rate,
4773 longterm_threshold: mem_props.longterm_threshold,
4774 max_reactivations: 1000,
4775 };
4776 let exec = executor.lock().map_err(|_| {
4777 BduError::Internal(
4778 "Failed to lock PlasticityExecutor for memory-area rebind".to_string(),
4779 )
4780 })?;
4781 exec.register_memory_area(
4782 area_idx,
4783 area_name,
4784 mem_props.temporal_depth,
4785 upstream_areas,
4786 Some(lifecycle_config),
4787 mem_props.mp_learning_enabled,
4788 );
4789 }
4790 Ok(())
4791 }
4792
4793 #[cfg(feature = "plasticity")]
4796 pub fn set_plasticity_executor(
4797 &mut self,
4798 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
4799 ) {
4800 if let Ok(mut exec) = executor.lock() {
4801 use feagi_npu_plasticity::executor::PlasticityExecutor;
4802 if !exec.is_running() {
4803 exec.start();
4804 }
4805 } else {
4806 warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
4807 }
4808 self.plasticity_executor = Some(executor);
4809 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
4810 }
4811
4812 #[cfg(feature = "plasticity")]
4814 pub fn get_plasticity_executor(
4815 &self,
4816 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
4817 self.plasticity_executor.as_ref()
4818 }
4819
4820 pub fn get_neuron_capacity(&self) -> usize {
4832 self.config.max_neurons
4834 }
4835
4836 pub fn get_synapse_capacity(&self) -> usize {
4848 self.config.max_synapses
4850 }
4851
4852 pub fn update_fatigue_index(&self) -> Option<u8> {
4867 let mut last_calc = match self.last_fatigue_calculation.lock() {
4869 Ok(guard) => guard,
4870 Err(_) => return None, };
4872
4873 let now = std::time::Instant::now();
4874 if now.duration_since(*last_calc).as_secs() < 2 {
4875 return None; }
4877 *last_calc = now;
4878 drop(last_calc);
4879
4880 let regular_neuron_count = self.get_neuron_count();
4882 let regular_neuron_capacity = self.get_neuron_capacity();
4883 let regular_neuron_util = if regular_neuron_capacity > 0 {
4884 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
4885 } else {
4886 0
4887 };
4888
4889 let memory_neuron_util = match StateManager::instance().try_read() {
4893 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
4894 None => {
4895 return None;
4897 }
4898 };
4899
4900 let synapse_count = self.get_synapse_count();
4902 let synapse_capacity = self.get_synapse_capacity();
4903 let synapse_util = if synapse_capacity > 0 {
4904 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
4905 } else {
4906 0
4907 };
4908
4909 let fatigue_index = regular_neuron_util
4911 .max(memory_neuron_util)
4912 .max(synapse_util);
4913
4914 let current_fatigue_active = {
4916 StateManager::instance()
4918 .try_read()
4919 .map(|m| m.get_core_state().is_fatigue_active())
4920 .unwrap_or(false)
4921 };
4922
4923 let new_fatigue_active = if fatigue_index >= 85 {
4924 true
4925 } else if fatigue_index < 80 {
4926 false
4927 } else {
4928 current_fatigue_active };
4930
4931 if let Some(state_manager) = StateManager::instance().try_write() {
4935 let core_state = state_manager.get_core_state();
4936 core_state.set_fatigue_index(fatigue_index);
4937 core_state.set_fatigue_active(new_fatigue_active);
4938 core_state.set_regular_neuron_util(regular_neuron_util);
4939 core_state.set_memory_neuron_util(memory_neuron_util);
4940 core_state.set_synapse_util(synapse_util);
4941 } else {
4942 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
4944 }
4945
4946 if let Some(ref npu) = self.npu {
4948 if let Ok(mut npu_lock) = npu.lock() {
4949 npu_lock.set_fatigue_active(new_fatigue_active);
4950 }
4951 }
4952
4953 trace!(
4954 target: "feagi-bdu",
4955 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
4956 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
4957 );
4958
4959 Some(fatigue_index)
4960 }
4961
4962 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
4979 let area = self
4981 .cortical_areas
4982 .get(cortical_id)
4983 .ok_or_else(|| {
4984 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
4985 })?
4986 .clone();
4987
4988 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
4990 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
4991 })?;
4992
4993 let npu = self
4995 .npu
4996 .as_ref()
4997 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4998
4999 use crate::models::CorticalAreaExt;
5002 let per_voxel_cnt = area.neurons_per_voxel();
5003 let firing_threshold = area.firing_threshold();
5004 let firing_threshold_increment_x = area.firing_threshold_increment_x();
5005 let firing_threshold_increment_y = area.firing_threshold_increment_y();
5006 let firing_threshold_increment_z = area.firing_threshold_increment_z();
5007 let firing_threshold_limit_raw = area.firing_threshold_limit();
5009 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
5010 f32::MAX } else {
5012 firing_threshold_limit_raw
5013 };
5014
5015 if firing_threshold_increment_x != 0.0
5017 || firing_threshold_increment_y != 0.0
5018 || firing_threshold_increment_z != 0.0
5019 {
5020 info!(
5021 target: "feagi-bdu",
5022 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
5023 cortical_id.as_base_64(),
5024 firing_threshold_increment_x,
5025 firing_threshold_increment_y,
5026 firing_threshold_increment_z
5027 );
5028 } else {
5029 if area.properties.contains_key("firing_threshold_increment_x")
5031 || area.properties.contains_key("firing_threshold_increment_y")
5032 || area.properties.contains_key("firing_threshold_increment_z")
5033 {
5034 info!(
5035 target: "feagi-bdu",
5036 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
5037 cortical_id.as_base_64(),
5038 area.properties.get("firing_threshold_increment_x"),
5039 area.properties.get("firing_threshold_increment_y"),
5040 area.properties.get("firing_threshold_increment_z")
5041 );
5042 }
5043 }
5044
5045 let leak_coefficient = area.leak_coefficient();
5046 let excitability = area.neuron_excitability();
5047 let refractory_period = area.refractory_period();
5048 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
5050 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
5051 u16::MAX } else {
5053 consecutive_fire_limit_raw
5054 };
5055 let snooze_length = area.snooze_period();
5056 let mp_charge_accumulation = area.mp_charge_accumulation();
5057
5058 let voxels = area.dimensions.width as usize
5060 * area.dimensions.height as usize
5061 * area.dimensions.depth as usize;
5062 let expected_neurons = voxels * per_voxel_cnt as usize;
5063
5064 trace!(
5065 target: "feagi-bdu",
5066 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
5067 cortical_id.as_base_64(),
5068 area.dimensions.width,
5069 area.dimensions.height,
5070 area.dimensions.depth,
5071 per_voxel_cnt,
5072 expected_neurons
5073 );
5074
5075 let neuron_count: u32 = {
5080 let mut npu_lock = npu
5081 .lock()
5082 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5083 npu_lock
5084 .create_cortical_area_neurons(
5085 *cortical_idx,
5086 area.dimensions.width,
5087 area.dimensions.height,
5088 area.dimensions.depth,
5089 per_voxel_cnt,
5090 firing_threshold,
5091 firing_threshold_increment_x,
5092 firing_threshold_increment_y,
5093 firing_threshold_increment_z,
5094 firing_threshold_limit,
5095 leak_coefficient,
5096 0.0, 0, refractory_period,
5099 excitability,
5100 consecutive_fire_limit,
5101 snooze_length,
5102 mp_charge_accumulation,
5103 )
5104 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
5105 };
5106
5107 trace!(
5108 target: "feagi-bdu",
5109 "Created {} neurons for area {} via NPU",
5110 neuron_count,
5111 cortical_id.as_base_64()
5112 );
5113
5114 {
5117 let mut cache = self.cached_neuron_counts_per_area.write();
5118 cache
5119 .entry(*cortical_id)
5120 .or_insert_with(|| AtomicUsize::new(0))
5121 .store(neuron_count as usize, Ordering::Relaxed);
5122 }
5123
5124 let state_manager = StateManager::instance();
5126 let state_manager = state_manager.read();
5127 state_manager
5128 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
5129
5130 self.cached_neuron_count
5132 .fetch_add(neuron_count as usize, Ordering::Relaxed);
5133
5134 let state_manager = StateManager::instance();
5136 let state_manager = state_manager.read();
5137 let core_state = state_manager.get_core_state();
5138 core_state.add_neuron_count(neuron_count);
5139 core_state.add_regular_neuron_count(neuron_count);
5140
5141 if let Some(npu) = &self.npu {
5143 if let Ok(mut npl) = npu.lock() {
5144 if let Some(v) = area.properties.get("rate_modulated_leak") {
5145 use crate::models::CorticalAreaExt;
5146 let idxs: Vec<usize> = npl
5147 .get_neurons_in_cortical_area(*cortical_idx)
5148 .into_iter()
5149 .map(|id| id as usize)
5150 .collect();
5151 npl.sync_rate_modulated_leak_from_cortical_property(
5152 *cortical_idx,
5153 v,
5154 area.leak_coefficient(),
5155 idxs,
5156 );
5157 } else {
5158 npl.remove_rate_modulated_leak(*cortical_idx);
5159 }
5160 }
5161 }
5162
5163 Ok(neuron_count)
5171 }
5172
5173 #[allow(clippy::too_many_arguments)]
5197 pub fn add_neuron(
5198 &mut self,
5199 cortical_id: &CorticalID,
5200 x: u32,
5201 y: u32,
5202 z: u32,
5203 firing_threshold: f32,
5204 firing_threshold_limit: f32,
5205 leak_coefficient: f32,
5206 resting_potential: f32,
5207 neuron_type: u8,
5208 refractory_period: u16,
5209 excitability: f32,
5210 consecutive_fire_limit: u16,
5211 snooze_length: u16,
5212 mp_charge_accumulation: bool,
5213 ) -> BduResult<u64> {
5214 self.add_neuron_with_area_stats(
5215 cortical_id,
5216 x,
5217 y,
5218 z,
5219 firing_threshold,
5220 firing_threshold_limit,
5221 leak_coefficient,
5222 resting_potential,
5223 neuron_type,
5224 refractory_period,
5225 excitability,
5226 consecutive_fire_limit,
5227 snooze_length,
5228 mp_charge_accumulation,
5229 true,
5230 )
5231 }
5232
5233 #[allow(clippy::too_many_arguments)]
5239 pub fn add_auxiliary_neuron(
5240 &mut self,
5241 cortical_id: &CorticalID,
5242 x: u32,
5243 y: u32,
5244 z: u32,
5245 firing_threshold: f32,
5246 firing_threshold_limit: f32,
5247 leak_coefficient: f32,
5248 resting_potential: f32,
5249 neuron_type: u8,
5250 refractory_period: u16,
5251 excitability: f32,
5252 consecutive_fire_limit: u16,
5253 snooze_length: u16,
5254 mp_charge_accumulation: bool,
5255 ) -> BduResult<u64> {
5256 self.add_neuron_with_area_stats(
5257 cortical_id,
5258 x,
5259 y,
5260 z,
5261 firing_threshold,
5262 firing_threshold_limit,
5263 leak_coefficient,
5264 resting_potential,
5265 neuron_type,
5266 refractory_period,
5267 excitability,
5268 consecutive_fire_limit,
5269 snooze_length,
5270 mp_charge_accumulation,
5271 false,
5272 )
5273 }
5274
5275 #[allow(clippy::too_many_arguments)]
5276 fn add_neuron_with_area_stats(
5277 &mut self,
5278 cortical_id: &CorticalID,
5279 x: u32,
5280 y: u32,
5281 z: u32,
5282 firing_threshold: f32,
5283 firing_threshold_limit: f32,
5284 leak_coefficient: f32,
5285 resting_potential: f32,
5286 neuron_type: u8,
5287 refractory_period: u16,
5288 excitability: f32,
5289 consecutive_fire_limit: u16,
5290 snooze_length: u16,
5291 mp_charge_accumulation: bool,
5292 update_area_stats: bool,
5293 ) -> BduResult<u64> {
5294 if !self.cortical_areas.contains_key(cortical_id) {
5296 return Err(BduError::InvalidArea(format!(
5297 "Cortical area {} not found",
5298 cortical_id
5299 )));
5300 }
5301
5302 let cortical_idx = *self
5303 .cortical_id_to_idx
5304 .get(cortical_id)
5305 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
5306
5307 let npu = self
5309 .npu
5310 .as_ref()
5311 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5312
5313 let mut npu_lock = npu
5314 .lock()
5315 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5316
5317 let neuron_id = npu_lock
5319 .add_neuron(
5320 firing_threshold,
5321 firing_threshold_limit,
5322 leak_coefficient,
5323 resting_potential,
5324 neuron_type as i32,
5325 refractory_period,
5326 excitability,
5327 consecutive_fire_limit,
5328 snooze_length,
5329 mp_charge_accumulation,
5330 cortical_idx,
5331 x,
5332 y,
5333 z,
5334 )
5335 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
5336
5337 trace!(
5338 target: "feagi-bdu",
5339 "Created neuron {} in area {} at ({}, {}, {})",
5340 neuron_id.0,
5341 cortical_id,
5342 x,
5343 y,
5344 z
5345 );
5346
5347 let state_manager = StateManager::instance();
5349 let state_manager = state_manager.read();
5350 let core_state = state_manager.get_core_state();
5351 core_state.add_neuron_count(1);
5352 core_state.add_regular_neuron_count(1);
5353 if update_area_stats {
5354 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
5355 }
5356
5357 Ok(neuron_id.0 as u64)
5358 }
5359
5360 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
5371 let npu = self
5373 .npu
5374 .as_ref()
5375 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5376
5377 let mut npu_lock = npu
5378 .lock()
5379 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5380
5381 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5382 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5383
5384 let deleted = npu_lock.delete_neuron(neuron_id as u32);
5385
5386 if deleted {
5387 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
5388
5389 let state_manager = StateManager::instance();
5391 let state_manager = state_manager.read();
5392 let core_state = state_manager.get_core_state();
5393 core_state.subtract_neuron_count(1);
5394 core_state.subtract_regular_neuron_count(1);
5395 if let Some(cortical_id) = cortical_id {
5396 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
5397 }
5398
5399 }
5403
5404 Ok(deleted)
5405 }
5406
5407 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
5421 let src_area = self
5423 .cortical_areas
5424 .get(src_cortical_id)
5425 .ok_or_else(|| {
5426 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
5427 })?
5428 .clone();
5429
5430 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
5432 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
5433 _ => return Ok(0), };
5435
5436 let src_cortical_idx = *self
5437 .cortical_id_to_idx
5438 .get(src_cortical_id)
5439 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
5440
5441 let mut total_synapses = 0u32;
5442 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
5446 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
5448 Ok(id) => id,
5449 Err(_) => {
5450 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
5451 continue;
5452 }
5453 };
5454
5455 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
5457 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
5458 continue;
5459 }
5460
5461 let synapse_count =
5463 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
5464 total_synapses += synapse_count as u32;
5465
5466 upstream_updates.push((dst_cortical_id, src_cortical_idx));
5469 }
5470
5471 for (dst_id, src_idx) in upstream_updates {
5473 self.add_upstream_area(&dst_id, src_idx);
5474 }
5475
5476 trace!(
5477 target: "feagi-bdu",
5478 "Created {} synapses for area {} via NPU",
5479 total_synapses,
5480 src_cortical_id
5481 );
5482
5483 if total_synapses > 0 {
5486 let mut cache = self.cached_synapse_counts_per_area.write();
5487 cache
5488 .entry(*src_cortical_id)
5489 .or_insert_with(|| AtomicUsize::new(0))
5490 .fetch_add(total_synapses as usize, Ordering::Relaxed);
5491 }
5492
5493 self.cached_synapse_count
5495 .fetch_add(total_synapses as usize, Ordering::Relaxed);
5496
5497 if total_synapses > 0 {
5499 let state_manager = StateManager::instance();
5500 let state_manager = state_manager.read();
5501 let core_state = state_manager.get_core_state();
5502 core_state.add_synapse_count(total_synapses);
5503 }
5504
5505 Ok(total_synapses)
5506 }
5507
5508 pub fn has_neuron(&self, neuron_id: u64) -> bool {
5527 if let Some(ref npu) = self.npu {
5528 if let Ok(npu_lock) = npu.lock() {
5529 npu_lock.is_neuron_valid(neuron_id as u32)
5531 } else {
5532 false
5533 }
5534 } else {
5535 false
5536 }
5537 }
5538
5539 pub fn get_neuron_count(&self) -> usize {
5551 if let Some(ref npu) = self.npu {
5553 if let Ok(npu_lock) = npu.try_lock() {
5554 let fresh_count = npu_lock.get_neuron_count();
5555 self.cached_neuron_count
5556 .store(fresh_count, Ordering::Relaxed);
5557 }
5558 }
5560
5561 self.cached_neuron_count.load(Ordering::Relaxed)
5563 }
5564
5565 pub fn update_cached_neuron_count(&self) {
5571 if let Some(ref npu) = self.npu {
5572 if let Ok(npu_lock) = npu.try_lock() {
5573 let count = npu_lock.get_neuron_count();
5574 self.cached_neuron_count.store(count, Ordering::Relaxed);
5575 }
5576 }
5577 }
5578
5579 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
5583 let npu = self.npu.as_ref()?;
5584 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
5585 let npu_lock = npu.lock().ok()?;
5586 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
5587 drop(npu_lock);
5588
5589 let mut cache = self.cached_neuron_counts_per_area.write();
5590 cache
5591 .entry(*cortical_id)
5592 .or_insert_with(|| AtomicUsize::new(0))
5593 .store(count, Ordering::Relaxed);
5594
5595 let state_manager = StateManager::instance();
5597 let state_manager = state_manager.read();
5598 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5599
5600 self.update_cached_neuron_count();
5601
5602 Some(count)
5603 }
5604
5605 pub fn get_synapse_count(&self) -> usize {
5617 if let Some(ref npu) = self.npu {
5619 if let Ok(npu_lock) = npu.try_lock() {
5620 let fresh_count = npu_lock.get_synapse_count();
5621 self.cached_synapse_count
5622 .store(fresh_count, Ordering::Relaxed);
5623 }
5624 }
5626
5627 self.cached_synapse_count.load(Ordering::Relaxed)
5629 }
5630
5631 pub fn update_cached_synapse_count(&self) {
5637 if let Some(ref npu) = self.npu {
5638 if let Ok(npu_lock) = npu.try_lock() {
5639 let count = npu_lock.get_synapse_count();
5640 self.cached_synapse_count.store(count, Ordering::Relaxed);
5641 }
5642 }
5643 }
5644
5645 pub fn update_all_cached_stats(&self) {
5651 self.update_cached_neuron_count();
5652 self.update_cached_synapse_count();
5653 }
5654
5655 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
5666 #[cfg(feature = "plasticity")]
5671 {
5672 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
5673 return (0, 0, 0);
5674 }
5675 }
5676 if let Some(ref npu) = self.npu {
5677 if let Ok(npu_lock) = npu.lock() {
5678 npu_lock
5679 .get_neuron_coordinates(neuron_id as u32)
5680 .unwrap_or((0, 0, 0))
5681 } else {
5682 (0, 0, 0)
5683 }
5684 } else {
5685 (0, 0, 0)
5686 }
5687 }
5688
5689 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
5700 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
5701 }
5702
5703 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
5709 #[cfg(feature = "plasticity")]
5710 {
5711 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
5712 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
5713 }
5714 }
5715 if let Some(ref npu) = self.npu {
5716 if let Ok(npu_lock) = npu.lock() {
5717 npu_lock.get_neuron_cortical_area(neuron_id as u32)
5718 } else {
5719 None
5720 }
5721 } else {
5722 None
5723 }
5724 }
5725
5726 #[cfg(feature = "plasticity")]
5728 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
5729 let exec = self.get_plasticity_executor()?;
5730 let guard = exec.lock().ok()?;
5731 guard
5732 .memory_neuron_detail(neuron_id)
5733 .map(|d| d.cortical_area_idx)
5734 }
5735
5736 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
5747 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
5749 Some(idx) => *idx,
5750 None => return Vec::new(),
5751 };
5752
5753 if let Some(ref npu) = self.npu {
5754 if let Ok(npu_lock) = npu.lock() {
5755 npu_lock
5757 .get_neurons_in_cortical_area(cortical_idx)
5758 .into_iter()
5759 .map(|id| id as u64)
5760 .collect()
5761 } else {
5762 Vec::new()
5763 }
5764 } else {
5765 Vec::new()
5766 }
5767 }
5768
5769 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
5780 if let Some(ref npu) = self.npu {
5781 if let Ok(npu_lock) = npu.lock() {
5782 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
5783 } else {
5784 Vec::new()
5785 }
5786 } else {
5787 Vec::new()
5788 }
5789 }
5790
5791 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
5802 if let Some(ref npu) = self.npu {
5803 if let Ok(npu_lock) = npu.lock() {
5804 npu_lock.get_incoming_synapses(target_neuron_id as u32)
5805 } else {
5806 Vec::new()
5807 }
5808 } else {
5809 Vec::new()
5810 }
5811 }
5812
5813 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5839 let is_memory_area = self
5840 .cortical_areas
5841 .get(cortical_id)
5842 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
5843 .is_some();
5844
5845 if is_memory_area {
5846 #[cfg(feature = "plasticity")]
5850 if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
5851 return count;
5852 }
5853
5854 return StateManager::instance()
5856 .try_read()
5857 .and_then(|state_manager| {
5858 state_manager
5859 .get_cortical_area_stats(&cortical_id.as_base_64())
5860 .map(|stats| stats.neuron_count)
5861 })
5862 .unwrap_or(0);
5863 }
5864
5865 let cache = self.cached_neuron_counts_per_area.read();
5867 cache
5868 .get(cortical_id)
5869 .map(|count| count.load(Ordering::Relaxed))
5870 .unwrap_or(0)
5871 }
5872
5873 #[cfg(feature = "plasticity")]
5875 fn active_memory_neuron_count_from_plasticity(
5876 &self,
5877 cortical_id: &CorticalID,
5878 ) -> Option<usize> {
5879 let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
5880 let exec = self.get_plasticity_executor()?;
5881 let guard = exec.lock().ok()?;
5882 let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
5883 Some(runtime.active_memory_neuron_count())
5884 }
5885
5886 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
5893 let mut result = Vec::new();
5894
5895 for cortical_id in self.cortical_areas.keys() {
5896 let count = self.get_neuron_count_in_area(cortical_id);
5897 if count > 0 {
5898 result.push((cortical_id.to_string(), count));
5899 }
5900 }
5901
5902 result
5903 }
5904
5905 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
5916 self.get_neuron_count_in_area(cortical_id) > 0
5917 }
5918
5919 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5935 let cache = self.cached_synapse_counts_per_area.read();
5937 cache
5938 .get(cortical_id)
5939 .map(|count| count.load(Ordering::Relaxed))
5940 .unwrap_or(0)
5941 }
5942
5943 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5953 if !self.cortical_id_to_idx.contains_key(cortical_id) {
5954 return 0;
5955 }
5956
5957 if let Some(state_manager) = StateManager::instance().try_read() {
5958 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
5959 return stats.incoming_synapse_count;
5960 }
5961 }
5962
5963 0
5964 }
5965
5966 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5976 if !self.cortical_id_to_idx.contains_key(cortical_id) {
5977 return 0;
5978 }
5979
5980 if let Some(state_manager) = StateManager::instance().try_read() {
5981 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
5982 return stats.outgoing_synapse_count;
5983 }
5984 }
5985
5986 0
5987 }
5988
5989 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
6001 let synapses = self.get_outgoing_synapses(source_neuron_id);
6002 synapses
6003 .iter()
6004 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
6005 }
6006
6007 pub fn get_connection_weight(
6019 &self,
6020 source_neuron_id: u64,
6021 target_neuron_id: u64,
6022 ) -> Option<f32> {
6023 let synapses = self.get_outgoing_synapses(source_neuron_id);
6024 synapses
6025 .iter()
6026 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
6027 .map(|(_, weight, _, _)| *weight)
6028 }
6029
6030 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
6041 let neurons = self.get_neurons_in_area(cortical_id);
6042 let neuron_count = neurons.len();
6043
6044 if neuron_count == 0 {
6045 return (0, 0, 0.0);
6046 }
6047
6048 let mut total_synapses = 0;
6049 for neuron_id in neurons {
6050 total_synapses += self.get_outgoing_synapses(neuron_id).len();
6051 }
6052
6053 let avg_synapses = total_synapses as f32 / neuron_count as f32;
6054
6055 (neuron_count, total_synapses, avg_synapses)
6056 }
6057
6058 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
6069 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
6070 self.cortical_idx_to_id.get(&cortical_idx).copied()
6071 }
6072
6073 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
6084 let area = match self.cortical_areas.get(cortical_id) {
6085 Some(a) => a,
6086 None => return 0.0,
6087 };
6088
6089 let neuron_count = self.get_neuron_count_in_area(cortical_id);
6090 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
6091
6092 if volume == 0 {
6093 return 0.0;
6094 }
6095
6096 neuron_count as f32 / volume as f32
6097 }
6098
6099 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
6106 let mut stats = Vec::new();
6107
6108 for cortical_id in self.cortical_areas.keys() {
6109 let neuron_count = self.get_neuron_count_in_area(cortical_id);
6110 let synapse_count = self.get_synapse_count_in_area(cortical_id);
6111 let density = self.get_neuron_density(cortical_id);
6112
6113 stats.push((
6114 cortical_id.to_string(),
6115 neuron_count,
6116 synapse_count,
6117 density,
6118 ));
6119 }
6120
6121 stats
6122 }
6123
6124 pub fn get_config(&self) -> &ConnectomeConfig {
6130 &self.config
6131 }
6132
6133 pub fn set_config(&mut self, config: ConnectomeConfig) {
6135 self.config = config;
6136 }
6137
6138 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
6161 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
6162
6163 use feagi_structures::genomic::cortical_area::{
6164 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
6165 };
6166
6167 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
6169 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
6170 })?;
6171
6172 let core_position = (0, 0, 0).into();
6174
6175 let death_id = CoreCorticalType::Death.to_cortical_id();
6177 if !self.cortical_areas.contains_key(&death_id) {
6178 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
6179 let death_area = CorticalArea::new(
6180 death_id,
6181 0, "_death".to_string(),
6183 core_dimensions,
6184 core_position,
6185 CorticalAreaType::Core(CoreCorticalType::Death),
6186 )
6187 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
6188 match self.add_cortical_area(death_area) {
6189 Ok(idx) => {
6190 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
6191 }
6192 Err(e) => {
6193 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
6194 return Err(e);
6195 }
6196 }
6197 } else {
6198 info!(target: "feagi-bdu", " ✓ _death area already exists");
6199 }
6200
6201 let power_id = CoreCorticalType::Power.to_cortical_id();
6203 if !self.cortical_areas.contains_key(&power_id) {
6204 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
6205 let power_area = CorticalArea::new(
6206 power_id,
6207 1, "_power".to_string(),
6209 core_dimensions,
6210 core_position,
6211 CorticalAreaType::Core(CoreCorticalType::Power),
6212 )
6213 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
6214 match self.add_cortical_area(power_area) {
6215 Ok(idx) => {
6216 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
6217 }
6218 Err(e) => {
6219 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
6220 return Err(e);
6221 }
6222 }
6223 } else {
6224 info!(target: "feagi-bdu", " ✓ _power area already exists");
6225 }
6226
6227 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
6229 let pain_id = CoreCorticalType::Pain.to_cortical_id();
6230 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
6231 let fear_id = CoreCorticalType::Fear.to_cortical_id();
6232 let hope_id = CoreCorticalType::Hope.to_cortical_id();
6233 if !self.cortical_areas.contains_key(&fatigue_id) {
6234 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
6235 let fatigue_area = CorticalArea::new(
6236 fatigue_id,
6237 2, "_fatigue".to_string(),
6239 core_dimensions,
6240 core_position,
6241 CorticalAreaType::Core(CoreCorticalType::Fatigue),
6242 )
6243 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
6244 match self.add_cortical_area(fatigue_area) {
6245 Ok(idx) => {
6246 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
6247 }
6248 Err(e) => {
6249 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
6250 return Err(e);
6251 }
6252 }
6253 } else {
6254 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
6255 }
6256
6257 if !self.cortical_areas.contains_key(&pain_id) {
6259 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
6260 let pain_area = CorticalArea::new(
6261 pain_id,
6262 3, "_pain".to_string(),
6264 core_dimensions,
6265 core_position,
6266 CorticalAreaType::Core(CoreCorticalType::Pain),
6267 )
6268 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
6269 match self.add_cortical_area(pain_area) {
6270 Ok(idx) => {
6271 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
6272 }
6273 Err(e) => {
6274 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
6275 return Err(e);
6276 }
6277 }
6278 } else {
6279 info!(target: "feagi-bdu", " ✓ _pain area already exists");
6280 }
6281
6282 if !self.cortical_areas.contains_key(&pleasure_id) {
6284 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
6285 let pleasure_area = CorticalArea::new(
6286 pleasure_id,
6287 4, "_pleasure".to_string(),
6289 core_dimensions,
6290 core_position,
6291 CorticalAreaType::Core(CoreCorticalType::Pleasure),
6292 )
6293 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
6294 match self.add_cortical_area(pleasure_area) {
6295 Ok(idx) => {
6296 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
6297 }
6298 Err(e) => {
6299 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
6300 return Err(e);
6301 }
6302 }
6303 } else {
6304 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
6305 }
6306
6307 if !self.cortical_areas.contains_key(&fear_id) {
6309 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
6310 let fear_area = CorticalArea::new(
6311 fear_id,
6312 5, "_fear".to_string(),
6314 core_dimensions,
6315 core_position,
6316 CorticalAreaType::Core(CoreCorticalType::Fear),
6317 )
6318 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
6319 match self.add_cortical_area(fear_area) {
6320 Ok(idx) => {
6321 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
6322 }
6323 Err(e) => {
6324 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
6325 return Err(e);
6326 }
6327 }
6328 } else {
6329 info!(target: "feagi-bdu", " ✓ _fear area already exists");
6330 }
6331
6332 if !self.cortical_areas.contains_key(&hope_id) {
6334 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
6335 let hope_area = CorticalArea::new(
6336 hope_id,
6337 6, "_hope".to_string(),
6339 core_dimensions,
6340 core_position,
6341 CorticalAreaType::Core(CoreCorticalType::Hope),
6342 )
6343 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
6344 match self.add_cortical_area(hope_area) {
6345 Ok(idx) => {
6346 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
6347 }
6348 Err(e) => {
6349 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
6350 return Err(e);
6351 }
6352 }
6353 } else {
6354 info!(target: "feagi-bdu", " ✓ _hope area already exists");
6355 }
6356
6357 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
6358 Ok(())
6359 }
6360
6361 #[deprecated(
6378 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
6379 )]
6380 #[allow(deprecated)]
6381 pub fn save_genome_to_json(
6382 &self,
6383 genome_id: Option<String>,
6384 genome_title: Option<String>,
6385 ) -> BduResult<String> {
6386 let mut brain_regions_with_parents = std::collections::HashMap::new();
6388
6389 for region_id in self.brain_regions.get_all_region_ids() {
6390 if let Some(region) = self.brain_regions.get_region(region_id) {
6391 let parent_id = self
6392 .brain_regions
6393 .get_parent(region_id)
6394 .map(|s| s.to_string());
6395 brain_regions_with_parents
6396 .insert(region_id.to_string(), (region.clone(), parent_id));
6397 }
6398 }
6399
6400 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
6402 &self.cortical_areas,
6403 &brain_regions_with_parents,
6404 genome_id,
6405 genome_title,
6406 )?)
6407 }
6408
6409 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
6440 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
6441
6442 self.cortical_areas.clear();
6444 self.cortical_id_to_idx.clear();
6445 self.cortical_idx_to_id.clear();
6446 self.next_cortical_idx = 7;
6448 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)");
6449
6450 self.brain_regions = BrainRegionHierarchy::new();
6452
6453 #[cfg(feature = "plasticity")]
6456 if let Some(executor) = self.plasticity_executor.as_ref() {
6457 executor
6458 .lock()
6459 .map_err(|_| {
6460 BduError::Internal(
6461 "Failed to lock PlasticityExecutor for memory state reset".to_string(),
6462 )
6463 })?
6464 .reset_all_memory_state()
6465 .map_err(BduError::Internal)?;
6466 }
6467
6468 if let Some(ref npu) = self.npu {
6470 let mut npu_lock = npu
6471 .lock()
6472 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6473 npu_lock
6474 .reset_for_new_genome()
6475 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
6476 }
6477
6478 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
6479 Ok(())
6480 }
6481
6482 pub fn resize_for_genome(
6492 &mut self,
6493 genome: &feagi_evolutionary::RuntimeGenome,
6494 ) -> BduResult<()> {
6495 self.morphology_registry = genome.morphologies.clone();
6497 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
6498
6499 let required_neurons = genome.stats.innate_neuron_count;
6501 let required_synapses = genome.stats.innate_synapse_count;
6502
6503 info!(target: "feagi-bdu",
6504 "Genome requires: {} neurons, {} synapses",
6505 required_neurons,
6506 required_synapses
6507 );
6508
6509 let mut total_voxels = 0;
6511 for area in genome.cortical_areas.values() {
6512 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
6513 }
6514
6515 info!(target: "feagi-bdu",
6516 "Genome has {} cortical areas with {} total voxels",
6517 genome.cortical_areas.len(),
6518 total_voxels
6519 );
6520
6521 Ok(())
6526 }
6527
6528 pub fn create_synapse(
6547 &mut self,
6548 source_neuron_id: u64,
6549 target_neuron_id: u64,
6550 weight: f32,
6551 psp: f32,
6552 synapse_type: u8,
6553 ) -> BduResult<()> {
6554 let npu = self
6556 .npu
6557 .as_ref()
6558 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6559
6560 let mut npu_lock = npu
6561 .lock()
6562 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6563
6564 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
6566 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
6567
6568 if !source_exists {
6569 return Err(BduError::InvalidNeuron(format!(
6570 "Source neuron {} not found",
6571 source_neuron_id
6572 )));
6573 }
6574 if !target_exists {
6575 return Err(BduError::InvalidNeuron(format!(
6576 "Target neuron {} not found",
6577 target_neuron_id
6578 )));
6579 }
6580
6581 let syn_type = if synapse_type == 0 {
6583 feagi_npu_neural::synapse::SynapseType::Excitatory
6584 } else {
6585 feagi_npu_neural::synapse::SynapseType::Inhibitory
6586 };
6587
6588 let synapse_idx = npu_lock
6589 .add_synapse(
6590 NeuronId(source_neuron_id as u32),
6591 NeuronId(target_neuron_id as u32),
6592 feagi_npu_neural::types::SynapticWeight(weight),
6593 feagi_npu_neural::types::SynapticPsp(psp),
6594 syn_type,
6595 0,
6596 1,
6597 )
6598 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
6599
6600 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
6601 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
6602
6603 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
6604 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
6605 let source_cortical_id =
6606 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6607 let target_cortical_id =
6608 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6609
6610 let state_manager = StateManager::instance();
6611 let state_manager = state_manager.read();
6612 let core_state = state_manager.get_core_state();
6613 core_state.add_synapse_count(1);
6614 if let Some(cortical_id) = source_cortical_id {
6615 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
6616 }
6617 if let Some(cortical_id) = target_cortical_id {
6618 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
6619 }
6620
6621 Ok(())
6626 }
6627
6628 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
6631 if let Some(ref npu) = self.npu {
6632 if let Ok(mut npu_lock) = npu.lock() {
6633 let mut psp_uniform_flags = ahash::AHashMap::new();
6635 let mut mp_driven_psp_flags = ahash::AHashMap::new();
6636 let mut postsynaptic_current_flags = ahash::AHashMap::new();
6637 let mut degeneration_flags = ahash::AHashMap::new();
6638
6639 for (cortical_id, area) in &self.cortical_areas {
6640 let default_psp_uniform = *cortical_id
6643 == CoreCorticalType::Power.to_cortical_id()
6644 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
6645 let psp_uniform = area
6646 .get_property("psp_uniform_distribution")
6647 .and_then(|v| v.as_bool())
6648 .unwrap_or(default_psp_uniform);
6649 psp_uniform_flags.insert(*cortical_id, psp_uniform);
6650
6651 let mp_driven_psp = area
6653 .get_property("mp_driven_psp")
6654 .and_then(|v| v.as_bool())
6655 .unwrap_or(false);
6656 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
6657
6658 let postsynaptic_current = area
6660 .get_property("postsynaptic_current")
6661 .and_then(|v| v.as_f64())
6662 .unwrap_or(1.0) as f32;
6663 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
6664
6665 let degeneration = area
6667 .get_property("degeneration")
6668 .and_then(|v| v.as_f64())
6669 .unwrap_or(0.0) as f32;
6670 if degeneration > 0.0 {
6671 degeneration_flags.insert(*cortical_id, degeneration);
6672 }
6673 }
6674
6675 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
6677 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
6678 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
6679 npu_lock.set_degeneration_flags(degeneration_flags);
6680
6681 trace!(
6682 target: "feagi-bdu",
6683 "Synchronized cortical area flags to NPU ({} areas)",
6684 self.cortical_areas.len()
6685 );
6686 }
6687 }
6688
6689 Ok(())
6690 }
6691
6692 pub fn get_synapse(
6704 &self,
6705 source_neuron_id: u64,
6706 target_neuron_id: u64,
6707 ) -> Option<(f32, f32, u8)> {
6708 let npu = self.npu.as_ref()?;
6710 let npu_lock = npu.lock().ok()?;
6711
6712 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
6715
6716 for (source_id, weight, psp, synapse_type) in incoming {
6718 if source_id == source_neuron_id as u32 {
6719 return Some((weight, psp, synapse_type));
6720 }
6721 }
6722
6723 None
6724 }
6725
6726 pub fn update_synapse_weight(
6739 &mut self,
6740 source_neuron_id: u64,
6741 target_neuron_id: u64,
6742 new_weight: f32,
6743 ) -> BduResult<()> {
6744 let npu = self
6746 .npu
6747 .as_ref()
6748 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6749
6750 let mut npu_lock = npu
6751 .lock()
6752 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6753
6754 let updated = npu_lock.update_synapse_weight(
6756 NeuronId(source_neuron_id as u32),
6757 NeuronId(target_neuron_id as u32),
6758 feagi_npu_neural::types::SynapticWeight(new_weight),
6759 );
6760
6761 if updated {
6762 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
6763 Ok(())
6764 } else {
6765 Err(BduError::InvalidSynapse(format!(
6766 "Synapse {} -> {} not found",
6767 source_neuron_id, target_neuron_id
6768 )))
6769 }
6770 }
6771
6772 pub fn remove_synapse(
6784 &mut self,
6785 source_neuron_id: u64,
6786 target_neuron_id: u64,
6787 ) -> BduResult<bool> {
6788 let npu = self
6790 .npu
6791 .as_ref()
6792 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6793
6794 let mut npu_lock = npu
6795 .lock()
6796 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6797
6798 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
6799 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
6800 let source_cortical_id =
6801 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6802 let target_cortical_id =
6803 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6804
6805 let removed = npu_lock.remove_synapse(
6807 NeuronId(source_neuron_id as u32),
6808 NeuronId(target_neuron_id as u32),
6809 );
6810
6811 if removed {
6812 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
6813
6814 let state_manager = StateManager::instance();
6816 let state_manager = state_manager.read();
6817 let core_state = state_manager.get_core_state();
6818 core_state.subtract_synapse_count(1);
6819 if let Some(cortical_id) = source_cortical_id {
6820 state_manager
6821 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
6822 }
6823 if let Some(cortical_id) = target_cortical_id {
6824 state_manager
6825 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
6826 }
6827 }
6828
6829 Ok(removed)
6830 }
6831
6832 pub fn batch_create_neurons(
6850 &mut self,
6851 cortical_id: &CorticalID,
6852 neurons: Vec<NeuronData>,
6853 ) -> BduResult<Vec<u64>> {
6854 let npu = self
6856 .npu
6857 .as_ref()
6858 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6859
6860 let mut npu_lock = npu
6861 .lock()
6862 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6863
6864 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
6866 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6867 })?;
6868 let cortical_idx = area.cortical_idx;
6869
6870 let count = neurons.len();
6871
6872 let mut x_coords = Vec::with_capacity(count);
6874 let mut y_coords = Vec::with_capacity(count);
6875 let mut z_coords = Vec::with_capacity(count);
6876 let mut firing_thresholds = Vec::with_capacity(count);
6877 let mut threshold_limits = Vec::with_capacity(count);
6878 let mut leak_coeffs = Vec::with_capacity(count);
6879 let mut resting_potentials = Vec::with_capacity(count);
6880 let mut neuron_types = Vec::with_capacity(count);
6881 let mut refractory_periods = Vec::with_capacity(count);
6882 let mut excitabilities = Vec::with_capacity(count);
6883 let mut consec_fire_limits = Vec::with_capacity(count);
6884 let mut snooze_lengths = Vec::with_capacity(count);
6885 let mut mp_accums = Vec::with_capacity(count);
6886 let mut cortical_areas = Vec::with_capacity(count);
6887
6888 for (
6889 x,
6890 y,
6891 z,
6892 threshold,
6893 threshold_limit,
6894 leak,
6895 resting,
6896 ntype,
6897 refract,
6898 excit,
6899 consec_limit,
6900 snooze,
6901 mp_accum,
6902 ) in neurons
6903 {
6904 x_coords.push(x);
6905 y_coords.push(y);
6906 z_coords.push(z);
6907 firing_thresholds.push(threshold);
6908 threshold_limits.push(threshold_limit);
6909 leak_coeffs.push(leak);
6910 resting_potentials.push(resting);
6911 neuron_types.push(ntype);
6912 refractory_periods.push(refract);
6913 excitabilities.push(excit);
6914 consec_fire_limits.push(consec_limit);
6915 snooze_lengths.push(snooze);
6916 mp_accums.push(mp_accum);
6917 cortical_areas.push(cortical_idx);
6918 }
6919
6920 let first_neuron_id = npu_lock.get_neuron_count() as u32;
6922
6923 let firing_thresholds_t = firing_thresholds;
6928 let threshold_limits_t = threshold_limits;
6929 let resting_potentials_t = resting_potentials;
6930 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
6931 firing_thresholds_t,
6932 threshold_limits_t,
6933 leak_coeffs,
6934 resting_potentials_t,
6935 neuron_types,
6936 refractory_periods,
6937 excitabilities,
6938 consec_fire_limits,
6939 snooze_lengths,
6940 mp_accums,
6941 cortical_areas,
6942 x_coords,
6943 y_coords,
6944 z_coords,
6945 );
6946
6947 let mut neuron_ids = Vec::with_capacity(count);
6949 for i in 0..neurons_created {
6950 neuron_ids.push((first_neuron_id + i) as u64);
6951 }
6952
6953 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
6954
6955 let state_manager = StateManager::instance();
6957 let state_manager = state_manager.read();
6958 let core_state = state_manager.get_core_state();
6959 core_state.add_neuron_count(neurons_created);
6960 core_state.add_regular_neuron_count(neurons_created);
6961 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
6962
6963 {
6965 let mut cache = self.cached_neuron_counts_per_area.write();
6966 cache
6967 .entry(*cortical_id)
6968 .or_insert_with(|| AtomicUsize::new(0))
6969 .fetch_add(count, Ordering::Relaxed);
6970 }
6971
6972 Ok(neuron_ids)
6973 }
6974
6975 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
6986 let npu = self
6988 .npu
6989 .as_ref()
6990 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6991
6992 let mut npu_lock = npu
6993 .lock()
6994 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6995
6996 let mut deleted_count = 0;
6997 let mut per_area_deleted: std::collections::HashMap<String, usize> =
6998 std::collections::HashMap::new();
6999
7000 for neuron_id in neuron_ids {
7003 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
7004 let cortical_id =
7005 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7006
7007 if npu_lock.delete_neuron(neuron_id as u32) {
7008 deleted_count += 1;
7009 if let Some(cortical_id) = cortical_id {
7010 let key = cortical_id.as_base_64();
7011 *per_area_deleted.entry(key).or_insert(0) += 1;
7012 }
7013 }
7014 }
7015
7016 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
7017
7018 if deleted_count > 0 {
7020 let state_manager = StateManager::instance();
7021 let state_manager = state_manager.read();
7022 let core_state = state_manager.get_core_state();
7023 core_state.subtract_neuron_count(deleted_count as u32);
7024 core_state.subtract_regular_neuron_count(deleted_count as u32);
7025 for (cortical_id, count) in per_area_deleted {
7026 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
7027 }
7028 }
7029
7030 Ok(deleted_count)
7037 }
7038
7039 pub fn update_neuron_properties(
7058 &mut self,
7059 neuron_id: u64,
7060 firing_threshold: Option<f32>,
7061 leak_coefficient: Option<f32>,
7062 resting_potential: Option<f32>,
7063 excitability: Option<f32>,
7064 ) -> BduResult<()> {
7065 let npu = self
7067 .npu
7068 .as_ref()
7069 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7070
7071 let mut npu_lock = npu
7072 .lock()
7073 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7074
7075 let neuron_id_u32 = neuron_id as u32;
7076
7077 let mut updated = false;
7079
7080 if let Some(threshold) = firing_threshold {
7082 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
7083 updated = true;
7084 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
7085 } else if !updated {
7086 return Err(BduError::InvalidNeuron(format!(
7087 "Neuron {} not found",
7088 neuron_id
7089 )));
7090 }
7091 }
7092
7093 if let Some(leak) = leak_coefficient {
7094 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
7095 updated = true;
7096 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
7097 } else if !updated {
7098 return Err(BduError::InvalidNeuron(format!(
7099 "Neuron {} not found",
7100 neuron_id
7101 )));
7102 }
7103 }
7104
7105 if let Some(resting) = resting_potential {
7106 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
7107 updated = true;
7108 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
7109 } else if !updated {
7110 return Err(BduError::InvalidNeuron(format!(
7111 "Neuron {} not found",
7112 neuron_id
7113 )));
7114 }
7115 }
7116
7117 if let Some(excit) = excitability {
7118 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
7119 updated = true;
7120 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
7121 } else if !updated {
7122 return Err(BduError::InvalidNeuron(format!(
7123 "Neuron {} not found",
7124 neuron_id
7125 )));
7126 }
7127 }
7128
7129 if !updated {
7130 return Err(BduError::Internal(
7131 "No properties provided for update".to_string(),
7132 ));
7133 }
7134
7135 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
7136
7137 Ok(())
7138 }
7139
7140 pub fn set_neuron_firing_threshold(
7152 &mut self,
7153 neuron_id: u64,
7154 new_threshold: f32,
7155 ) -> BduResult<()> {
7156 let npu = self
7158 .npu
7159 .as_ref()
7160 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7161
7162 let mut npu_lock = npu
7163 .lock()
7164 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7165
7166 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
7168 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
7169 Ok(())
7170 } else {
7171 Err(BduError::InvalidNeuron(format!(
7172 "Neuron {} not found",
7173 neuron_id
7174 )))
7175 }
7176 }
7177
7178 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
7193 self.cortical_areas
7194 .values()
7195 .find(|area| area.name == name)
7196 .cloned()
7197 }
7198
7199 pub fn resize_cortical_area(
7216 &mut self,
7217 cortical_id: &CorticalID,
7218 new_dimensions: CorticalAreaDimensions,
7219 ) -> BduResult<()> {
7220 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
7222 return Err(BduError::InvalidArea(format!(
7223 "Invalid dimensions: {:?} (all must be > 0)",
7224 new_dimensions
7225 )));
7226 }
7227
7228 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
7230 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
7231 })?;
7232
7233 let old_dimensions = area.dimensions;
7234 area.dimensions = new_dimensions;
7235
7236 info!(target: "feagi-bdu",
7240 "Resized cortical area {} from {:?} to {:?}",
7241 cortical_id,
7242 old_dimensions,
7243 new_dimensions
7244 );
7245
7246 self.refresh_cortical_area_hashes(false, true);
7247
7248 Ok(())
7249 }
7250
7251 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
7262 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
7263 BduError::InvalidArea(format!("Brain region {} not found", region_id))
7264 })?;
7265
7266 Ok(region
7268 .cortical_areas
7269 .iter()
7270 .map(|id| id.as_base_64())
7271 .collect())
7272 }
7273
7274 pub fn update_brain_region(
7287 &mut self,
7288 region_id: &str,
7289 new_name: Option<String>,
7290 new_description: Option<String>,
7291 ) -> BduResult<()> {
7292 let region = self
7293 .brain_regions
7294 .get_region_mut(region_id)
7295 .ok_or_else(|| {
7296 BduError::InvalidArea(format!("Brain region {} not found", region_id))
7297 })?;
7298
7299 if let Some(name) = new_name {
7300 region.name = name;
7301 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
7302 }
7303
7304 if let Some(desc) = new_description {
7305 region
7307 .properties
7308 .insert("description".to_string(), serde_json::json!(desc));
7309 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
7310 }
7311
7312 info!(target: "feagi-bdu","Updated brain region {}", region_id);
7313
7314 self.refresh_brain_regions_hash();
7315
7316 Ok(())
7317 }
7318
7319 pub fn update_brain_region_properties(
7333 &mut self,
7334 region_id: &str,
7335 properties: std::collections::HashMap<String, serde_json::Value>,
7336 ) -> BduResult<Option<BrainRegionIoRegistry>> {
7337 use tracing::{debug, info};
7338
7339 let should_recompute_io = properties
7340 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
7341 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
7342
7343 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
7344 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
7345 {
7346 let region_snapshot = self
7347 .brain_regions
7348 .get_region(region_id)
7349 .ok_or_else(|| {
7350 BduError::InvalidArea(format!("Brain region {} not found", region_id))
7351 })?
7352 .clone();
7353 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
7354 ®ion_snapshot,
7355 &properties,
7356 )?;
7357 crate::region_io_designation::validate_merged_designations_against_connectivity(
7358 self,
7359 ®ion_snapshot,
7360 &merged_in,
7361 &merged_out,
7362 )?;
7363 }
7364
7365 let region = self
7366 .brain_regions
7367 .get_region_mut(region_id)
7368 .ok_or_else(|| {
7369 BduError::InvalidArea(format!("Brain region {} not found", region_id))
7370 })?;
7371
7372 for (key, value) in properties {
7373 match key.as_str() {
7374 "title" | "name" | "region_title" => {
7376 if let Some(name) = value.as_str() {
7377 region.name = name.to_string();
7378 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
7379 }
7380 }
7381 "coordinate_3d" | "coordinates_3d" => {
7382 region
7383 .properties
7384 .insert("coordinate_3d".to_string(), value.clone());
7385 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
7386 }
7387 "coordinate_2d" | "coordinates_2d" => {
7388 region
7389 .properties
7390 .insert("coordinate_2d".to_string(), value.clone());
7391 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
7392 }
7393 "description" => {
7394 region
7395 .properties
7396 .insert("description".to_string(), value.clone());
7397 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
7398 }
7399 "region_type" => {
7400 if let Some(type_str) = value.as_str() {
7401 region.region_type = feagi_structures::genomic::RegionType::Undefined;
7404 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
7405 }
7406 }
7407 _ => {
7409 region.properties.insert(key.clone(), value.clone());
7410 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
7411 }
7412 }
7413 }
7414
7415 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
7416
7417 if should_recompute_io {
7420 let registry = self.recompute_brain_region_io_registry()?;
7421 return Ok(Some(registry));
7422 }
7423
7424 self.refresh_brain_regions_hash();
7428
7429 Ok(None)
7430 }
7431
7432 pub fn get_neuron_by_coordinates(
7450 &self,
7451 cortical_id: &CorticalID,
7452 x: u32,
7453 y: u32,
7454 z: u32,
7455 ) -> Option<u64> {
7456 let area = self.get_cortical_area(cortical_id)?;
7458 let cortical_idx = area.cortical_idx;
7459
7460 let npu = self.npu.as_ref()?;
7462 let npu_lock = npu.lock().ok()?;
7463
7464 npu_lock
7465 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
7466 .map(|id| id as u64)
7467 }
7468
7469 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
7480 let npu = self.npu.as_ref()?;
7481 let npu_lock = npu.lock().ok()?;
7482
7483 let neuron_count = npu_lock.get_neuron_count();
7485 if (neuron_id as usize) >= neuron_count {
7486 return None;
7487 }
7488
7489 Some(
7490 npu_lock
7491 .get_neuron_coordinates(neuron_id as u32)
7492 .unwrap_or((0, 0, 0)),
7493 )
7494 }
7495
7496 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
7507 let npu = self.npu.as_ref()?;
7508 let npu_lock = npu.lock().ok()?;
7509
7510 let neuron_count = npu_lock.get_neuron_count();
7512 if (neuron_id as usize) >= neuron_count {
7513 return None;
7514 }
7515
7516 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
7517
7518 self.cortical_areas
7520 .values()
7521 .find(|area| area.cortical_idx == cortical_idx)
7522 .map(|area| area.cortical_id)
7523 }
7524
7525 pub fn get_neuron_properties(
7536 &self,
7537 neuron_id: u64,
7538 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7539 let npu = self.npu.as_ref()?;
7540 let npu_lock = npu.lock().ok()?;
7541
7542 let neuron_id_u32 = neuron_id as u32;
7543 let idx = neuron_id as usize;
7544
7545 let neuron_count = npu_lock.get_neuron_count();
7547 if idx >= neuron_count {
7548 return None;
7549 }
7550
7551 let mut properties = std::collections::HashMap::new();
7552
7553 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
7555
7556 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
7558 properties.insert("x".to_string(), serde_json::json!(x));
7559 properties.insert("y".to_string(), serde_json::json!(y));
7560 properties.insert("z".to_string(), serde_json::json!(z));
7561
7562 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
7564 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
7565
7566 properties.insert(
7568 "mp_charge_accumulation".to_string(),
7569 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
7570 );
7571 properties.insert(
7572 "neuron_type".to_string(),
7573 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
7574 );
7575 let (mp_drv, psp_uni) = self
7576 .cortical_idx_to_id
7577 .get(&cortical_idx)
7578 .map(|cid| {
7579 (
7580 npu_lock.get_mp_driven_psp_for_cortical(cid),
7581 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
7582 )
7583 })
7584 .unwrap_or((false, false));
7585 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
7586 properties.insert(
7587 "psp_uniform_distribution".to_string(),
7588 serde_json::json!(psp_uni),
7589 );
7590
7591 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
7594 .get_neuron_state(NeuronId(neuron_id_u32))
7595 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
7596 properties.insert(
7597 "consecutive_fire_count".to_string(),
7598 serde_json::json!(consec_count),
7599 );
7600 properties.insert(
7601 "consecutive_fire_limit".to_string(),
7602 serde_json::json!(consec_limit),
7603 );
7604 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
7605 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
7606 properties.insert("threshold".to_string(), serde_json::json!(threshold));
7607 properties.insert(
7608 "refractory_countdown".to_string(),
7609 serde_json::json!(refract_countdown),
7610 );
7611
7612 properties.insert(
7614 "leak_coefficient".to_string(),
7615 serde_json::json!(npu_lock
7616 .get_neuron_property_by_index(idx, "leak_coefficient")
7617 .unwrap_or(0.0)),
7618 );
7619 properties.insert(
7620 "resting_potential".to_string(),
7621 serde_json::json!(npu_lock
7622 .get_neuron_property_by_index(idx, "resting_potential")
7623 .unwrap_or(0.0)),
7624 );
7625 properties.insert(
7626 "excitability".to_string(),
7627 serde_json::json!(npu_lock
7628 .get_neuron_property_by_index(idx, "excitability")
7629 .unwrap_or(0.0)),
7630 );
7631 properties.insert(
7632 "threshold_limit".to_string(),
7633 serde_json::json!(npu_lock
7634 .get_neuron_property_by_index(idx, "threshold_limit")
7635 .unwrap_or(0.0)),
7636 );
7637 properties.insert(
7638 "refractory_period".to_string(),
7639 serde_json::json!(npu_lock
7640 .get_neuron_property_u16_by_index(idx, "refractory_period")
7641 .unwrap_or(0)),
7642 );
7643
7644 Some(properties)
7645 }
7646
7647 pub fn get_neuron_property(
7659 &self,
7660 neuron_id: u64,
7661 property_name: &str,
7662 ) -> Option<serde_json::Value> {
7663 self.get_neuron_properties(neuron_id)?
7664 .get(property_name)
7665 .cloned()
7666 }
7667
7668 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
7679 self.cortical_areas.keys().copied().collect()
7680 }
7681
7682 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
7689 self.cortical_areas
7690 .values()
7691 .map(|area| area.cortical_idx)
7692 .collect()
7693 }
7694
7695 pub fn get_cortical_area_names(&self) -> Vec<String> {
7702 self.cortical_areas
7703 .values()
7704 .map(|area| area.name.clone())
7705 .collect()
7706 }
7707
7708 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
7715 use crate::models::CorticalAreaExt;
7716 self.cortical_areas
7717 .values()
7718 .filter(|area| area.is_input_area())
7719 .map(|area| area.cortical_id)
7720 .collect()
7721 }
7722
7723 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
7730 use crate::models::CorticalAreaExt;
7731 self.cortical_areas
7732 .values()
7733 .filter(|area| area.is_output_area())
7734 .map(|area| area.cortical_id)
7735 .collect()
7736 }
7737
7738 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
7745 self.cortical_areas
7746 .values()
7747 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
7748 (
7749 max_w.max(area.dimensions.width as usize),
7750 max_h.max(area.dimensions.height as usize),
7751 max_d.max(area.dimensions.depth as usize),
7752 )
7753 })
7754 }
7755
7756 pub fn get_cortical_area_properties(
7767 &self,
7768 cortical_id: &CorticalID,
7769 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7770 let area = self.get_cortical_area(cortical_id)?;
7771
7772 let mut properties = std::collections::HashMap::new();
7773 properties.insert(
7774 "cortical_id".to_string(),
7775 serde_json::json!(area.cortical_id),
7776 );
7777 properties.insert(
7778 "cortical_id_s".to_string(),
7779 serde_json::json!(area.cortical_id.to_string()),
7780 );
7781 properties.insert(
7782 "cortical_idx".to_string(),
7783 serde_json::json!(area.cortical_idx),
7784 );
7785 properties.insert("name".to_string(), serde_json::json!(area.name));
7786 use crate::models::CorticalAreaExt;
7787 properties.insert(
7788 "area_type".to_string(),
7789 serde_json::json!(area.get_cortical_group()),
7790 );
7791 properties.insert(
7792 "dimensions".to_string(),
7793 serde_json::json!({
7794 "width": area.dimensions.width,
7795 "height": area.dimensions.height,
7796 "depth": area.dimensions.depth,
7797 }),
7798 );
7799 properties.insert("position".to_string(), serde_json::json!(area.position));
7800
7801 for (key, value) in &area.properties {
7803 properties.insert(key.clone(), value.clone());
7804 }
7805
7806 properties.extend(area.properties.clone());
7808
7809 Some(properties)
7810 }
7811
7812 pub fn get_all_cortical_area_properties(
7819 &self,
7820 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
7821 self.cortical_areas
7822 .keys()
7823 .filter_map(|id| self.get_cortical_area_properties(id))
7824 .collect()
7825 }
7826
7827 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
7838 self.brain_regions
7839 .get_all_region_ids()
7840 .into_iter()
7841 .cloned()
7842 .collect()
7843 }
7844
7845 pub fn get_brain_region_names(&self) -> Vec<String> {
7852 self.brain_regions
7853 .get_all_region_ids()
7854 .iter()
7855 .filter_map(|id| {
7856 self.brain_regions
7857 .get_region(id)
7858 .map(|region| region.name.clone())
7859 })
7860 .collect()
7861 }
7862
7863 pub fn get_brain_region_properties(
7874 &self,
7875 region_id: &str,
7876 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7877 let region = self.brain_regions.get_region(region_id)?;
7878
7879 let mut properties = std::collections::HashMap::new();
7880 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
7881 properties.insert("name".to_string(), serde_json::json!(region.name));
7882 properties.insert(
7883 "region_type".to_string(),
7884 serde_json::json!(format!("{:?}", region.region_type)),
7885 );
7886 properties.insert(
7887 "cortical_areas".to_string(),
7888 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
7889 );
7890
7891 properties.extend(region.properties.clone());
7893
7894 Some(properties)
7895 }
7896
7897 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
7908 self.cortical_areas.contains_key(cortical_id)
7909 }
7910
7911 pub fn brain_region_exists(&self, region_id: &str) -> bool {
7922 self.brain_regions.get_region(region_id).is_some()
7923 }
7924
7925 pub fn get_brain_region_count(&self) -> usize {
7932 self.brain_regions.region_count()
7933 }
7934
7935 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
7946 self.get_neurons_in_area(cortical_id)
7950 }
7951}
7952
7953impl std::fmt::Debug for ConnectomeManager {
7955 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7956 f.debug_struct("ConnectomeManager")
7957 .field("cortical_areas", &self.cortical_areas.len())
7958 .field("next_cortical_idx", &self.next_cortical_idx)
7959 .field("brain_regions", &self.brain_regions)
7960 .field(
7961 "npu",
7962 &if self.npu.is_some() {
7963 "Connected"
7964 } else {
7965 "Not connected"
7966 },
7967 )
7968 .field("initialized", &self.initialized)
7969 .finish()
7970 }
7971}
7972
7973#[cfg(test)]
7974mod tests {
7975 use super::*;
7976 use feagi_structures::genomic::cortical_area::CoreCorticalType;
7977
7978 #[cfg(feature = "plasticity")]
7982 #[test]
7983 fn prepare_for_new_genome_discards_memory_neurons_from_previous_brain() {
7984 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
7985 use feagi_npu_plasticity::executor::PlasticityExecutor;
7986 use feagi_npu_plasticity::{
7987 create_memory_stats_cache, AsyncPlasticityExecutor, MemoryNeuronDetail,
7988 PlasticityConfig,
7989 };
7990 use feagi_npu_runtime::StdRuntime;
7991
7992 let npu = Arc::new(TracingMutex::new(
7993 DynamicNPU::new_f32(
7994 StdRuntime::new(),
7995 feagi_npu_burst_engine::backend::CPUBackend::new(),
7996 16,
7997 16,
7998 8,
7999 )
8000 .unwrap(),
8001 "prepare-for-new-genome-test-npu",
8002 ));
8003 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
8004 PlasticityConfig::default(),
8005 create_memory_stats_cache(),
8006 npu.clone(),
8007 )));
8008
8009 let mut manager = ConnectomeManager::new_for_testing();
8010 manager.set_npu(npu);
8011 manager.set_plasticity_executor(executor.clone());
8012
8013 let previous_brain_memory_idx = 8;
8014 {
8015 let exec = executor.lock().expect("plasticity executor");
8016 PlasticityExecutor::register_memory_area(
8017 &*exec,
8018 previous_brain_memory_idx,
8019 "previous_brain_memory".to_string(),
8020 1,
8021 vec![7],
8022 None,
8023 false,
8024 );
8025 exec.restore_long_term_memory_neurons(&[MemoryNeuronDetail {
8026 neuron_id: 50_000_003,
8027 cortical_area_idx: previous_brain_memory_idx,
8028 pattern_hash: Some(9_631_261_403_772_054_764),
8029 is_longterm_memory: true,
8030 is_active: true,
8031 lifespan_current: 120,
8032 lifespan_initial: 20,
8033 lifespan_growth_rate: 3.0,
8034 creation_burst: 0,
8035 last_activation_burst: 0,
8036 activation_count: 5,
8037 }])
8038 .expect("seeded long-term memory neuron");
8039 assert_eq!(
8040 exec.export_long_term_memory_neurons()
8041 .expect("plasticity service is initialized")
8042 .len(),
8043 1
8044 );
8045 }
8046
8047 manager
8048 .prepare_for_new_genome()
8049 .expect("genome preparation must succeed");
8050
8051 let exec = executor.lock().expect("plasticity executor");
8052 assert!(
8053 exec.export_long_term_memory_neurons()
8054 .expect("plasticity service is initialized")
8055 .is_empty(),
8056 "memory neurons from the previous brain must not survive a genome load"
8057 );
8058 assert_eq!(
8059 exec.paginated_memory_neuron_ids_in_area(previous_brain_memory_idx, 0, 10),
8060 Some((Vec::new(), 0)),
8061 "the previous brain's memory area must no longer report any memory neurons"
8062 );
8063 }
8064
8065 #[test]
8066 fn test_singleton_instance() {
8067 let instance1 = ConnectomeManager::instance();
8068 let instance2 = ConnectomeManager::instance();
8069
8070 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
8072 }
8073
8074 #[test]
8075 fn test_add_cortical_area() {
8076 ConnectomeManager::reset_for_testing();
8077
8078 let instance = ConnectomeManager::instance();
8079 let mut manager = instance.write();
8080
8081 use feagi_structures::genomic::cortical_area::{
8082 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8083 };
8084 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
8086 let area = CorticalArea::new(
8087 cortical_id,
8088 0,
8089 "Visual Input".to_string(),
8090 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
8091 (0, 0, 0).into(),
8092 cortical_type,
8093 )
8094 .unwrap();
8095
8096 let initial_count = manager.get_cortical_area_count();
8097 let _cortical_idx = manager.add_cortical_area(area).unwrap();
8098
8099 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
8100 assert!(manager.has_cortical_area(&cortical_id));
8101 assert!(manager.is_initialized());
8102 }
8103
8104 #[test]
8105 fn refresh_all_connectome_hashes_publishes_mappings() {
8106 use feagi_structures::genomic::cortical_area::{
8107 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CustomCorticalType,
8108 };
8109
8110 let src_id = CorticalID::try_from_bytes(b"chashsrc").unwrap();
8111 let dst_id = CorticalID::try_from_bytes(b"chashdst").unwrap();
8112 let mut src = CorticalArea::new(
8113 src_id,
8114 1,
8115 "src".to_string(),
8116 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8117 (0, 0, 0).into(),
8118 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
8119 )
8120 .unwrap();
8121 src.properties.insert(
8122 "cortical_mapping_dst".to_string(),
8123 serde_json::json!({
8124 dst_id.as_base_64(): [{ "morphology_id": "projector" }]
8125 }),
8126 );
8127 let mut manager = ConnectomeManager::new_for_testing();
8128 manager.add_cortical_area(src).unwrap();
8129 manager.refresh_all_connectome_hashes();
8130 let mappings_hash = feagi_state_manager::StateManager::instance()
8131 .read()
8132 .get_cortical_mappings_hash();
8133 assert_ne!(
8134 mappings_hash, 0,
8135 "refresh_all_connectome_hashes must publish cortical_mappings_hash"
8136 );
8137 }
8138
8139 #[test]
8140 fn test_cortical_area_lookups() {
8141 ConnectomeManager::reset_for_testing();
8142
8143 let instance = ConnectomeManager::instance();
8144 let mut manager = instance.write();
8145
8146 use feagi_structures::genomic::cortical_area::{
8147 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8148 };
8149 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
8151 let area = CorticalArea::new(
8152 cortical_id,
8153 0,
8154 "Test Area".to_string(),
8155 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
8156 (0, 0, 0).into(),
8157 cortical_type,
8158 )
8159 .unwrap();
8160
8161 let cortical_idx = manager.add_cortical_area(area).unwrap();
8162
8163 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
8165
8166 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
8168
8169 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
8171 assert_eq!(retrieved_area.name, "Test Area");
8172 }
8173
8174 #[test]
8175 fn test_remove_cortical_area() {
8176 ConnectomeManager::reset_for_testing();
8177
8178 let instance = ConnectomeManager::instance();
8179 let mut manager = instance.write();
8180
8181 use feagi_structures::genomic::cortical_area::{
8182 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8183 };
8184 let cortical_id = CoreCorticalType::Power.to_cortical_id();
8185
8186 if manager.has_cortical_area(&cortical_id) {
8188 manager.remove_cortical_area(&cortical_id).unwrap();
8189 }
8190
8191 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
8192 let area = CorticalArea::new(
8193 cortical_id,
8194 0,
8195 "Test".to_string(),
8196 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
8197 (0, 0, 0).into(),
8198 cortical_type,
8199 )
8200 .unwrap();
8201
8202 let initial_count = manager.get_cortical_area_count();
8203 manager.add_cortical_area(area).unwrap();
8204 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
8205
8206 manager.remove_cortical_area(&cortical_id).unwrap();
8207 assert_eq!(manager.get_cortical_area_count(), initial_count);
8208 assert!(!manager.has_cortical_area(&cortical_id));
8209 }
8210
8211 #[test]
8212 fn test_duplicate_area_error() {
8213 ConnectomeManager::reset_for_testing();
8214
8215 let instance = ConnectomeManager::instance();
8216 let mut manager = instance.write();
8217
8218 use feagi_structures::genomic::cortical_area::{
8219 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8220 };
8221 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
8224 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
8225 let area1 = CorticalArea::new(
8226 cortical_id,
8227 0,
8228 "First".to_string(),
8229 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
8230 (0, 0, 0).into(),
8231 cortical_type,
8232 )
8233 .unwrap();
8234
8235 let area2 = CorticalArea::new(
8236 cortical_id, 1,
8238 "Second".to_string(),
8239 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
8240 (0, 0, 0).into(),
8241 cortical_type,
8242 )
8243 .unwrap();
8244
8245 manager.add_cortical_area(area1).unwrap();
8246 let result = manager.add_cortical_area(area2);
8247
8248 assert!(result.is_err());
8249 }
8250
8251 #[test]
8252 fn test_brain_region_management() {
8253 ConnectomeManager::reset_for_testing();
8254
8255 let instance = ConnectomeManager::instance();
8256 let mut manager = instance.write();
8257
8258 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
8259 let region_id_str = region_id.to_string();
8260 let root = BrainRegion::new(
8261 region_id,
8262 "Root".to_string(),
8263 feagi_structures::genomic::brain_regions::RegionType::Undefined,
8264 )
8265 .unwrap();
8266
8267 let initial_count = manager.get_brain_region_ids().len();
8268 manager.add_brain_region(root, None).unwrap();
8269
8270 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
8271 assert!(manager.get_brain_region(®ion_id_str).is_some());
8272 }
8273
8274 #[test]
8275 fn test_synapse_operations() {
8276 use feagi_npu_burst_engine::npu::RustNPU;
8277 use feagi_npu_burst_engine::TracingMutex;
8278 use std::sync::Arc;
8279
8280 use feagi_npu_burst_engine::backend::CPUBackend;
8282 use feagi_npu_burst_engine::DynamicNPU;
8283 use feagi_npu_runtime::StdRuntime;
8284
8285 let runtime = StdRuntime;
8286 let backend = CPUBackend::new();
8287 let npu_result =
8288 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
8289 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
8290 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
8291
8292 use feagi_structures::genomic::cortical_area::{
8294 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8295 };
8296 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
8298 let area = CorticalArea::new(
8299 cortical_id,
8300 0, "Test Area".to_string(),
8302 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
8303 (0, 0, 0).into(), cortical_type,
8305 )
8306 .unwrap();
8307 let cortical_idx = manager.add_cortical_area(area).unwrap();
8308
8309 if let Some(npu_arc) = manager.get_npu() {
8311 if let Ok(mut npu_guard) = npu_arc.try_lock() {
8312 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
8313 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
8314 }
8315 }
8316 }
8317
8318 let neuron1_id = manager
8320 .add_neuron(
8321 &cortical_id,
8322 0,
8323 0,
8324 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
8336 .unwrap();
8337
8338 let neuron2_id = manager
8339 .add_neuron(
8340 &cortical_id,
8341 1,
8342 0,
8343 0, 100.0,
8345 f32::MAX, 0.1,
8347 -60.0,
8348 0,
8349 2,
8350 1.0,
8351 5,
8352 10,
8353 false,
8354 )
8355 .unwrap();
8356
8357 manager
8359 .create_synapse(
8360 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
8364 .unwrap();
8365
8366 println!("✅ Synapse creation test passed");
8369 }
8370
8371 #[test]
8372 fn test_apply_cortical_mapping_missing_rules_is_ok() {
8373 let mut manager = ConnectomeManager::new_for_testing();
8376
8377 use feagi_structures::genomic::cortical_area::{
8378 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8379 };
8380
8381 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
8382 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
8383
8384 let src_area = CorticalArea::new(
8385 src_id,
8386 0,
8387 "src".to_string(),
8388 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8389 (0, 0, 0).into(),
8390 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8391 )
8392 .unwrap();
8393
8394 let dst_area = CorticalArea::new(
8395 dst_id,
8396 1,
8397 "dst".to_string(),
8398 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8399 (0, 0, 0).into(),
8400 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8401 )
8402 .unwrap();
8403
8404 manager.add_cortical_area(src_area).unwrap();
8405 manager.add_cortical_area(dst_area).unwrap();
8406
8407 let count = manager
8409 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
8410 .unwrap();
8411 assert_eq!(count, 0);
8412
8413 manager
8415 .update_cortical_mapping(
8416 &src_id,
8417 &dst_id,
8418 vec![serde_json::json!({"morphology_id":"m1"})],
8419 )
8420 .unwrap();
8421 manager
8422 .update_cortical_mapping(&src_id, &dst_id, vec![])
8423 .unwrap();
8424
8425 let count2 = manager
8426 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
8427 .unwrap();
8428 assert_eq!(count2, 0);
8429 }
8430
8431 #[test]
8432 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
8433 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8434 let mapping_dst = serde_json::json!({
8435 "csrc0002": [
8436 {"morphology_id": "m1"}
8437 ]
8438 });
8439 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
8440
8441 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
8442 .expect("legacy destination key should resolve");
8443 assert_eq!(rules.len(), 1);
8444 assert_eq!(
8445 rules[0].get("morphology_id").and_then(|v| v.as_str()),
8446 Some("m1")
8447 );
8448 }
8449
8450 #[test]
8451 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
8452 use feagi_npu_burst_engine::backend::CPUBackend;
8453 use feagi_npu_burst_engine::RustNPU;
8454 use feagi_npu_burst_engine::TracingMutex;
8455 use feagi_npu_runtime::StdRuntime;
8456 use feagi_structures::genomic::cortical_area::{
8457 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
8458 };
8459 use std::sync::Arc;
8460
8461 let runtime = StdRuntime;
8462 let backend = CPUBackend::new();
8463 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8464 let dyn_npu = Arc::new(TracingMutex::new(
8465 feagi_npu_burst_engine::DynamicNPU::F32(npu),
8466 "TestNPU",
8467 ));
8468 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8469
8470 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
8471 let area = CorticalArea::new(
8472 area_id,
8473 0,
8474 "n".to_string(),
8475 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8476 (0, 0, 0).into(),
8477 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8478 )
8479 .unwrap();
8480
8481 manager.add_cortical_area(area).unwrap();
8482 let nid = manager
8483 .add_neuron(
8484 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
8485 )
8486 .unwrap();
8487
8488 let props = manager
8489 .get_neuron_properties(nid)
8490 .expect("neuron properties");
8491 for key in [
8492 "consecutive_fire_count",
8493 "consecutive_fire_limit",
8494 "snooze_period",
8495 "membrane_potential",
8496 "threshold",
8497 "refractory_countdown",
8498 "mp_charge_accumulation",
8499 "neuron_type",
8500 "mp_driven_psp",
8501 "psp_uniform_distribution",
8502 "leak_coefficient",
8503 "resting_potential",
8504 "excitability",
8505 "threshold_limit",
8506 "refractory_period",
8507 ] {
8508 assert!(props.contains_key(key), "missing neuron state key: {key}");
8509 }
8510 }
8511
8512 #[test]
8513 fn test_mapping_deletion_prunes_synapses_between_areas() {
8514 use feagi_npu_burst_engine::backend::CPUBackend;
8515 use feagi_npu_burst_engine::RustNPU;
8516 use feagi_npu_burst_engine::TracingMutex;
8517 use feagi_npu_runtime::StdRuntime;
8518 use feagi_structures::genomic::cortical_area::{
8519 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
8520 };
8521 use std::sync::Arc;
8522
8523 let runtime = StdRuntime;
8525 let backend = CPUBackend::new();
8526 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8527 let dyn_npu = Arc::new(TracingMutex::new(
8528 feagi_npu_burst_engine::DynamicNPU::F32(npu),
8529 "TestNPU",
8530 ));
8531 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8532
8533 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
8535 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
8536
8537 let src_area = CorticalArea::new(
8538 src_id,
8539 0,
8540 "src".to_string(),
8541 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8542 (0, 0, 0).into(),
8543 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8544 )
8545 .unwrap();
8546 let dst_area = CorticalArea::new(
8547 dst_id,
8548 1,
8549 "dst".to_string(),
8550 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8551 (0, 0, 0).into(),
8552 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8553 )
8554 .unwrap();
8555
8556 manager.add_cortical_area(src_area).unwrap();
8557 manager.add_cortical_area(dst_area).unwrap();
8558
8559 let s0 = manager
8561 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8562 .unwrap();
8563 let s1 = manager
8564 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8565 .unwrap();
8566 let t0 = manager
8567 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8568 .unwrap();
8569 let t1 = manager
8570 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8571 .unwrap();
8572
8573 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
8575 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
8576
8577 {
8579 let mut npu = dyn_npu.lock().unwrap();
8580 npu.rebuild_synapse_index();
8581 assert_eq!(npu.get_synapse_count(), 2);
8582 }
8583
8584 manager
8586 .update_cortical_mapping(&src_id, &dst_id, vec![])
8587 .unwrap();
8588 let created = manager
8589 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8590 .unwrap();
8591 assert_eq!(created, 0);
8592
8593 {
8595 let mut npu = dyn_npu.lock().unwrap();
8596 npu.rebuild_synapse_index();
8598 assert_eq!(npu.get_synapse_count(), 0);
8599 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
8600 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
8601 }
8602 }
8603
8604 #[test]
8605 fn test_mapping_update_prunes_synapses_between_areas() {
8606 use feagi_npu_burst_engine::backend::CPUBackend;
8607 use feagi_npu_burst_engine::RustNPU;
8608 use feagi_npu_burst_engine::TracingMutex;
8609 use feagi_npu_runtime::StdRuntime;
8610 use feagi_structures::genomic::cortical_area::{
8611 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
8612 };
8613 use std::sync::Arc;
8614
8615 let runtime = StdRuntime;
8617 let backend = CPUBackend::new();
8618 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8619 let dyn_npu = Arc::new(TracingMutex::new(
8620 feagi_npu_burst_engine::DynamicNPU::F32(npu),
8621 "TestNPU",
8622 ));
8623 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8624
8625 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8627
8628 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
8631 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
8632
8633 let src_area = CorticalArea::new(
8634 src_id,
8635 0,
8636 "src".to_string(),
8637 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8638 (0, 0, 0).into(),
8639 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8640 )
8641 .unwrap();
8642 let dst_area = CorticalArea::new(
8643 dst_id,
8644 0,
8645 "dst".to_string(),
8646 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8647 (0, 0, 0).into(),
8648 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8649 )
8650 .unwrap();
8651
8652 manager.add_cortical_area(src_area).unwrap();
8653 manager.add_cortical_area(dst_area).unwrap();
8654
8655 let s0 = manager
8657 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8658 .unwrap();
8659 let s1 = manager
8660 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8661 .unwrap();
8662 let t0 = manager
8663 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8664 .unwrap();
8665 let t1 = manager
8666 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8667 .unwrap();
8668
8669 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
8671 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
8672
8673 {
8675 let mut npu = dyn_npu.lock().unwrap();
8676 npu.rebuild_synapse_index();
8677 assert_eq!(npu.get_synapse_count(), 2);
8678 }
8679
8680 manager
8686 .update_cortical_mapping(
8687 &src_id,
8688 &dst_id,
8689 vec![serde_json::json!({
8690 "morphology_id": "episodic_memory",
8691 "morphology_scalar": [1],
8692 "postSynapticCurrent_multiplier": 1,
8693 "plasticity_flag": false,
8694 "plasticity_constant": 0,
8695 "ltp_multiplier": 0,
8696 "ltd_multiplier": 0,
8697 "plasticity_window": 0,
8698 })],
8699 )
8700 .unwrap();
8701 let created = manager
8702 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8703 .unwrap();
8704 assert_eq!(created, 0);
8705
8706 {
8708 let mut npu = dyn_npu.lock().unwrap();
8709 npu.rebuild_synapse_index();
8711 assert_eq!(npu.get_synapse_count(), 0);
8712 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
8713 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
8714 }
8715 }
8716
8717 #[test]
8718 fn test_upstream_area_tracking() {
8719 use crate::models::cortical_area::CorticalArea;
8721 use feagi_npu_burst_engine::backend::CPUBackend;
8722 use feagi_npu_burst_engine::TracingMutex;
8723 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8724 use feagi_npu_runtime::StdRuntime;
8725 use feagi_structures::genomic::cortical_area::{
8726 CorticalAreaDimensions, CorticalAreaType, CorticalID,
8727 };
8728
8729 let runtime = StdRuntime;
8731 let backend = CPUBackend::new();
8732 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8733 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8734 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8735
8736 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8739
8740 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
8742 let src_area = CorticalArea::new(
8743 src_id,
8744 0,
8745 "Source Area".to_string(),
8746 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8747 (0, 0, 0).into(),
8748 CorticalAreaType::Custom(
8749 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8750 ),
8751 )
8752 .unwrap();
8753 let src_idx = manager.add_cortical_area(src_area).unwrap();
8754
8755 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
8757 let dst_area = CorticalArea::new(
8758 dst_id,
8759 0,
8760 "Dest Area".to_string(),
8761 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8762 (0, 0, 0).into(),
8763 CorticalAreaType::Custom(
8764 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8765 ),
8766 )
8767 .unwrap();
8768 manager.add_cortical_area(dst_area).unwrap();
8769
8770 {
8772 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
8773 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
8774 assert!(
8775 upstream.as_array().unwrap().is_empty(),
8776 "Upstream areas should be empty initially"
8777 );
8778 }
8779
8780 let mapping_data = vec![serde_json::json!({
8782 "morphology_id": "episodic_memory",
8783 "morphology_scalar": 1,
8784 "postSynapticCurrent_multiplier": 1.0,
8785 })];
8786 manager
8787 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
8788 .unwrap();
8789 manager
8790 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8791 .unwrap();
8792
8793 {
8795 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
8796 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
8797 assert_eq!(
8798 upstream_areas[0], src_idx,
8799 "Upstream area should be src_idx"
8800 );
8801 }
8802
8803 manager
8805 .update_cortical_mapping(&src_id, &dst_id, vec![])
8806 .unwrap();
8807 manager
8808 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8809 .unwrap();
8810
8811 {
8813 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
8814 assert_eq!(
8815 upstream_areas.len(),
8816 0,
8817 "Should have 0 upstream areas after deletion"
8818 );
8819 }
8820 }
8821
8822 #[test]
8823 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
8824 use crate::models::cortical_area::CorticalArea;
8825 use feagi_npu_burst_engine::backend::CPUBackend;
8826 use feagi_npu_burst_engine::TracingMutex;
8827 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8828 use feagi_npu_runtime::StdRuntime;
8829 use feagi_structures::genomic::cortical_area::{
8830 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8831 };
8832 use std::sync::Arc;
8833
8834 let runtime = StdRuntime;
8835 let backend = CPUBackend::new();
8836 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8837 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8838 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8839 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8840
8841 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8842 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
8843 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
8844 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
8845
8846 let a1_area = CorticalArea::new(
8847 a1_id,
8848 0,
8849 "A1".to_string(),
8850 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8851 (0, 0, 0).into(),
8852 CorticalAreaType::Custom(
8853 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8854 ),
8855 )
8856 .unwrap();
8857 let a2_area = CorticalArea::new(
8858 a2_id,
8859 0,
8860 "A2".to_string(),
8861 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8862 (0, 0, 0).into(),
8863 CorticalAreaType::Custom(
8864 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8865 ),
8866 )
8867 .unwrap();
8868
8869 let mut m1_area = CorticalArea::new(
8870 m1_id,
8871 0,
8872 "M1".to_string(),
8873 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8874 (0, 0, 0).into(),
8875 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8876 )
8877 .unwrap();
8878 m1_area
8879 .properties
8880 .insert("is_mem_type".to_string(), serde_json::json!(true));
8881 m1_area
8882 .properties
8883 .insert("temporal_depth".to_string(), serde_json::json!(1));
8884
8885 let mut m2_area = CorticalArea::new(
8886 m2_id,
8887 0,
8888 "M2".to_string(),
8889 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8890 (0, 0, 0).into(),
8891 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8892 )
8893 .unwrap();
8894 m2_area
8895 .properties
8896 .insert("is_mem_type".to_string(), serde_json::json!(true));
8897 m2_area
8898 .properties
8899 .insert("temporal_depth".to_string(), serde_json::json!(1));
8900
8901 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
8902 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
8903 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
8904 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
8905
8906 manager
8907 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8908 .unwrap();
8909 manager
8910 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8911 .unwrap();
8912
8913 let episodic_mapping = vec![serde_json::json!({
8914 "morphology_id": "episodic_memory",
8915 "morphology_scalar": 1,
8916 "postSynapticCurrent_multiplier": 1.0,
8917 })];
8918 manager
8919 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
8920 .unwrap();
8921 manager
8922 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
8923 .unwrap();
8924 manager
8925 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
8926 .unwrap();
8927 manager
8928 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
8929 .unwrap();
8930
8931 let assoc_mapping = vec![serde_json::json!({
8932 "morphology_id": "associative_memory",
8933 "morphology_scalar": 1,
8934 "postSynapticCurrent_multiplier": 1.0,
8935 "plasticity_flag": true,
8936 "plasticity_constant": 1,
8937 "ltp_multiplier": 1,
8938 "ltd_multiplier": 1,
8939 "plasticity_window": 5,
8940 })];
8941 manager
8942 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
8943 .unwrap();
8944 manager
8945 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8946 .unwrap();
8947 manager
8949 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
8950 .unwrap();
8951 manager
8952 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
8953 .unwrap();
8954
8955 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8956 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8957 assert_eq!(
8958 upstream_m1.len(),
8959 2,
8960 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
8961 );
8962 assert_eq!(
8963 upstream_m2.len(),
8964 2,
8965 "M2 should have A2 and M1 as upstreams"
8966 );
8967
8968 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
8969 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
8970
8971 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8972 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8973 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
8974 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
8975 assert!(upstream_m1.contains(&a1_idx));
8976 assert!(upstream_m1.contains(&m2_idx));
8977 assert!(upstream_m2.contains(&a2_idx));
8978 assert!(upstream_m2.contains(&m1_idx));
8979
8980 {
8982 let mut npu_lock = dyn_npu.lock().unwrap();
8983 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
8984 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
8985 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
8986 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
8987 npu_lock.process_burst().expect("Burst processing failed");
8988 }
8989
8990 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8991 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8992 assert_eq!(
8993 upstream_m1.len(),
8994 2,
8995 "M1 should keep 2 upstreams after firing"
8996 );
8997 assert_eq!(
8998 upstream_m2.len(),
8999 2,
9000 "M2 should keep 2 upstreams after firing"
9001 );
9002
9003 let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
9004 let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
9005 assert_eq!(
9006 episodic_upstream_m1,
9007 vec![a1_idx],
9008 "Episodic upstream list for M1 should exclude associative-only memory source M2"
9009 );
9010 assert_eq!(
9011 episodic_upstream_m2,
9012 vec![a2_idx],
9013 "Episodic upstream list for M2 should exclude associative-only memory source M1"
9014 );
9015 }
9016
9017 #[test]
9018 fn test_memory_to_non_memory_requires_associative_morphology() {
9019 use crate::models::cortical_area::CorticalArea;
9020 use feagi_structures::genomic::cortical_area::{
9021 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
9022 MemoryCorticalType,
9023 };
9024
9025 let mut manager = ConnectomeManager::new_for_testing();
9026 let memory_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
9027 let destination_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
9028 let memory_area = CorticalArea::new(
9029 memory_id,
9030 0,
9031 "Memory Area".to_string(),
9032 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9033 (0, 0, 0).into(),
9034 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9035 )
9036 .unwrap();
9037 let destination_area = CorticalArea::new(
9038 destination_id,
9039 0,
9040 "Destination Area".to_string(),
9041 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9042 (1, 0, 0).into(),
9043 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9044 )
9045 .unwrap();
9046 manager.add_cortical_area(memory_area).unwrap();
9047 manager.add_cortical_area(destination_area).unwrap();
9048
9049 let invalid = manager.update_cortical_mapping(
9050 &memory_id,
9051 &destination_id,
9052 vec![serde_json::json!({"morphology_id": "episodic_memory"})],
9053 );
9054 assert!(matches!(invalid, Err(BduError::InvalidMorphology(_))));
9055
9056 manager
9057 .update_cortical_mapping(
9058 &memory_id,
9059 &destination_id,
9060 vec![serde_json::json!({"morphology_id": "associative_memory"})],
9061 )
9062 .unwrap();
9063 }
9064
9065 #[test]
9066 fn test_memory_twin_created_for_memory_mapping() {
9067 use crate::models::cortical_area::CorticalArea;
9068 use feagi_npu_burst_engine::backend::CPUBackend;
9069 use feagi_npu_burst_engine::TracingMutex;
9070 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9071 use feagi_npu_runtime::StdRuntime;
9072 use feagi_structures::genomic::cortical_area::{
9073 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
9074 MemoryCorticalType,
9075 };
9076 use std::sync::Arc;
9077
9078 let runtime = StdRuntime;
9079 let backend = CPUBackend::new();
9080 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9081 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9082 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9083 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9084
9085 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
9086 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
9087
9088 let src_area = CorticalArea::new(
9089 src_id,
9090 0,
9091 "Source Area".to_string(),
9092 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9093 (0, 0, 0).into(),
9094 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9095 )
9096 .unwrap();
9097 let mut dst_area = CorticalArea::new(
9098 dst_id,
9099 0,
9100 "Memory Area".to_string(),
9101 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9102 (0, 0, 0).into(),
9103 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9104 )
9105 .unwrap();
9106 dst_area
9107 .properties
9108 .insert("is_mem_type".to_string(), serde_json::json!(true));
9109 dst_area
9110 .properties
9111 .insert("temporal_depth".to_string(), serde_json::json!(1));
9112
9113 manager.add_cortical_area(src_area).unwrap();
9114 manager.add_cortical_area(dst_area).unwrap();
9115
9116 let mapping_data = vec![serde_json::json!({
9117 "morphology_id": "episodic_memory",
9118 "morphology_scalar": 1,
9119 "postSynapticCurrent_multiplier": 1.0,
9120 })];
9121 manager
9122 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
9123 .unwrap();
9124 manager
9125 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9126 .unwrap();
9127
9128 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
9129 let twin_map = memory_area
9130 .properties
9131 .get("memory_twin_areas")
9132 .and_then(|v| v.as_object())
9133 .expect("memory_twin_areas should be set");
9134 let twin_id_str = twin_map
9135 .get(&src_id.as_base_64())
9136 .and_then(|v| v.as_str())
9137 .expect("Missing twin entry for upstream area");
9138 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
9139 let mapping = memory_area
9140 .properties
9141 .get("cortical_mapping_dst")
9142 .and_then(|v| v.as_object())
9143 .and_then(|map| map.get(&twin_id.as_base_64()))
9144 .and_then(|v| v.as_array())
9145 .expect("Missing memory replay mapping for twin area");
9146 let uses_replay = mapping.iter().any(|rule| {
9147 rule.get("morphology_id")
9148 .and_then(|v| v.as_str())
9149 .is_some_and(|id| id == "memory_replay")
9150 });
9151 assert!(uses_replay, "Expected memory_replay mapping for twin area");
9152
9153 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
9154 assert!(matches!(
9155 twin_area.cortical_type,
9156 CorticalAreaType::Custom(_)
9157 ));
9158 assert_eq!(
9159 twin_area
9160 .properties
9161 .get("memory_twin_of")
9162 .and_then(|v| v.as_str()),
9163 Some(src_id.as_base_64().as_str())
9164 );
9165 assert_eq!(
9166 twin_area
9167 .properties
9168 .get("memory_twin_for")
9169 .and_then(|v| v.as_str()),
9170 Some(dst_id.as_base_64().as_str())
9171 );
9172 }
9173
9174 #[test]
9175 fn test_associative_memory_between_memory_areas_creates_synapses() {
9176 use crate::models::cortical_area::CorticalArea;
9177 use feagi_npu_burst_engine::backend::CPUBackend;
9178 use feagi_npu_burst_engine::TracingMutex;
9179 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9180 use feagi_npu_runtime::StdRuntime;
9181 use feagi_structures::genomic::cortical_area::{
9182 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
9183 };
9184 use std::sync::Arc;
9185
9186 let runtime = StdRuntime;
9187 let backend = CPUBackend::new();
9188 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9189 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9190 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9191 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9192
9193 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
9194 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
9195
9196 let mut m1_area = CorticalArea::new(
9197 m1_id,
9198 0,
9199 "Memory M1".to_string(),
9200 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9201 (0, 0, 0).into(),
9202 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9203 )
9204 .unwrap();
9205 m1_area
9206 .properties
9207 .insert("is_mem_type".to_string(), serde_json::json!(true));
9208 m1_area
9209 .properties
9210 .insert("temporal_depth".to_string(), serde_json::json!(1));
9211
9212 let mut m2_area = CorticalArea::new(
9213 m2_id,
9214 0,
9215 "Memory M2".to_string(),
9216 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9217 (0, 0, 0).into(),
9218 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9219 )
9220 .unwrap();
9221 m2_area
9222 .properties
9223 .insert("is_mem_type".to_string(), serde_json::json!(true));
9224 m2_area
9225 .properties
9226 .insert("temporal_depth".to_string(), serde_json::json!(1));
9227
9228 manager.add_cortical_area(m1_area).unwrap();
9229 manager.add_cortical_area(m2_area).unwrap();
9230
9231 manager
9232 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9233 .unwrap();
9234 manager
9235 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9236 .unwrap();
9237
9238 let mapping_data = vec![serde_json::json!({
9239 "morphology_id": "associative_memory",
9240 "morphology_scalar": 1,
9241 "postSynapticCurrent_multiplier": 1.0,
9242 "plasticity_flag": true,
9243 "plasticity_constant": 1,
9244 "ltp_multiplier": 1,
9245 "ltd_multiplier": 1,
9246 "plasticity_window": 5,
9247 })];
9248 manager
9249 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
9250 .unwrap();
9251 let created = manager
9252 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
9253 .unwrap();
9254 assert!(
9255 created > 0,
9256 "Expected associative memory mapping between memory areas to create synapses"
9257 );
9258 let npu_guard = dyn_npu.lock().unwrap();
9259 let assoc_tagged =
9260 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
9261 assert!(
9262 assoc_tagged >= 1,
9263 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
9264 );
9265 }
9266
9267 #[test]
9268 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
9269 use crate::models::cortical_area::CorticalArea;
9270 use feagi_npu_burst_engine::backend::CPUBackend;
9271 use feagi_npu_burst_engine::TracingMutex;
9272 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9273 use feagi_npu_runtime::StdRuntime;
9274 use feagi_structures::genomic::cortical_area::{
9275 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
9276 MemoryCorticalType,
9277 };
9278 use std::sync::Arc;
9279
9280 let runtime = StdRuntime;
9281 let backend = CPUBackend::new();
9282 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9283 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9284 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9285 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9286
9287 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
9288 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
9289
9290 let src_area = CorticalArea::new(
9291 src_id,
9292 0,
9293 "Source Area".to_string(),
9294 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9295 (0, 0, 0).into(),
9296 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9297 )
9298 .unwrap();
9299 let mut mem_area = CorticalArea::new(
9300 mem_id,
9301 0,
9302 "Memory Area".to_string(),
9303 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9304 (0, 0, 0).into(),
9305 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9306 )
9307 .unwrap();
9308 mem_area
9309 .properties
9310 .insert("is_mem_type".to_string(), serde_json::json!(true));
9311 mem_area
9312 .properties
9313 .insert("temporal_depth".to_string(), serde_json::json!(1));
9314
9315 manager.add_cortical_area(src_area).unwrap();
9316 manager.add_cortical_area(mem_area).unwrap();
9317
9318 let twin_id = manager
9319 .build_memory_twin_id(&mem_id, &src_id)
9320 .expect("Failed to build twin id");
9321 let twin_area = CorticalArea::new(
9322 twin_id,
9323 0,
9324 "Source Area_twin".to_string(),
9325 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9326 (0, 0, 0).into(),
9327 CorticalAreaType::Custom(
9328 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9329 ),
9330 )
9331 .unwrap();
9332 manager.add_cortical_area(twin_area).unwrap();
9333
9334 let repaired = manager
9335 .ensure_memory_twin_area(&mem_id, &src_id)
9336 .expect("Failed to repair twin");
9337 assert_eq!(repaired, twin_id);
9338
9339 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
9340 let twin_map = mem_area
9341 .properties
9342 .get("memory_twin_areas")
9343 .and_then(|v| v.as_object())
9344 .expect("memory_twin_areas should be set");
9345 let twin_id_str = twin_map
9346 .get(&src_id.as_base_64())
9347 .and_then(|v| v.as_str())
9348 .expect("Missing twin entry for upstream area");
9349 assert_eq!(twin_id_str, twin_id.as_base_64());
9350
9351 let replay_map = mem_area
9352 .properties
9353 .get("cortical_mapping_dst")
9354 .and_then(|v| v.as_object())
9355 .and_then(|map| map.get(&twin_id.as_base_64()))
9356 .and_then(|v| v.as_array())
9357 .expect("Missing memory replay mapping for twin area");
9358 let uses_replay = replay_map.iter().any(|rule| {
9359 rule.get("morphology_id")
9360 .and_then(|v| v.as_str())
9361 .is_some_and(|id| id == "memory_replay")
9362 });
9363 assert!(uses_replay, "Expected memory_replay mapping for twin area");
9364
9365 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
9366 assert_eq!(
9367 twin_area
9368 .properties
9369 .get("memory_twin_of")
9370 .and_then(|v| v.as_str()),
9371 Some(src_id.as_base_64().as_str())
9372 );
9373 assert_eq!(
9374 twin_area
9375 .properties
9376 .get("memory_twin_for")
9377 .and_then(|v| v.as_str()),
9378 Some(mem_id.as_base_64().as_str())
9379 );
9380 }
9381
9382 #[test]
9383 fn test_rebuild_memory_twin_index_from_replay_without_hash_map() {
9384 use crate::models::cortical_area::CorticalArea;
9385 use feagi_npu_burst_engine::backend::CPUBackend;
9386 use feagi_npu_burst_engine::TracingMutex;
9387 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9388 use feagi_npu_runtime::StdRuntime;
9389 use feagi_structures::genomic::cortical_area::{
9390 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
9391 IOCorticalAreaConfigurationFlag, MemoryCorticalType,
9392 };
9393 use std::sync::Arc;
9394
9395 let runtime = StdRuntime;
9396 let backend = CPUBackend::new();
9397 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9398 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9399 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9400 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9401
9402 let src_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
9403 let mem_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
9404 let twin_id = CorticalID::try_from_bytes(b"ctwin003").unwrap();
9405
9406 let mut src_area = CorticalArea::new(
9407 src_id,
9408 0,
9409 "Source Area".to_string(),
9410 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9411 (0, 0, 0).into(),
9412 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9413 )
9414 .unwrap();
9415 src_area.properties.insert(
9416 "cortical_mapping_dst".to_string(),
9417 serde_json::json!({
9418 mem_id.as_base_64(): [{
9419 "morphology_id": "episodic_memory",
9420 "morphology_scalar": [1, 1, 1],
9421 "postSynapticCurrent_multiplier": 1
9422 }]
9423 }),
9424 );
9425
9426 let mut mem_area = CorticalArea::new(
9427 mem_id,
9428 0,
9429 "Memory Area".to_string(),
9430 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9431 (0, 0, 0).into(),
9432 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9433 )
9434 .unwrap();
9435 mem_area
9436 .properties
9437 .insert("is_mem_type".to_string(), serde_json::json!(true));
9438 mem_area
9439 .properties
9440 .insert("temporal_depth".to_string(), serde_json::json!(1));
9441 mem_area.properties.insert(
9442 "cortical_mapping_dst".to_string(),
9443 serde_json::json!({
9444 twin_id.as_base_64(): [{
9445 "morphology_id": "memory_replay",
9446 "morphology_scalar": [1, 1, 1],
9447 "postSynapticCurrent_multiplier": 1,
9448 "plasticity_flag": false
9449 }]
9450 }),
9451 );
9452
9453 let mut twin_area = CorticalArea::new(
9454 twin_id,
9455 0,
9456 "Source Area_twin".to_string(),
9457 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9458 (0, 0, 0).into(),
9459 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
9460 )
9461 .unwrap();
9462 twin_area.properties.insert(
9463 "memory_twin_of".to_string(),
9464 serde_json::json!(src_id.as_base_64()),
9465 );
9466
9467 manager.add_cortical_area(src_area).unwrap();
9468 manager.add_cortical_area(mem_area).unwrap();
9469 manager.add_cortical_area(twin_area).unwrap();
9470
9471 let diagnostic_before = manager
9472 .diagnose_memory_twin_for_mapping(&src_id, &mem_id)
9473 .expect("diagnostic before rebuild");
9474 assert!(diagnostic_before.twin_expected);
9475 assert!(!diagnostic_before.twin_present);
9476
9477 manager
9478 .rebind_npu_runtime_after_connectome_apply()
9479 .expect("rebind should rebuild twin index");
9480
9481 let diagnostic_after = manager
9482 .diagnose_memory_twin_for_mapping(&src_id, &mem_id)
9483 .expect("diagnostic after rebuild");
9484 assert!(diagnostic_after.twin_present);
9485 assert_eq!(
9486 diagnostic_after.twin_cortical_area.as_deref(),
9487 Some(twin_id.as_base_64().as_str())
9488 );
9489
9490 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
9491 let twin_map = mem_area
9492 .properties
9493 .get("memory_twin_areas")
9494 .and_then(|v| v.as_object())
9495 .expect("memory_twin_areas should be restored");
9496 assert_eq!(
9497 twin_map.get(&src_id.as_base_64()).and_then(|v| v.as_str()),
9498 Some(twin_id.as_base_64().as_str())
9499 );
9500
9501 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
9502 assert_eq!(
9503 twin_area
9504 .properties
9505 .get("memory_twin_for")
9506 .and_then(|v| v.as_str()),
9507 Some(mem_id.as_base_64().as_str())
9508 );
9509
9510 let memory_idx = manager.get_cortical_idx(&mem_id).expect("memory idx");
9511 let upstream_idx = manager.get_cortical_idx(&src_id).expect("src idx");
9512 let twin_idx = manager.get_cortical_idx(&twin_id).expect("twin idx");
9513 let npu = dyn_npu.lock().unwrap();
9514 let (bound_twin, _) = npu
9515 .get_memory_twin_mapping(memory_idx, upstream_idx)
9516 .expect("NPU twin route restored");
9517 assert_eq!(bound_twin, twin_idx);
9518 }
9519
9520 #[test]
9521 fn test_episodic_upstream_reads_mapping_when_cache_empty() {
9522 use crate::models::cortical_area::CorticalArea;
9523 use feagi_structures::genomic::cortical_area::{
9524 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
9525 MemoryCorticalType,
9526 };
9527
9528 let mut manager = ConnectomeManager::new_for_testing();
9529 let src_id = CorticalID::try_from_bytes(b"csrc0004").unwrap();
9530 let mem_id = CorticalID::try_from_bytes(b"mmem0004").unwrap();
9531
9532 let mut src_area = CorticalArea::new(
9533 src_id,
9534 0,
9535 "pattern in".to_string(),
9536 CorticalAreaDimensions::new(10, 1, 1).unwrap(),
9537 (0, 0, 0).into(),
9538 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
9539 )
9540 .unwrap();
9541 src_area.properties.insert(
9542 "cortical_mapping_dst".to_string(),
9543 serde_json::json!({
9544 mem_id.as_base_64(): [{
9545 "morphology_id": "episodic_memory",
9546 "postSynapticCurrent_multiplier": 1
9547 }]
9548 }),
9549 );
9550 let mut mem_area = CorticalArea::new(
9551 mem_id,
9552 0,
9553 "pattern mem".to_string(),
9554 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9555 (1, 0, 0).into(),
9556 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9557 )
9558 .unwrap();
9559 mem_area
9560 .properties
9561 .insert("is_mem_type".to_string(), serde_json::json!(true));
9562 mem_area
9563 .properties
9564 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
9565
9566 let src_idx = manager.add_cortical_area(src_area).unwrap();
9567 manager.add_cortical_area(mem_area).unwrap();
9568
9569 assert!(
9570 manager.get_upstream_cortical_areas(&mem_id).is_empty(),
9571 "cache stays empty until refresh"
9572 );
9573 assert_eq!(
9574 manager.get_episodic_memory_upstream_cortical_areas(&mem_id),
9575 vec![src_idx],
9576 "episodic upstream must come from the mapping rule, not the cache"
9577 );
9578
9579 manager.refresh_all_upstream_cortical_areas_from_mappings();
9580 assert_eq!(manager.get_upstream_cortical_areas(&mem_id), vec![src_idx]);
9581 }
9582
9583 #[test]
9584 fn test_memory_twin_inherits_upstream_parent_region() {
9585 use crate::models::cortical_area::CorticalArea;
9586 use crate::models::BrainRegion;
9587 use feagi_npu_burst_engine::backend::CPUBackend;
9588 use feagi_npu_burst_engine::TracingMutex;
9589 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9590 use feagi_npu_runtime::StdRuntime;
9591 use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
9592 use feagi_structures::genomic::cortical_area::{
9593 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
9594 MemoryCorticalType,
9595 };
9596 use std::sync::Arc;
9597
9598 let runtime = StdRuntime;
9599 let backend = CPUBackend::new();
9600 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9601 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9602 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9603 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9604 let src_region_id = RegionID::new();
9605 let src_region_id_str = src_region_id.to_string();
9606 manager
9607 .add_brain_region(
9608 BrainRegion::new(
9609 src_region_id,
9610 "Src Region".to_string(),
9611 RegionType::Undefined,
9612 )
9613 .unwrap(),
9614 None,
9615 )
9616 .unwrap();
9617 let mem_region_id = RegionID::new();
9618 let mem_region_id_str = mem_region_id.to_string();
9619 manager
9620 .add_brain_region(
9621 BrainRegion::new(
9622 mem_region_id,
9623 "Mem Region".to_string(),
9624 RegionType::Undefined,
9625 )
9626 .unwrap(),
9627 None,
9628 )
9629 .unwrap();
9630
9631 let src_id = CorticalID::try_from_bytes(b"csrc1001").unwrap();
9632 let mem_id = CorticalID::try_from_bytes(b"mmem1001").unwrap();
9633
9634 let mut src_area = CorticalArea::new(
9635 src_id,
9636 0,
9637 "Origin".to_string(),
9638 CorticalAreaDimensions::new(4, 4, 1).unwrap(),
9639 (10, 20, 0).into(),
9640 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9641 )
9642 .unwrap();
9643 src_area.properties.insert(
9644 "parent_region_id".to_string(),
9645 serde_json::json!(src_region_id_str.clone()),
9646 );
9647
9648 let mut mem_area = CorticalArea::new(
9649 mem_id,
9650 0,
9651 "Memory".to_string(),
9652 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9653 (200, 300, 0).into(),
9654 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9655 )
9656 .unwrap();
9657 mem_area
9658 .properties
9659 .insert("is_mem_type".to_string(), serde_json::json!(true));
9660 mem_area
9661 .properties
9662 .insert("temporal_depth".to_string(), serde_json::json!(1));
9663 mem_area.properties.insert(
9664 "parent_region_id".to_string(),
9665 serde_json::json!(mem_region_id_str.clone()),
9666 );
9667
9668 manager.add_cortical_area(src_area).unwrap();
9669 manager.add_cortical_area(mem_area).unwrap();
9670
9671 let mapping_data = vec![serde_json::json!({
9672 "morphology_id": "episodic_memory",
9673 "morphology_scalar": 1,
9674 "postSynapticCurrent_multiplier": 1.0,
9675 })];
9676 manager
9677 .update_cortical_mapping(&src_id, &mem_id, mapping_data)
9678 .unwrap();
9679 manager
9680 .regenerate_synapses_for_mapping(&src_id, &mem_id)
9681 .unwrap();
9682
9683 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
9684 let twin_map = memory_area
9685 .properties
9686 .get("memory_twin_areas")
9687 .and_then(|v| v.as_object())
9688 .expect("memory_twin_areas should be set");
9689 let twin_id = CorticalID::try_from_base_64(
9690 twin_map
9691 .get(&src_id.as_base_64())
9692 .and_then(|v| v.as_str())
9693 .expect("missing twin for source"),
9694 )
9695 .unwrap();
9696 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
9697
9698 assert_eq!(
9699 twin_area
9700 .properties
9701 .get("parent_region_id")
9702 .and_then(|v| v.as_str()),
9703 Some(src_region_id_str.as_str()),
9704 "Twin should inherit upstream/source parent region"
9705 );
9706 assert_ne!(
9707 twin_area
9708 .properties
9709 .get("parent_region_id")
9710 .and_then(|v| v.as_str()),
9711 Some(mem_region_id_str.as_str()),
9712 "Twin must not inherit memory area's parent region"
9713 );
9714 }
9715
9716 #[test]
9717 fn test_memory_twin_diagnostic_explains_memory_upstream_blocker() {
9718 use crate::models::cortical_area::CorticalArea;
9719 use feagi_npu_burst_engine::backend::CPUBackend;
9720 use feagi_npu_burst_engine::TracingMutex;
9721 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9722 use feagi_npu_runtime::StdRuntime;
9723 use feagi_structures::genomic::cortical_area::{
9724 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
9725 };
9726 use std::sync::Arc;
9727
9728 let runtime = StdRuntime;
9729 let backend = CPUBackend::new();
9730 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9731 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9732 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9733 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9734
9735 let m1_id = CorticalID::try_from_bytes(b"mmem2001").unwrap();
9736 let m2_id = CorticalID::try_from_bytes(b"mmem2002").unwrap();
9737
9738 let mut m1_area = CorticalArea::new(
9739 m1_id,
9740 0,
9741 "Memory A".to_string(),
9742 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9743 (0, 0, 0).into(),
9744 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9745 )
9746 .unwrap();
9747 m1_area
9748 .properties
9749 .insert("is_mem_type".to_string(), serde_json::json!(true));
9750 m1_area
9751 .properties
9752 .insert("temporal_depth".to_string(), serde_json::json!(1));
9753
9754 let mut m2_area = CorticalArea::new(
9755 m2_id,
9756 0,
9757 "Memory B".to_string(),
9758 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9759 (0, 0, 0).into(),
9760 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9761 )
9762 .unwrap();
9763 m2_area
9764 .properties
9765 .insert("is_mem_type".to_string(), serde_json::json!(true));
9766 m2_area
9767 .properties
9768 .insert("temporal_depth".to_string(), serde_json::json!(1));
9769
9770 manager.add_cortical_area(m1_area).unwrap();
9771 manager.add_cortical_area(m2_area).unwrap();
9772
9773 let mapping_data = vec![serde_json::json!({
9774 "morphology_id": "episodic_memory",
9775 "morphology_scalar": 1,
9776 "postSynapticCurrent_multiplier": 1.0,
9777 })];
9778 manager
9779 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
9780 .unwrap();
9781 manager
9782 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
9783 .unwrap();
9784
9785 let diagnostic = manager
9786 .diagnose_memory_twin_for_mapping(&m1_id, &m2_id)
9787 .expect("diagnostic should succeed");
9788 assert!(diagnostic.mapping_exists);
9789 assert_eq!(diagnostic.episodic_rule_count, 1);
9790 assert!(!diagnostic.twin_expected);
9791 assert!(!diagnostic.twin_present);
9792 assert_eq!(
9793 diagnostic.reason.as_deref(),
9794 Some("upstream_is_memory_area_twin_not_supported")
9795 );
9796 }
9797
9798 fn build_max_weight_test_manager() -> (
9803 ConnectomeManager,
9804 CorticalID,
9805 CorticalID,
9806 CorticalID,
9807 CorticalID,
9808 ) {
9809 use feagi_npu_burst_engine::backend::CPUBackend;
9810 use feagi_npu_burst_engine::TracingMutex;
9811 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9812 use feagi_npu_runtime::StdRuntime;
9813 use feagi_structures::genomic::cortical_area::{
9814 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9815 };
9816
9817 let runtime = StdRuntime;
9818 let backend = CPUBackend::new();
9819 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("npu");
9820 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9821 let mut mgr = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
9822 feagi_evolutionary::templates::add_core_morphologies(&mut mgr.morphology_registry);
9826
9827 let src = CorticalID::try_from_bytes(b"cstmwsrc").unwrap();
9828 let dst = CorticalID::try_from_bytes(b"cstmwdst").unwrap();
9829 let reward = CorticalID::try_from_bytes(b"cstmwrwd").unwrap();
9830 let pain = CorticalID::try_from_bytes(b"cstmwpan").unwrap();
9831
9832 for (id, label, kind) in [
9833 (
9834 src,
9835 "src",
9836 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9837 ),
9838 (
9839 dst,
9840 "dst",
9841 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9842 ),
9843 (
9844 reward,
9845 "reward",
9846 CorticalAreaType::Custom(
9847 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9848 ),
9849 ),
9850 (
9851 pain,
9852 "pain",
9853 CorticalAreaType::Custom(
9854 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9855 ),
9856 ),
9857 ] {
9858 mgr.add_cortical_area(
9859 CorticalArea::new(
9860 id,
9861 0,
9862 label.to_string(),
9863 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9864 (0, 0, 0).into(),
9865 kind,
9866 )
9867 .unwrap(),
9868 )
9869 .unwrap();
9870 mgr.add_neuron(&id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9871 .unwrap();
9872 }
9873 (mgr, src, dst, reward, pain)
9874 }
9875
9876 fn write_and_regenerate_mapping(
9881 mgr: &mut ConnectomeManager,
9882 src: &CorticalID,
9883 dst: &CorticalID,
9884 rule: serde_json::Value,
9885 ) -> BduResult<usize> {
9886 mgr.update_cortical_mapping(src, dst, vec![rule])?;
9887 mgr.regenerate_synapses_for_mapping(src, dst)
9888 }
9889
9890 #[test]
9894 fn test_max_weight_finite_positive_accepted_on_rstdp_rule() {
9895 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9896
9897 let result = write_and_regenerate_mapping(
9898 &mut mgr,
9899 &src,
9900 &dst,
9901 serde_json::json!({
9902 "morphology_id": "block_to_block",
9903 "morphology_scalar": [1, 1, 1],
9904 "postSynapticCurrent_multiplier": 1,
9905 "plasticity_flag": true,
9906 "plasticity_constant": 1,
9907 "ltp_multiplier": 1,
9908 "ltd_multiplier": 1,
9909 "plasticity_window": 10,
9910 "synaptic_delay_bursts": 1,
9911 "plasticity_mode": "rstdp",
9912 "eligibility_decay_bursts": 50,
9913 "reward_source_area": reward.as_base_64(),
9914 "punishment_source_area": pain.as_base_64(),
9915 "max_weight": 12.5,
9916 }),
9917 );
9918 assert!(
9919 result.is_ok(),
9920 "valid max_weight=12.5 must be accepted, got {:?}",
9921 result
9922 );
9923 }
9924
9925 #[test]
9930 fn test_max_weight_invalid_values_rejected() {
9931 for bad in &[
9932 serde_json::json!(0.0),
9933 serde_json::json!(-1.5),
9934 serde_json::json!("not_a_number"),
9935 ] {
9936 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9937 let result = write_and_regenerate_mapping(
9938 &mut mgr,
9939 &src,
9940 &dst,
9941 serde_json::json!({
9942 "morphology_id": "block_to_block",
9943 "morphology_scalar": [1, 1, 1],
9944 "postSynapticCurrent_multiplier": 1,
9945 "plasticity_flag": true,
9946 "plasticity_constant": 1,
9947 "ltp_multiplier": 1,
9948 "ltd_multiplier": 1,
9949 "plasticity_window": 10,
9950 "synaptic_delay_bursts": 1,
9951 "plasticity_mode": "rstdp",
9952 "eligibility_decay_bursts": 50,
9953 "reward_source_area": reward.as_base_64(),
9954 "punishment_source_area": pain.as_base_64(),
9955 "max_weight": bad,
9956 }),
9957 );
9958 assert!(
9959 result.is_err(),
9960 "max_weight={:?} should have been rejected, got {:?}",
9961 bad,
9962 result
9963 );
9964 }
9965 }
9966
9967 #[test]
9970 fn test_ltp_ltd_multiplier_out_of_i8_range_rejected() {
9971 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9972 let result = write_and_regenerate_mapping(
9973 &mut mgr,
9974 &src,
9975 &dst,
9976 serde_json::json!({
9977 "morphology_id": "block_to_block",
9978 "morphology_scalar": [1, 1, 1],
9979 "postSynapticCurrent_multiplier": 1,
9980 "plasticity_flag": true,
9981 "plasticity_constant": 1,
9982 "ltp_multiplier": 200,
9983 "ltd_multiplier": 1,
9984 "plasticity_window": 10,
9985 "synaptic_delay_bursts": 1,
9986 "plasticity_mode": "rstdp",
9987 "eligibility_decay_bursts": 50,
9988 "reward_source_area": reward.as_base_64(),
9989 "punishment_source_area": pain.as_base_64(),
9990 }),
9991 );
9992 assert!(
9993 result.is_err(),
9994 "ltp_multiplier=200 must be rejected (i8 range); got {:?}",
9995 result
9996 );
9997 }
9998
9999 #[test]
10002 fn test_max_weight_rejected_when_plasticity_off() {
10003 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
10004
10005 let result = write_and_regenerate_mapping(
10006 &mut mgr,
10007 &src,
10008 &dst,
10009 serde_json::json!({
10010 "morphology_id": "block_to_block",
10011 "morphology_scalar": [1, 1, 1],
10012 "postSynapticCurrent_multiplier": 1,
10013 "plasticity_flag": true,
10018 "plasticity_constant": 0,
10019 "ltp_multiplier": 0,
10020 "ltd_multiplier": 0,
10021 "plasticity_window": 0,
10022 "synaptic_delay_bursts": 1,
10023 "plasticity_mode": "off",
10024 "max_weight": 10.0,
10025 }),
10026 );
10027 assert!(
10028 result.is_err(),
10029 "max_weight on off-mode rule must be rejected; got {:?}",
10030 result
10031 );
10032 }
10033
10034 #[test]
10035 fn test_plasticity_eta_rejected_when_plasticity_off() {
10036 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
10037
10038 let result = write_and_regenerate_mapping(
10039 &mut mgr,
10040 &src,
10041 &dst,
10042 serde_json::json!({
10043 "morphology_id": "block_to_block",
10044 "morphology_scalar": [1, 1, 1],
10045 "postSynapticCurrent_multiplier": 1,
10046 "plasticity_flag": true,
10047 "plasticity_constant": 0,
10048 "ltp_multiplier": 0,
10049 "ltd_multiplier": 0,
10050 "plasticity_window": 0,
10051 "synaptic_delay_bursts": 1,
10052 "plasticity_mode": "off",
10053 "plasticity_eta": 0.5,
10054 }),
10055 );
10056 assert!(
10057 result.is_err(),
10058 "plasticity_eta on off-mode rule must be rejected; got {:?}",
10059 result
10060 );
10061 }
10062
10063 #[test]
10064 fn test_plasticity_eta_non_positive_rejected() {
10065 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
10066
10067 let result = write_and_regenerate_mapping(
10068 &mut mgr,
10069 &src,
10070 &dst,
10071 serde_json::json!({
10072 "morphology_id": "block_to_block",
10073 "morphology_scalar": [1, 1, 1],
10074 "postSynapticCurrent_multiplier": 1,
10075 "plasticity_flag": true,
10076 "plasticity_constant": 1,
10077 "ltp_multiplier": 1,
10078 "ltd_multiplier": 1,
10079 "plasticity_window": 10,
10080 "synaptic_delay_bursts": 1,
10081 "plasticity_mode": "rstdp",
10082 "eligibility_decay_bursts": 50,
10083 "reward_source_area": reward.as_base_64(),
10084 "punishment_source_area": pain.as_base_64(),
10085 "plasticity_eta": 0.0,
10086 }),
10087 );
10088 assert!(
10089 result.is_err(),
10090 "plasticity_eta=0 must be rejected; got {:?}",
10091 result
10092 );
10093 }
10094}