1use once_cell::sync::Lazy;
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
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_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
463 let cortical_hash = if properties_changed {
464 Some(self.compute_cortical_areas_hash())
465 } else {
466 None
467 };
468 let geometry_hash = if geometry_changed {
469 Some(self.compute_brain_geometry_hash())
470 } else {
471 None
472 };
473 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
474 }
475
476 fn compute_brain_regions_hash(&self) -> u64 {
478 let mut hasher = Xxh64::new(DATA_HASH_SEED);
479 let mut region_ids: Vec<String> = self
480 .brain_regions
481 .get_all_region_ids()
482 .into_iter()
483 .cloned()
484 .collect();
485 region_ids.sort();
486
487 for region_id in region_ids {
488 let Some(region) = self.brain_regions.get_region(®ion_id) else {
489 continue;
490 };
491 Self::hash_str(&mut hasher, ®ion_id);
492 Self::hash_str(&mut hasher, ®ion.name);
493 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
494 let parent_id = self.brain_regions.get_parent(®ion_id);
495 match parent_id {
496 Some(parent) => Self::hash_str(&mut hasher, parent),
497 None => Self::hash_str(&mut hasher, "null"),
498 }
499
500 let mut cortical_ids: Vec<String> = region
501 .cortical_areas
502 .iter()
503 .map(|id| id.as_base_64())
504 .collect();
505 cortical_ids.sort();
506 for cortical_id in cortical_ids {
507 Self::hash_str(&mut hasher, &cortical_id);
508 }
509
510 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
511 }
512
513 hasher.finish() & HASH_SAFE_MASK
514 }
515
516 fn compute_cortical_areas_hash(&self) -> u64 {
518 let mut hasher = Xxh64::new(DATA_HASH_SEED);
519 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
520 areas.sort_by_key(|area| area.cortical_id.as_base_64());
521
522 for area in areas {
523 let cortical_id = area.cortical_id.as_base_64();
524 Self::hash_str(&mut hasher, &cortical_id);
525 hasher.write_u32(area.cortical_idx);
526 Self::hash_str(&mut hasher, &area.name);
527 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
528
529 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
530 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
531 }
532
533 hasher.finish() & HASH_SAFE_MASK
534 }
535
536 fn compute_brain_geometry_hash(&self) -> u64 {
538 let mut hasher = Xxh64::new(DATA_HASH_SEED);
539 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
540 areas.sort_by_key(|area| area.cortical_id.as_base_64());
541
542 for area in areas {
543 let cortical_id = area.cortical_id.as_base_64();
544 Self::hash_str(&mut hasher, &cortical_id);
545
546 Self::hash_i32(&mut hasher, area.position.x);
547 Self::hash_i32(&mut hasher, area.position.y);
548 Self::hash_i32(&mut hasher, area.position.z);
549
550 Self::hash_u32(&mut hasher, area.dimensions.width);
551 Self::hash_u32(&mut hasher, area.dimensions.height);
552 Self::hash_u32(&mut hasher, area.dimensions.depth);
553
554 let coord_2d = area
555 .properties
556 .get("coordinate_2d")
557 .or_else(|| area.properties.get("coordinates_2d"));
558 match coord_2d {
559 Some(value) => Self::hash_json_value(&mut hasher, value),
560 None => Self::hash_str(&mut hasher, "null"),
561 }
562 }
563
564 hasher.finish() & HASH_SAFE_MASK
565 }
566
567 fn compute_morphologies_hash(&self) -> u64 {
569 let mut hasher = Xxh64::new(DATA_HASH_SEED);
570 let mut morphology_ids = self.morphology_registry.morphology_ids();
571 morphology_ids.sort();
572
573 for morphology_id in morphology_ids {
574 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
575 Self::hash_str(&mut hasher, &morphology_id);
576 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
577 Self::hash_str(&mut hasher, &morphology.class);
578 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
579 Self::hash_json_value(&mut hasher, &value);
580 }
581 }
582 }
583
584 hasher.finish() & HASH_SAFE_MASK
585 }
586
587 fn compute_cortical_mappings_hash(&self) -> u64 {
589 let mut hasher = Xxh64::new(DATA_HASH_SEED);
590 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
591 areas.sort_by_key(|area| area.cortical_id.as_base_64());
592
593 for area in areas {
594 let cortical_id = area.cortical_id.as_base_64();
595 Self::hash_str(&mut hasher, &cortical_id);
596 if let Some(serde_json::Value::Object(map)) =
597 area.properties.get("cortical_mapping_dst")
598 {
599 let mut dest_ids: Vec<&String> = map.keys().collect();
600 dest_ids.sort();
601 for dest_id in dest_ids {
602 Self::hash_str(&mut hasher, dest_id);
603 if let Some(value) = map.get(dest_id) {
604 Self::hash_json_value(&mut hasher, value);
605 }
606 }
607 } else {
608 Self::hash_str(&mut hasher, "null");
609 }
610 }
611
612 hasher.finish() & HASH_SAFE_MASK
613 }
614
615 fn hash_str(hasher: &mut Xxh64, value: &str) {
617 hasher.write(value.as_bytes());
618 hasher.write_u8(0);
619 }
620
621 fn hash_i32(hasher: &mut Xxh64, value: i32) {
623 hasher.write(&value.to_le_bytes());
624 }
625
626 fn hash_u32(hasher: &mut Xxh64, value: u32) {
628 hasher.write(&value.to_le_bytes());
629 }
630
631 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
633 match value {
634 serde_json::Value::Null => {
635 hasher.write_u8(0);
636 }
637 serde_json::Value::Bool(val) => {
638 hasher.write_u8(1);
639 hasher.write_u8(*val as u8);
640 }
641 serde_json::Value::Number(num) => {
642 hasher.write_u8(2);
643 Self::hash_str(hasher, &num.to_string());
644 }
645 serde_json::Value::String(val) => {
646 hasher.write_u8(3);
647 Self::hash_str(hasher, val);
648 }
649 serde_json::Value::Array(items) => {
650 hasher.write_u8(4);
651 for item in items {
652 Self::hash_json_value(hasher, item);
653 }
654 }
655 serde_json::Value::Object(map) => {
656 hasher.write_u8(5);
657 let mut keys: Vec<&String> = map.keys().collect();
658 keys.sort();
659 for key in keys {
660 Self::hash_str(hasher, key);
661 if let Some(val) = map.get(key) {
662 Self::hash_json_value(hasher, val);
663 }
664 }
665 }
666 }
667 }
668
669 fn hash_properties_filtered(
671 hasher: &mut Xxh64,
672 properties: &HashMap<String, serde_json::Value>,
673 excluded_keys: &[&str],
674 ) {
675 let mut keys: Vec<&String> = properties.keys().collect();
676 keys.sort();
677 for key in keys {
678 if excluded_keys.contains(&key.as_str()) {
679 continue;
680 }
681 Self::hash_str(hasher, key);
682 if let Some(value) = properties.get(key) {
683 Self::hash_json_value(hasher, value);
684 }
685 }
686 }
687
688 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
709 if self.cortical_areas.contains_key(&area.cortical_id) {
711 return Err(BduError::InvalidArea(format!(
712 "Cortical area {} already exists",
713 area.cortical_id
714 )));
715 }
716
717 use feagi_structures::genomic::cortical_area::CoreCorticalType;
720
721 let death_id = CoreCorticalType::Death.to_cortical_id();
722 let power_id = CoreCorticalType::Power.to_cortical_id();
723 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
724 let pain_id = CoreCorticalType::Pain.to_cortical_id();
725 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
726 let fear_id = CoreCorticalType::Fear.to_cortical_id();
727 let hope_id = CoreCorticalType::Hope.to_cortical_id();
728
729 let is_death_area = area.cortical_id == death_id;
730 let is_power_area = area.cortical_id == power_id;
731 let is_fatigue_area = area.cortical_id == fatigue_id;
732 let is_pain_area = area.cortical_id == pain_id;
733 let is_pleasure_area = area.cortical_id == pleasure_id;
734 let is_fear_area = area.cortical_id == fear_id;
735 let is_hope_area = area.cortical_id == hope_id;
736
737 if is_death_area {
738 trace!(
739 target: "feagi-bdu",
740 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
741 area.cortical_id
742 );
743 area.cortical_idx = 0;
744 } else if is_power_area {
745 trace!(
746 target: "feagi-bdu",
747 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
748 area.cortical_id
749 );
750 area.cortical_idx = 1;
751 } else if is_fatigue_area {
752 trace!(
753 target: "feagi-bdu",
754 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
755 area.cortical_id
756 );
757 area.cortical_idx = 2;
758 } else if is_pain_area {
759 trace!(
760 target: "feagi-bdu",
761 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
762 area.cortical_id
763 );
764 area.cortical_idx = 3;
765 } else if is_pleasure_area {
766 trace!(
767 target: "feagi-bdu",
768 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
769 area.cortical_id
770 );
771 area.cortical_idx = 4;
772 } else if is_fear_area {
773 trace!(
774 target: "feagi-bdu",
775 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
776 area.cortical_id
777 );
778 area.cortical_idx = 5;
779 } else if is_hope_area {
780 trace!(
781 target: "feagi-bdu",
782 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
783 area.cortical_id
784 );
785 area.cortical_idx = 6;
786 } else {
787 if area.cortical_idx == 0 {
789 area.cortical_idx = self.next_cortical_idx;
790 self.next_cortical_idx += 1;
791 trace!(
792 target: "feagi-bdu",
793 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
794 area.cortical_idx,
795 area.cortical_id.as_base_64()
796 );
797 } else {
798 if area.cortical_idx <= 6 {
800 warn!(
801 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
802 area.cortical_id, area.cortical_idx);
803 area.cortical_idx = self.next_cortical_idx;
804 self.next_cortical_idx += 1;
805 info!(
806 " Reassigned '{}' to cortical_idx={}",
807 area.cortical_id, area.cortical_idx
808 );
809 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
810 return Err(BduError::InvalidArea(format!(
811 "Cortical index {} is already in use",
812 area.cortical_idx
813 )));
814 }
815
816 if area.cortical_idx >= self.next_cortical_idx {
818 self.next_cortical_idx = area.cortical_idx + 1;
819 }
820 }
821 }
822
823 let cortical_id = area.cortical_id;
824 let cortical_idx = area.cortical_idx;
825
826 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
828 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
829
830 area.properties
832 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
833
834 let parent_region_id = area
843 .properties
844 .get("parent_region_id")
845 .and_then(|v| v.as_str())
846 .map(|s| s.to_string());
847
848 self.cortical_areas.insert(cortical_id, area);
850
851 if let Some(region_id) = parent_region_id {
853 let region = self
854 .brain_regions
855 .get_region_mut(®ion_id)
856 .ok_or_else(|| {
857 BduError::InvalidArea(format!(
858 "Unknown parent_region_id '{}' for cortical area {}",
859 region_id,
860 cortical_id.as_base_64()
861 ))
862 })?;
863 region.add_area(cortical_id);
864 }
865
866 {
869 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
870 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
871 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
872 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
873 }
874 let state_manager = StateManager::instance();
876 let state_manager = state_manager.read();
877 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
878
879 if let Some(ref npu) = self.npu {
883 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
884 if let Ok(mut npu_lock) = npu.lock() {
885 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
886 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
887 trace!(
888 target: "feagi-bdu",
889 "Registered cortical area idx={} -> '{}' in NPU",
890 cortical_idx,
891 cortical_id.as_base_64()
892 );
893 }
894 }
895
896 self.sync_cortical_area_flags_to_npu()?;
898
899 self.initialized = true;
900
901 self.refresh_cortical_area_hashes(true, true);
902 self.refresh_brain_regions_hash();
903
904 Ok(cortical_idx)
905 }
906
907 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
922 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
923 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
924 })?;
925
926 self.cortical_id_to_idx.remove(cortical_id);
928 self.cortical_idx_to_id.remove(&area.cortical_idx);
929
930 self.refresh_cortical_area_hashes(true, true);
931 Ok(())
932 }
933
934 pub fn rename_cortical_area_id(
938 &mut self,
939 old_id: &CorticalID,
940 new_id: CorticalID,
941 new_cortical_type: CorticalAreaType,
942 ) -> BduResult<()> {
943 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
944 }
945
946 pub fn rename_cortical_area_id_with_options(
948 &mut self,
949 old_id: &CorticalID,
950 new_id: CorticalID,
951 new_cortical_type: CorticalAreaType,
952 update_npu_registry: bool,
953 ) -> BduResult<()> {
954 if !self.cortical_areas.contains_key(old_id) {
955 return Err(BduError::InvalidArea(format!(
956 "Cortical area {} does not exist",
957 old_id
958 )));
959 }
960 if self.cortical_areas.contains_key(&new_id) {
961 return Err(BduError::InvalidArea(format!(
962 "Cortical area {} already exists",
963 new_id
964 )));
965 }
966
967 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
968 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
969 })?;
970 let cortical_idx = area.cortical_idx;
971 area.cortical_id = new_id;
972 area.cortical_type = new_cortical_type;
973
974 self.cortical_areas.insert(new_id, area);
975 self.cortical_id_to_idx.remove(old_id);
976 self.cortical_id_to_idx.insert(new_id, cortical_idx);
977 self.cortical_idx_to_id.insert(cortical_idx, new_id);
978
979 {
981 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
982 if let Some(value) = neuron_cache.remove(old_id) {
983 neuron_cache.insert(new_id, value);
984 }
985 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
986 if let Some(value) = synapse_cache.remove(old_id) {
987 synapse_cache.insert(new_id, value);
988 }
989 }
990
991 self.brain_regions.rename_cortical_area_id(old_id, new_id);
993
994 let old_id_str = old_id.as_base_64();
996 let new_id_str = new_id.as_base_64();
997 for area in self.cortical_areas.values_mut() {
998 if let Some(mapping) = area
999 .properties
1000 .get_mut("cortical_mapping_dst")
1001 .and_then(|v| v.as_object_mut())
1002 {
1003 if let Some(value) = mapping.remove(&old_id_str) {
1004 mapping.insert(new_id_str.clone(), value);
1005 }
1006 }
1007 }
1008
1009 if update_npu_registry {
1011 if let Some(ref npu) = self.npu {
1012 if let Ok(mut npu_lock) = npu.lock() {
1013 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
1014 }
1015 }
1016 }
1017
1018 self.refresh_cortical_area_hashes(true, true);
1019 self.refresh_brain_regions_hash();
1020 self.refresh_cortical_mappings_hash();
1021
1022 Ok(())
1023 }
1024
1025 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1027 self.cortical_areas.get(cortical_id)
1028 }
1029
1030 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1032 self.cortical_areas.get_mut(cortical_id)
1033 }
1034
1035 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1037 self.cortical_id_to_idx.get(cortical_id).copied()
1038 }
1039
1040 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1052 self.brain_regions.find_region_containing_area(cortical_id)
1053 }
1054
1055 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1058 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1059 return false;
1060 };
1061 let Some(src_area) = self.cortical_areas.get(area) else {
1062 return false;
1063 };
1064 let Some(dst_obj) = src_area
1065 .properties
1066 .get("cortical_mapping_dst")
1067 .and_then(|v| v.as_object())
1068 else {
1069 return false;
1070 };
1071 for dst_key in dst_obj.keys() {
1072 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1073 continue;
1074 };
1075 match self.brain_regions.find_region_containing_area(&dst_id) {
1076 None => return true,
1077 Some(rid) if rid != my_region => return true,
1078 _ => {}
1079 }
1080 }
1081 false
1082 }
1083
1084 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1086 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1087 return false;
1088 };
1089 let my_b64 = area.as_base_64();
1090 for (src_id, src_area) in &self.cortical_areas {
1091 if src_id == area {
1092 continue;
1093 }
1094 let Some(dst_map) = src_area
1095 .properties
1096 .get("cortical_mapping_dst")
1097 .and_then(|v| v.as_object())
1098 else {
1099 continue;
1100 };
1101 if !dst_map.contains_key(&my_b64) {
1102 continue;
1103 }
1104 match self.brain_regions.find_region_containing_area(src_id) {
1105 None => return true,
1106 Some(rid) if rid != my_region => return true,
1107 _ => {}
1108 }
1109 }
1110 false
1111 }
1112
1113 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1124 use std::collections::HashSet;
1125
1126 let region_ids: Vec<String> = self
1127 .brain_regions
1128 .get_all_region_ids()
1129 .into_iter()
1130 .cloned()
1131 .collect();
1132
1133 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1134 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1135
1136 for rid in ®ion_ids {
1138 inputs_by_region.insert(rid.clone(), HashSet::new());
1139 outputs_by_region.insert(rid.clone(), HashSet::new());
1140 }
1141
1142 for (src_id, src_area) in &self.cortical_areas {
1143 let Some(dstmap) = src_area
1144 .properties
1145 .get("cortical_mapping_dst")
1146 .and_then(|v| v.as_object())
1147 else {
1148 continue;
1149 };
1150
1151 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1152 debug!(
1153 target: "feagi-bdu",
1154 "Skipping region IO for source area {} (not in any region)",
1155 src_id.as_base_64()
1156 );
1157 continue;
1158 };
1159
1160 for dst_id_str in dstmap.keys() {
1161 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1162 BduError::InvalidArea(format!(
1163 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1164 dst_id_str,
1165 src_id.as_base_64(),
1166 e
1167 ))
1168 })?;
1169
1170 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1171 else {
1172 warn!(
1173 target: "feagi-bdu",
1174 "Skipping region IO for destination area {} (not in any region)",
1175 dst_id.as_base_64()
1176 );
1177 continue;
1178 };
1179
1180 if src_region_id == dst_region_id {
1181 continue;
1182 }
1183
1184 outputs_by_region
1185 .entry(src_region_id.clone())
1186 .or_default()
1187 .insert(src_id.as_base_64());
1188 inputs_by_region
1189 .entry(dst_region_id.clone())
1190 .or_default()
1191 .insert(dst_id.as_base_64());
1192 }
1193 }
1194
1195 for rid in ®ion_ids {
1197 let Some(region) = self.brain_regions.get_region(rid) else {
1198 continue;
1199 };
1200 let in_ids = crate::region_io_designation::parse_designated_id_list(
1201 region
1202 .properties
1203 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1204 )?;
1205 let out_ids = crate::region_io_designation::parse_designated_id_list(
1206 region
1207 .properties
1208 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1209 )?;
1210 for id in in_ids {
1211 inputs_by_region
1212 .entry(rid.clone())
1213 .or_default()
1214 .insert(id.as_base_64());
1215 }
1216 for id in out_ids {
1217 outputs_by_region
1218 .entry(rid.clone())
1219 .or_default()
1220 .insert(id.as_base_64());
1221 }
1222 }
1223
1224 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1225 for rid in region_ids {
1226 let mut inputs: Vec<String> = inputs_by_region
1227 .remove(&rid)
1228 .unwrap_or_default()
1229 .into_iter()
1230 .collect();
1231 let mut outputs: Vec<String> = outputs_by_region
1232 .remove(&rid)
1233 .unwrap_or_default()
1234 .into_iter()
1235 .collect();
1236
1237 inputs.sort();
1238 outputs.sort();
1239
1240 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1241 BduError::InvalidArea(format!(
1242 "Unable to recompute region IO: region '{}' not found in hierarchy",
1243 rid
1244 ))
1245 })?;
1246
1247 if inputs.is_empty() {
1248 region.properties.remove("inputs");
1249 } else {
1250 region
1251 .properties
1252 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1253 }
1254
1255 if outputs.is_empty() {
1256 region.properties.remove("outputs");
1257 } else {
1258 region
1259 .properties
1260 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1261 }
1262
1263 computed.insert(rid, (inputs, outputs));
1264 }
1265
1266 self.refresh_brain_regions_hash();
1267
1268 Ok(computed)
1269 }
1270
1271 pub fn get_root_region_id(&self) -> Option<String> {
1277 self.brain_regions.get_root_region_id()
1278 }
1279
1280 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1282 self.cortical_idx_to_id.get(&cortical_idx)
1283 }
1284
1285 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1288 self.cortical_idx_to_id
1289 .iter()
1290 .map(|(idx, id)| (*idx, id.as_base_64()))
1291 .collect()
1292 }
1293
1294 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1299 let mut granularities = ahash::AHashMap::new();
1300 for (cortical_id, area) in &self.cortical_areas {
1301 let cortical_idx = self
1302 .cortical_id_to_idx
1303 .get(cortical_id)
1304 .copied()
1305 .unwrap_or(0);
1306
1307 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1310 if let Some(arr) = granularity_json.as_array() {
1311 if arr.len() == 3 {
1312 let x_opt = arr[0]
1313 .as_u64()
1314 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1315 let y_opt = arr[1]
1316 .as_u64()
1317 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1318 let z_opt = arr[2]
1319 .as_u64()
1320 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1321
1322 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1323 let granularity = (x as u32, y as u32, z as u32);
1324 if granularity != (1, 1, 1) {
1326 granularities.insert(cortical_idx, granularity);
1327 }
1328 }
1329 }
1330 }
1331 }
1332 }
1333 granularities
1334 }
1335
1336 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1338 self.cortical_areas.keys().collect()
1339 }
1340
1341 pub fn get_cortical_area_count(&self) -> usize {
1343 self.cortical_areas.len()
1344 }
1345
1346 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1360 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1361 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1362 if let Some(upstream_array) = upstream_prop.as_array() {
1363 return upstream_array
1364 .iter()
1365 .filter_map(|v| v.as_u64().map(|n| n as u32))
1366 .collect();
1367 }
1368 }
1369
1370 warn!(target: "feagi-bdu",
1372 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1373 target_cortical_id.as_base_64()
1374 );
1375 }
1376
1377 Vec::new()
1378 }
1379
1380 pub fn get_episodic_memory_upstream_cortical_areas(
1385 &self,
1386 target_cortical_id: &CorticalID,
1387 ) -> Vec<u32> {
1388 let mut episodic_upstream = Vec::new();
1389 let upstream = self.get_upstream_cortical_areas(target_cortical_id);
1390
1391 for src_idx in upstream {
1392 let Some(src_id) = self.cortical_idx_to_id.get(&src_idx) else {
1393 continue;
1394 };
1395 let Some(src_area) = self.cortical_areas.get(src_id) else {
1396 continue;
1397 };
1398 let Some(mapping_dst) = src_area
1399 .properties
1400 .get("cortical_mapping_dst")
1401 .and_then(|v| v.as_object())
1402 else {
1403 continue;
1404 };
1405 let Some(rules) =
1406 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1407 else {
1408 continue;
1409 };
1410
1411 let has_episodic_rule = rules.iter().any(|rule| {
1412 let morphology_id = rule
1413 .as_object()
1414 .and_then(|obj| obj.get("morphology_id"))
1415 .and_then(|v| v.as_str())
1416 .or_else(|| {
1417 rule.as_array()
1418 .and_then(|arr| arr.first())
1419 .and_then(|v| v.as_str())
1420 });
1421 morphology_id == Some("episodic_memory")
1422 });
1423
1424 if has_episodic_rule {
1425 episodic_upstream.push(src_idx);
1426 }
1427 }
1428
1429 episodic_upstream.sort_unstable();
1430 episodic_upstream.dedup();
1431 episodic_upstream
1432 }
1433
1434 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1436 upstream
1437 .iter()
1438 .filter_map(|idx| {
1439 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1440 let area = self.cortical_areas.get(cortical_id)?;
1441 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1442 None
1443 } else {
1444 Some(*idx)
1445 }
1446 })
1447 .collect()
1448 }
1449
1450 pub fn refresh_upstream_cortical_areas_from_mappings(
1454 &mut self,
1455 target_cortical_id: &CorticalID,
1456 ) -> Vec<u32> {
1457 use std::collections::HashSet;
1458 let target_id_str = target_cortical_id.as_base_64();
1459 let mut upstream_idxs = HashSet::new();
1460 for (src_id, src_area) in &self.cortical_areas {
1461 if src_id == target_cortical_id {
1462 continue;
1463 }
1464 if let Some(mapping) = src_area
1465 .properties
1466 .get("cortical_mapping_dst")
1467 .and_then(|v| v.as_object())
1468 {
1469 if mapping.contains_key(&target_id_str) {
1470 upstream_idxs.insert(src_area.cortical_idx);
1471 }
1472 }
1473 }
1474
1475 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1476 upstream_list.sort_unstable();
1477
1478 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1479 target_area.properties.insert(
1480 "upstream_cortical_areas".to_string(),
1481 serde_json::json!(upstream_list),
1482 );
1483 }
1484
1485 self.get_upstream_cortical_areas(target_cortical_id)
1486 }
1487
1488 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1498 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1499 let upstream_array = area
1500 .properties
1501 .entry("upstream_cortical_areas".to_string())
1502 .or_insert_with(|| serde_json::json!([]));
1503
1504 if let Some(arr) = upstream_array.as_array_mut() {
1505 let src_value = serde_json::json!(src_cortical_idx);
1506 if !arr.contains(&src_value) {
1507 arr.push(src_value);
1508 debug!(target: "feagi-bdu",
1509 "Added upstream area idx={} to cortical area '{}'",
1510 src_cortical_idx, target_cortical_id.as_base_64()
1511 );
1512 }
1513 }
1514 }
1515 }
1516
1517 pub fn get_memory_twin_for_upstream_idx(
1519 &self,
1520 memory_area_idx: u32,
1521 upstream_idx: u32,
1522 ) -> Option<CorticalID> {
1523 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1524 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1525 let area = self.cortical_areas.get(memory_id)?;
1526 let mapping = area
1527 .properties
1528 .get("memory_twin_areas")
1529 .and_then(|v| v.as_object())?;
1530 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1531 CorticalID::try_from_base_64(twin_b64).ok()
1532 }
1533
1534 pub fn diagnose_memory_twin_for_mapping(
1541 &self,
1542 src_area_id: &CorticalID,
1543 dst_area_id: &CorticalID,
1544 ) -> BduResult<MemoryTwinDiagnostic> {
1545 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
1546 BduError::InvalidArea(format!(
1547 "Source area not found: {}",
1548 src_area_id.as_base_64()
1549 ))
1550 })?;
1551 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
1552 BduError::InvalidArea(format!(
1553 "Destination area not found: {}",
1554 dst_area_id.as_base_64()
1555 ))
1556 })?;
1557
1558 let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
1559 let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
1560
1561 let rules_opt = src_area
1562 .properties
1563 .get("cortical_mapping_dst")
1564 .and_then(|v| v.as_object())
1565 .and_then(|mapping_dst| {
1566 Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
1567 });
1568 let mapping_exists = rules_opt.is_some();
1569 let episodic_rule_count = rules_opt
1570 .map(|rules| {
1571 rules
1572 .iter()
1573 .filter(|rule| {
1574 let morphology_id = rule
1575 .as_object()
1576 .and_then(|obj| obj.get("morphology_id"))
1577 .and_then(|v| v.as_str())
1578 .or_else(|| {
1579 rule.as_array()
1580 .and_then(|arr| arr.first())
1581 .and_then(|v| v.as_str())
1582 });
1583 morphology_id == Some("episodic_memory")
1584 })
1585 .count()
1586 })
1587 .unwrap_or(0);
1588
1589 let twin_expected =
1590 mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
1591 let twin_cortical_area = dst_area
1592 .properties
1593 .get("memory_twin_areas")
1594 .and_then(|v| v.as_object())
1595 .and_then(|map| map.get(&src_area_id.as_base_64()))
1596 .and_then(|v| v.as_str())
1597 .map(ToString::to_string);
1598 let twin_present = twin_cortical_area.is_some();
1599
1600 let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
1601 CorticalID::try_from_base_64(twin_b64)
1602 .ok()
1603 .and_then(|twin_id| {
1604 self.cortical_areas.get(&twin_id).and_then(|area| {
1605 area.properties
1606 .get("parent_region_id")
1607 .and_then(|v| v.as_str())
1608 .map(ToString::to_string)
1609 })
1610 })
1611 });
1612 let src_parent_region_id = src_area
1613 .properties
1614 .get("parent_region_id")
1615 .and_then(|v| v.as_str())
1616 .map(ToString::to_string);
1617 let dst_parent_region_id = dst_area
1618 .properties
1619 .get("parent_region_id")
1620 .and_then(|v| v.as_str())
1621 .map(ToString::to_string);
1622 let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
1623 (Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
1624 (None, None) if twin_present => Some(true),
1625 _ if twin_present => Some(false),
1626 _ => None,
1627 };
1628
1629 let reason = if !mapping_exists {
1630 Some("no_mapping_rule_between_src_and_dst".to_string())
1631 } else if episodic_rule_count == 0 {
1632 Some("mapping_exists_but_not_episodic_memory".to_string())
1633 } else if !dst_is_memory {
1634 Some("destination_is_not_memory_area".to_string())
1635 } else if src_is_memory {
1636 Some("upstream_is_memory_area_twin_not_supported".to_string())
1637 } else if !twin_present {
1638 Some("twin_expected_but_missing".to_string())
1639 } else if twin_parent_matches_src_parent == Some(false) {
1640 Some("twin_parent_region_mismatch_with_source".to_string())
1641 } else {
1642 None
1643 };
1644
1645 Ok(MemoryTwinDiagnostic {
1646 src_cortical_area: src_area_id.as_base_64(),
1647 dst_cortical_area: dst_area_id.as_base_64(),
1648 src_cortical_type: src_area.cortical_type.to_string(),
1649 dst_cortical_type: dst_area.cortical_type.to_string(),
1650 mapping_exists,
1651 episodic_rule_count,
1652 twin_expected,
1653 twin_present,
1654 twin_cortical_area,
1655 reason,
1656 src_parent_region_id,
1657 dst_parent_region_id,
1658 twin_parent_region_id,
1659 twin_parent_matches_src_parent,
1660 })
1661 }
1662
1663 pub fn ensure_memory_twin_area(
1665 &mut self,
1666 memory_area_id: &CorticalID,
1667 upstream_area_id: &CorticalID,
1668 ) -> BduResult<CorticalID> {
1669 use crate::models::CorticalAreaExt;
1670
1671 let register_replay_mapping = |manager: &mut ConnectomeManager,
1672 twin_id: &CorticalID|
1673 -> BduResult<()> {
1674 let Some(npu) = manager.npu.as_ref() else {
1675 return Ok(());
1676 };
1677 let memory_area_idx =
1678 *manager
1679 .cortical_id_to_idx
1680 .get(memory_area_id)
1681 .ok_or_else(|| {
1682 BduError::InvalidArea(format!(
1683 "Memory area idx missing for {}",
1684 memory_area_id.as_base_64()
1685 ))
1686 })?;
1687 let upstream_area_idx = *manager
1688 .cortical_id_to_idx
1689 .get(upstream_area_id)
1690 .ok_or_else(|| {
1691 BduError::InvalidArea(format!(
1692 "Upstream area idx missing for {}",
1693 upstream_area_id.as_base_64()
1694 ))
1695 })?;
1696 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1697 BduError::InvalidArea(format!(
1698 "Twin area idx missing for {}",
1699 twin_id.as_base_64()
1700 ))
1701 })?;
1702 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1703 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1704 })?;
1705 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1706 if let Ok(mut npu_lock) = npu.lock() {
1707 npu_lock.register_memory_twin_mapping(
1708 memory_area_idx,
1709 upstream_area_idx,
1710 twin_area_idx,
1711 potential,
1712 );
1713 }
1714 Ok(())
1715 };
1716
1717 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1718 BduError::InvalidArea(format!(
1719 "Memory area {} not found",
1720 memory_area_id.as_base_64()
1721 ))
1722 })?;
1723 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1724 BduError::InvalidArea(format!(
1725 "Upstream area {} not found",
1726 upstream_area_id.as_base_64()
1727 ))
1728 })?;
1729
1730 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1731 return Err(BduError::InvalidArea(format!(
1732 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1733 upstream_area_id.as_base_64()
1734 )));
1735 }
1736
1737 if let Some(existing) = memory_area
1738 .properties
1739 .get("memory_twin_areas")
1740 .and_then(|v| v.as_object())
1741 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1742 .and_then(|v| v.as_str())
1743 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1744 {
1745 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1746 register_replay_mapping(self, &existing)?;
1747 self.refresh_cortical_mappings_hash();
1748 return Ok(existing);
1749 }
1750
1751 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1752 if self.cortical_areas.contains_key(&twin_id) {
1753 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1754 let expected_source = upstream_area_id.as_base_64();
1755 let expected_target = memory_area_id.as_base_64();
1756 let existing_source = existing
1757 .properties
1758 .get("memory_twin_of")
1759 .and_then(|v| v.as_str());
1760 let existing_target = existing
1761 .properties
1762 .get("memory_twin_for")
1763 .and_then(|v| v.as_str());
1764 if existing_source != Some(expected_source.as_str())
1765 || existing_target != Some(expected_target.as_str())
1766 {
1767 warn!(
1768 target: "feagi-bdu",
1769 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1770 upstream_area_id.as_base_64(),
1771 memory_area_id.as_base_64()
1772 );
1773 existing.properties.insert(
1774 "memory_twin_of".to_string(),
1775 serde_json::json!(expected_source),
1776 );
1777 existing.properties.insert(
1778 "memory_twin_for".to_string(),
1779 serde_json::json!(expected_target),
1780 );
1781 }
1782 }
1783 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1784 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1785 register_replay_mapping(self, &twin_id)?;
1786 self.refresh_cortical_mappings_hash();
1787 return Ok(twin_id);
1788 }
1789
1790 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1791 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1792 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1793 let mut twin_area = CorticalArea::new(
1794 twin_id,
1795 0,
1796 twin_name,
1797 upstream_area.dimensions,
1798 twin_position,
1799 twin_type,
1800 )?;
1801 twin_area.properties = self.build_memory_twin_properties(
1802 memory_area,
1803 upstream_area,
1804 memory_area_id,
1805 upstream_area_id,
1806 );
1807
1808 let _twin_idx = self.add_cortical_area(twin_area)?;
1809 let _ = self.create_neurons_for_area(&twin_id);
1810
1811 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1812 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1813 register_replay_mapping(self, &twin_id)?;
1814 self.refresh_cortical_mappings_hash();
1815 Ok(twin_id)
1816 }
1817
1818 fn build_memory_twin_position(
1819 &self,
1820 _memory_area: &CorticalArea,
1821 upstream_area: &CorticalArea,
1822 ) -> GenomeCoordinate3D {
1823 let width = upstream_area.dimensions.width as f32;
1824 let margin = (width * 0.25).ceil() as i32;
1825 let offset = upstream_area.dimensions.width as i32 + margin;
1826 GenomeCoordinate3D::new(
1827 upstream_area.position.x + offset,
1828 upstream_area.position.y,
1829 upstream_area.position.z,
1830 )
1831 }
1832
1833 fn build_memory_twin_id(
1834 &self,
1835 memory_area_id: &CorticalID,
1836 upstream_area_id: &CorticalID,
1837 ) -> BduResult<CorticalID> {
1838 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1839 hasher.write(memory_area_id.as_base_64().as_bytes());
1840 hasher.write(upstream_area_id.as_base_64().as_bytes());
1841 hasher.write(b"memory_twin");
1842 let hash = hasher.finish();
1843 let mut bytes = hash.to_be_bytes();
1844 bytes[0] = b'c';
1845 CorticalID::try_from_bytes(&bytes)
1846 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1847 }
1848
1849 fn build_memory_twin_properties(
1850 &self,
1851 _memory_area: &CorticalArea,
1852 upstream_area: &CorticalArea,
1853 memory_area_id: &CorticalID,
1854 upstream_area_id: &CorticalID,
1855 ) -> HashMap<String, serde_json::Value> {
1856 let mut props = upstream_area.properties.clone();
1857 props.remove("cortical_mapping_dst");
1858 props.remove("upstream_cortical_areas");
1859 props.remove("parent_region_id");
1860 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1861 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1862 props.insert(
1863 "memory_twin_of".to_string(),
1864 serde_json::json!(upstream_area_id.as_base_64()),
1865 );
1866 props.insert(
1867 "memory_twin_for".to_string(),
1868 serde_json::json!(memory_area_id.as_base_64()),
1869 );
1870 if let Some(parent_region_id) = upstream_area
1871 .properties
1872 .get("parent_region_id")
1873 .and_then(|v| v.as_str())
1874 {
1875 props.insert(
1876 "parent_region_id".to_string(),
1877 serde_json::json!(parent_region_id),
1878 );
1879 }
1880 props
1881 }
1882
1883 fn set_memory_twin_mapping(
1884 &mut self,
1885 memory_area_id: &CorticalID,
1886 upstream_area_id: &CorticalID,
1887 twin_id: &CorticalID,
1888 ) {
1889 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1890 let mapping = memory_area
1891 .properties
1892 .entry("memory_twin_areas".to_string())
1893 .or_insert_with(|| serde_json::json!({}));
1894 if let Some(map) = mapping.as_object_mut() {
1895 map.insert(
1896 upstream_area_id.as_base_64(),
1897 serde_json::json!(twin_id.as_base_64()),
1898 );
1899 }
1900 }
1901 }
1902
1903 fn ensure_memory_replay_mapping(
1904 &mut self,
1905 memory_area_id: &CorticalID,
1906 twin_id: &CorticalID,
1907 ) -> BduResult<()> {
1908 if !self.morphology_registry.contains("memory_replay") {
1909 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1910 }
1911 self.refresh_morphologies_hash();
1912 let mapping_data = vec![serde_json::json!({
1913 "morphology_id": "memory_replay",
1914 "morphology_scalar": [1, 1, 1],
1915 "postSynapticCurrent_multiplier": 1,
1916 "plasticity_flag": false,
1917 "plasticity_constant": 0,
1918 "ltp_multiplier": 0,
1919 "ltd_multiplier": 0,
1920 "plasticity_window": 0,
1921 })];
1922 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1923 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1924 self.refresh_cortical_area_hashes(true, false);
1926 Ok(())
1927 }
1928
1929 pub fn teardown_owned_memory_twin_for_mapping(
1934 &mut self,
1935 memory_area_id: &CorticalID,
1936 upstream_area_id: &CorticalID,
1937 ) -> BduResult<Option<CorticalID>> {
1938 let upstream_id = upstream_area_id.as_base_64();
1939 let twin_id = self
1940 .cortical_areas
1941 .get(memory_area_id)
1942 .and_then(|memory_area| memory_area.properties.get("memory_twin_areas"))
1943 .and_then(|value| value.as_object())
1944 .and_then(|twins| twins.get(&upstream_id))
1945 .and_then(|value| value.as_str())
1946 .map(CorticalID::try_from_base_64)
1947 .transpose()
1948 .map_err(|error| {
1949 BduError::InvalidArea(format!(
1950 "Invalid owned memory twin ID for {} -> {}: {}",
1951 upstream_area_id.as_base_64(),
1952 memory_area_id.as_base_64(),
1953 error
1954 ))
1955 })?;
1956 let Some(twin_id) = twin_id else {
1957 return Ok(None);
1958 };
1959
1960 let twin = self.cortical_areas.get(&twin_id).ok_or_else(|| {
1961 BduError::InvalidArea(format!(
1962 "Owned memory twin {} for {} -> {} does not exist",
1963 twin_id.as_base_64(),
1964 upstream_area_id.as_base_64(),
1965 memory_area_id.as_base_64()
1966 ))
1967 })?;
1968 let owned_by_source = twin
1969 .properties
1970 .get("memory_twin_of")
1971 .and_then(|value| value.as_str())
1972 == Some(upstream_id.as_str());
1973 let owned_by_memory = twin
1974 .properties
1975 .get("memory_twin_for")
1976 .and_then(|value| value.as_str())
1977 == Some(memory_area_id.as_base_64().as_str());
1978 if !owned_by_source || !owned_by_memory {
1979 return Err(BduError::InvalidArea(format!(
1980 "Cortical area {} is not the generated twin owned by {} -> {}",
1981 twin_id.as_base_64(),
1982 upstream_area_id.as_base_64(),
1983 memory_area_id.as_base_64()
1984 )));
1985 }
1986
1987 let memory_idx = *self.cortical_id_to_idx.get(memory_area_id).ok_or_else(|| {
1988 BduError::InvalidArea(format!(
1989 "Memory area idx missing for {}",
1990 memory_area_id.as_base_64()
1991 ))
1992 })?;
1993 let upstream_idx = *self
1994 .cortical_id_to_idx
1995 .get(upstream_area_id)
1996 .ok_or_else(|| {
1997 BduError::InvalidArea(format!(
1998 "Upstream area idx missing for {}",
1999 upstream_area_id.as_base_64()
2000 ))
2001 })?;
2002
2003 self.update_cortical_mapping(memory_area_id, &twin_id, Vec::new())?;
2004 let _ = self.regenerate_synapses_for_mapping(memory_area_id, &twin_id)?;
2005 if let Some(npu) = &self.npu {
2006 npu.lock()
2007 .unwrap()
2008 .unregister_memory_twin_mapping(memory_idx, upstream_idx);
2009 }
2010
2011 let memory_area = self.cortical_areas.get_mut(memory_area_id).ok_or_else(|| {
2012 BduError::InvalidArea(format!(
2013 "Memory area {} not found",
2014 memory_area_id.as_base_64()
2015 ))
2016 })?;
2017 let twins = memory_area
2018 .properties
2019 .get_mut("memory_twin_areas")
2020 .and_then(|value| value.as_object_mut())
2021 .ok_or_else(|| {
2022 BduError::InvalidArea(format!(
2023 "Memory area {} has invalid memory_twin_areas metadata",
2024 memory_area_id.as_base_64()
2025 ))
2026 })?;
2027 twins.remove(&upstream_id);
2028 if twins.is_empty() {
2029 memory_area.properties.remove("memory_twin_areas");
2030 }
2031
2032 for region_id in self
2033 .get_brain_region_ids()
2034 .into_iter()
2035 .cloned()
2036 .collect::<Vec<_>>()
2037 {
2038 if let Some(region) = self.get_brain_region_mut(®ion_id) {
2039 region.remove_area(&twin_id);
2040 }
2041 }
2042 self.remove_cortical_area(&twin_id)?;
2043 self.refresh_cortical_mappings_hash();
2044 Ok(Some(twin_id))
2045 }
2046
2047 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
2057 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
2058 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
2059 if let Some(arr) = upstream_prop.as_array_mut() {
2060 let src_value = serde_json::json!(src_cortical_idx);
2061 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
2062 arr.remove(pos);
2063 debug!(target: "feagi-bdu",
2064 "Removed upstream area idx={} from cortical area '{}'",
2065 src_cortical_idx, target_cortical_id.as_base_64()
2066 );
2067 }
2068 }
2069 }
2070 }
2071 }
2072
2073 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
2075 self.cortical_areas.contains_key(cortical_id)
2076 }
2077
2078 pub fn is_initialized(&self) -> bool {
2080 self.initialized && !self.cortical_areas.is_empty()
2081 }
2082
2083 pub fn add_brain_region(
2089 &mut self,
2090 region: BrainRegion,
2091 parent_id: Option<String>,
2092 ) -> BduResult<()> {
2093 self.brain_regions.add_region(region, parent_id)?;
2094 self.refresh_brain_regions_hash();
2095 Ok(())
2096 }
2097
2098 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
2100 self.brain_regions.remove_region(region_id)?;
2101 self.refresh_brain_regions_hash();
2102 Ok(())
2103 }
2104
2105 pub fn change_brain_region_parent(
2107 &mut self,
2108 region_id: &str,
2109 new_parent_id: &str,
2110 ) -> BduResult<()> {
2111 self.brain_regions.change_parent(region_id, new_parent_id)?;
2112 self.refresh_brain_regions_hash();
2113 Ok(())
2114 }
2115
2116 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
2118 self.brain_regions.get_region(region_id)
2119 }
2120
2121 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
2123 self.brain_regions.get_region_mut(region_id)
2124 }
2125
2126 pub fn get_brain_region_ids(&self) -> Vec<&String> {
2128 self.brain_regions.get_all_region_ids()
2129 }
2130
2131 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
2133 &self.brain_regions
2134 }
2135
2136 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
2142 &self.morphology_registry
2143 }
2144
2145 pub fn get_morphology_count(&self) -> usize {
2147 self.morphology_registry.count()
2148 }
2149
2150 pub fn upsert_morphology(
2155 &mut self,
2156 morphology_id: String,
2157 morphology: feagi_evolutionary::Morphology,
2158 ) {
2159 self.morphology_registry
2160 .add_morphology(morphology_id, morphology);
2161 self.refresh_morphologies_hash();
2162 }
2163
2164 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
2170 let removed = self.morphology_registry.remove_morphology(morphology_id);
2171 if removed {
2172 self.refresh_morphologies_hash();
2173 }
2174 removed
2175 }
2176
2177 pub fn update_cortical_mapping(
2195 &mut self,
2196 src_area_id: &CorticalID,
2197 dst_area_id: &CorticalID,
2198 mapping_data: Vec<serde_json::Value>,
2199 ) -> BduResult<()> {
2200 use tracing::info;
2201
2202 self.validate_memory_outbound_mapping_contract(src_area_id, dst_area_id, &mapping_data)?;
2203 crate::region_io_designation::validate_cross_region_mapping_proposal(
2204 self,
2205 src_area_id,
2206 dst_area_id,
2207 &mapping_data,
2208 )?;
2209
2210 debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
2211
2212 {
2213 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
2215 crate::types::BduError::InvalidArea(format!(
2216 "Source area not found: {}",
2217 src_area_id
2218 ))
2219 })?;
2220
2221 let cortical_mapping_dst =
2223 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
2224 existing.as_object_mut().ok_or_else(|| {
2225 crate::types::BduError::InvalidMorphology(
2226 "cortical_mapping_dst is not an object".to_string(),
2227 )
2228 })?
2229 } else {
2230 src_area
2232 .properties
2233 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
2234 src_area
2235 .properties
2236 .get_mut("cortical_mapping_dst")
2237 .unwrap()
2238 .as_object_mut()
2239 .unwrap()
2240 };
2241
2242 if mapping_data.is_empty() {
2244 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
2246 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
2247 } else {
2248 cortical_mapping_dst.insert(
2249 dst_area_id.as_base_64(),
2250 serde_json::Value::Array(mapping_data.clone()),
2251 );
2252 debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
2253 src_area_id, dst_area_id, mapping_data.len());
2254 }
2255 }
2256
2257 self.refresh_cortical_mappings_hash();
2258
2259 Ok(())
2260 }
2261
2262 pub fn validate_memory_outbound_mapping_contract(
2269 &self,
2270 src_area_id: &CorticalID,
2271 dst_area_id: &CorticalID,
2272 mapping_data: &[serde_json::Value],
2273 ) -> BduResult<()> {
2274 if mapping_data.is_empty() {
2275 return Ok(());
2276 }
2277 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2278 BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2279 })?;
2280 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2281 BduError::InvalidArea(format!("Destination area not found: {}", dst_area_id))
2282 })?;
2283 if !matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2284 || matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2285 {
2286 return Ok(());
2287 }
2288
2289 for rule in mapping_data {
2290 let morphology_id = rule
2291 .as_object()
2292 .and_then(|rule_obj| rule_obj.get("morphology_id"))
2293 .and_then(|value| value.as_str())
2294 .or_else(|| {
2295 rule.as_array()
2296 .and_then(|rule_array| rule_array.first())
2297 .and_then(|value| value.as_str())
2298 });
2299 let is_replay_edge_to_own_twin = morphology_id == Some("memory_replay")
2300 && dst_area
2301 .properties
2302 .get("memory_twin_for")
2303 .and_then(|value| value.as_str())
2304 == Some(src_area_id.as_base_64().as_str());
2305 if morphology_id != Some("associative_memory") && !is_replay_edge_to_own_twin {
2306 return Err(BduError::InvalidMorphology(format!(
2307 "Memory-to-non-memory mapping {} -> {} only supports associative_memory \
2308 (or memory_replay to its own twin)",
2309 src_area_id, dst_area_id
2310 )));
2311 }
2312 }
2313 Ok(())
2314 }
2315
2316 pub fn regenerate_synapses_for_mapping(
2329 &mut self,
2330 src_area_id: &CorticalID,
2331 dst_area_id: &CorticalID,
2332 ) -> BduResult<usize> {
2333 use tracing::info;
2334
2335 debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
2336
2337 let mapping_rules_len = self
2338 .cortical_areas
2339 .get(src_area_id)
2340 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2341 .and_then(|v| v.as_object())
2342 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2343 .and_then(|v| v.as_array())
2344 .map(|arr| arr.len())
2345 .unwrap_or(0);
2346 tracing::debug!(
2347 target: "feagi-bdu",
2348 "Mapping rules for {} -> {}: {}",
2349 src_area_id,
2350 dst_area_id,
2351 mapping_rules_len
2352 );
2353
2354 let Some(npu_arc) = self.npu.clone() else {
2356 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
2357 return Ok(0);
2358 };
2359
2360 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2370 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
2371 })?;
2372 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2373 BduError::InvalidArea(format!(
2374 "No cortical idx for destination area {}",
2375 dst_area_id
2376 ))
2377 })?;
2378
2379 let mut pruned_synapse_count: usize = 0;
2382 use std::time::Instant;
2383 let start = Instant::now();
2384
2385 let (sources, targets) = {
2391 let lock_start = std::time::Instant::now();
2392 let npu = npu_arc.lock().unwrap();
2393 let lock_wait = lock_start.elapsed();
2394 tracing::debug!(
2395 target: "feagi-bdu",
2396 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
2397 lock_wait.as_secs_f64() * 1000.0,
2398 src_area_id,
2399 dst_area_id
2400 );
2401 let sources: Vec<NeuronId> = npu
2402 .get_neurons_in_cortical_area(src_idx)
2403 .into_iter()
2404 .map(NeuronId)
2405 .collect();
2406 let targets: Vec<NeuronId> = npu
2407 .get_neurons_in_cortical_area(dst_idx)
2408 .into_iter()
2409 .map(NeuronId)
2410 .collect();
2411 (sources, targets)
2412 };
2413
2414 tracing::debug!(
2415 target: "feagi-bdu",
2416 "Prune synapses: {} sources, {} targets",
2417 sources.len(),
2418 targets.len()
2419 );
2420
2421 if !sources.is_empty() && !targets.is_empty() {
2422 let remove_start = Instant::now();
2423 pruned_synapse_count = {
2424 let lock_start = std::time::Instant::now();
2425 let mut npu = npu_arc.lock().unwrap();
2426 let lock_wait = lock_start.elapsed();
2427 tracing::debug!(
2428 target: "feagi-bdu",
2429 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
2430 lock_wait.as_secs_f64() * 1000.0,
2431 src_area_id,
2432 dst_area_id
2433 );
2434 npu.remove_synapses_between(sources, targets)
2438 };
2439 let remove_time = remove_start.elapsed();
2440 let total_time = start.elapsed();
2441
2442 info!(
2443 target: "feagi-bdu",
2444 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2445 pruned_synapse_count,
2446 src_area_id,
2447 dst_area_id,
2448 total_time.as_millis(),
2449 remove_time.as_millis()
2450 );
2451
2452 if pruned_synapse_count > 0 {
2454 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2455 BduError::Internal(format!(
2456 "Pruned synapse count overflow (usize -> u32): {}",
2457 pruned_synapse_count
2458 ))
2459 })?;
2460 let state_manager = StateManager::instance();
2461 let state_manager = state_manager.read();
2462 let core_state = state_manager.get_core_state();
2463 core_state.subtract_synapse_count(pruned_u32);
2464 state_manager.subtract_cortical_area_outgoing_synapses(
2465 &src_area_id.as_base_64(),
2466 pruned_synapse_count,
2467 );
2468 state_manager.subtract_cortical_area_incoming_synapses(
2469 &dst_area_id.as_base_64(),
2470 pruned_synapse_count,
2471 );
2472
2473 {
2477 let mut cache = self.cached_synapse_counts_per_area.write();
2478 let entry = cache
2479 .entry(*src_area_id)
2480 .or_insert_with(|| AtomicUsize::new(0));
2481 let mut current = entry.load(Ordering::Relaxed);
2482 loop {
2483 let next = current.saturating_sub(pruned_synapse_count);
2484 match entry.compare_exchange(
2485 current,
2486 next,
2487 Ordering::Relaxed,
2488 Ordering::Relaxed,
2489 ) {
2490 Ok(_) => break,
2491 Err(v) => current = v,
2492 }
2493 }
2494 }
2495 }
2496 }
2497
2498 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2505 tracing::debug!(
2506 target: "feagi-bdu",
2507 "Synaptogenesis created {} synapses for {} -> {}",
2508 synapse_count,
2509 src_area_id,
2510 dst_area_id
2511 );
2512
2513 if synapse_count > 0 {
2516 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2517 BduError::Internal(format!(
2518 "Created synapse count overflow (usize -> u32): {}",
2519 synapse_count
2520 ))
2521 })?;
2522
2523 {
2525 let mut cache = self.cached_synapse_counts_per_area.write();
2526 cache
2527 .entry(*src_area_id)
2528 .or_insert_with(|| AtomicUsize::new(0))
2529 .fetch_add(synapse_count, Ordering::Relaxed);
2530 }
2531
2532 let state_manager = StateManager::instance();
2534 let state_manager = state_manager.read();
2535 let core_state = state_manager.get_core_state();
2536 core_state.add_synapse_count(created_u32);
2537 state_manager
2538 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2539 state_manager
2540 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2541 }
2542
2543 let src_idx_for_upstream = src_idx;
2546
2547 let has_mapping = self
2549 .cortical_areas
2550 .get(src_area_id)
2551 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2552 .and_then(|v| v.as_object())
2553 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2554 .is_some();
2555
2556 debug!(target: "feagi-bdu",
2557 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2558 synapse_count,
2559 src_area_id.as_base_64(),
2560 dst_area_id.as_base_64(),
2561 has_mapping,
2562 if has_mapping { "" } else { "NOT " }
2563 );
2564
2565 if has_mapping {
2566 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2568
2569 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2570 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2571 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2572 warn!(
2573 target: "feagi-bdu",
2574 "Failed to ensure memory twin for {} -> {}: {}",
2575 src_area_id.as_base_64(),
2576 dst_area_id.as_base_64(),
2577 e
2578 );
2579 }
2580 }
2581 }
2582
2583 #[cfg(feature = "plasticity")]
2585 if let Some(ref executor) = self.plasticity_executor {
2586 use feagi_evolutionary::extract_memory_properties;
2587
2588 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2589 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2590 let upstream_areas =
2591 self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
2592 debug!(
2593 target: "feagi-bdu",
2594 "Registering memory area idx={} id={} upstream={} depth={}",
2595 dst_area.cortical_idx,
2596 dst_area_id.as_base_64(),
2597 upstream_areas.len(),
2598 mem_props.temporal_depth
2599 );
2600
2601 if let Some(ref npu_arc) = self.npu {
2604 if let Ok(mut npu) = npu_arc.lock() {
2605 let existing_configs = npu.get_all_fire_ledger_configs();
2606 for &upstream_idx in &upstream_areas {
2607 let existing = existing_configs
2608 .iter()
2609 .find(|(idx, _)| *idx == upstream_idx)
2610 .map(|(_, w)| *w)
2611 .unwrap_or(0);
2612
2613 let desired = mem_props.temporal_depth as usize;
2614 let resolved = existing.max(desired);
2615 if resolved != existing {
2616 if let Err(e) =
2617 npu.configure_fire_ledger_window(upstream_idx, resolved)
2618 {
2619 warn!(
2620 target: "feagi-bdu",
2621 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2622 upstream_idx,
2623 resolved,
2624 e
2625 );
2626 }
2627 }
2628 }
2629 } else {
2630 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2631 }
2632 }
2633
2634 if let Ok(exec) = executor.lock() {
2635 use feagi_npu_plasticity::{
2636 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2637 };
2638
2639 let lifecycle_config = MemoryNeuronLifecycleConfig {
2640 initial_lifespan: mem_props.init_lifespan,
2641 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2642 longterm_threshold: mem_props.longterm_threshold,
2643 max_reactivations: 1000,
2644 };
2645
2646 exec.register_memory_area(
2647 dst_area.cortical_idx,
2648 dst_area_id.as_base_64(),
2649 mem_props.temporal_depth,
2650 upstream_areas.clone(),
2651 Some(lifecycle_config),
2652 mem_props.mp_learning_enabled,
2653 );
2654 } else {
2655 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2656 }
2657 } else {
2658 debug!(
2659 target: "feagi-bdu",
2660 "Skipping plasticity registration: no memory properties for area {}",
2661 dst_area_id.as_base_64()
2662 );
2663 }
2664 } else {
2665 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2666 }
2667 } else {
2668 warn!(
2669 target: "feagi-bdu",
2670 "PlasticityExecutor not available; memory area {} not registered",
2671 dst_area_id.as_base_64()
2672 );
2673 }
2674
2675 #[cfg(not(feature = "plasticity"))]
2676 {
2677 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2678 }
2679 } else {
2680 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2682
2683 let destination_is_memory = self
2684 .cortical_areas
2685 .get(dst_area_id)
2686 .is_some_and(|area| matches!(area.cortical_type, CorticalAreaType::Memory(_)));
2687 if destination_is_memory {
2688 self.teardown_owned_memory_twin_for_mapping(dst_area_id, src_area_id)?;
2689 }
2690
2691 let mut npu = npu_arc.lock().unwrap();
2693 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2694 }
2695
2696 debug!(
2697 target: "feagi-bdu",
2698 "Created {} new synapses: {} -> {}",
2699 synapse_count,
2700 src_area_id,
2701 dst_area_id
2702 );
2703
2704 if pruned_synapse_count > 0 || synapse_count == 0 {
2707 let mut npu = npu_arc.lock().unwrap();
2708 npu.rebuild_synapse_index();
2709 info!(
2710 target: "feagi-bdu",
2711 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2712 src_area_id,
2713 dst_area_id,
2714 pruned_synapse_count,
2715 synapse_count
2716 );
2717 } else {
2718 debug!(
2719 target: "feagi-bdu",
2720 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2721 src_area_id,
2722 dst_area_id,
2723 synapse_count
2724 );
2725 }
2726
2727 {
2729 let npu = npu_arc.lock().unwrap();
2730 let fresh_count = npu.get_synapse_count();
2731 self.cached_synapse_count
2732 .store(fresh_count, Ordering::Relaxed);
2733 }
2734
2735 Ok(synapse_count)
2736 }
2737
2738 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2740 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2741 }
2742
2743 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2744 v.as_u64()
2745 .map(|n| n as usize)
2746 .or_else(|| v.as_f64().map(|f| f as usize))
2747 }
2748
2749 #[allow(clippy::too_many_arguments)]
2751 fn register_stdp_mapping_for_rule(
2752 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2753 src_area_id: &CorticalID,
2754 dst_area_id: &CorticalID,
2755 src_cortical_idx: u32,
2756 dst_cortical_idx: u32,
2757 rule_obj: &serde_json::Map<String, serde_json::Value>,
2758 bidirectional_stdp: bool,
2759 synapse_psp: f32,
2760 synapse_type: feagi_npu_neural::SynapseType,
2761 ) -> BduResult<()> {
2762 let plasticity_window = rule_obj
2763 .get("plasticity_window")
2764 .and_then(Self::json_number_as_usize_for_stdp)
2765 .ok_or_else(|| {
2766 BduError::Internal(format!(
2767 "Missing plasticity_window in plastic mapping rule {} -> {}",
2768 src_area_id, dst_area_id
2769 ))
2770 })?;
2771 let plasticity_constant = rule_obj
2772 .get("plasticity_constant")
2773 .and_then(Self::json_number_as_i64_for_stdp)
2774 .ok_or_else(|| {
2775 BduError::Internal(format!(
2776 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2777 src_area_id, dst_area_id
2778 ))
2779 })?;
2780 let ltp_i64 = rule_obj
2781 .get("ltp_multiplier")
2782 .and_then(Self::json_number_as_i64_for_stdp)
2783 .ok_or_else(|| {
2784 BduError::Internal(format!(
2785 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2786 src_area_id, dst_area_id
2787 ))
2788 })?;
2789 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
2790 BduError::Internal(format!(
2791 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2792 i8::MIN,
2793 i8::MAX,
2794 ltp_i64,
2795 src_area_id,
2796 dst_area_id
2797 ))
2798 })?;
2799 let ltd_i64 = rule_obj
2800 .get("ltd_multiplier")
2801 .and_then(Self::json_number_as_i64_for_stdp)
2802 .ok_or_else(|| {
2803 BduError::Internal(format!(
2804 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2805 src_area_id, dst_area_id
2806 ))
2807 })?;
2808 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
2809 BduError::Internal(format!(
2810 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2811 i8::MIN,
2812 i8::MAX,
2813 ltd_i64,
2814 src_area_id,
2815 dst_area_id
2816 ))
2817 })?;
2818
2819 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
2823 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
2824 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2825 }
2826 Some(s) if s.eq_ignore_ascii_case("stdp") => {
2827 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
2828 }
2829 Some(s) if s.eq_ignore_ascii_case("off") => {
2830 feagi_npu_burst_engine::npu::PlasticityMode::Off
2831 }
2832 Some(other) => {
2833 return Err(BduError::Internal(format!(
2834 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
2835 other, src_area_id, dst_area_id
2836 )));
2837 }
2838 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
2839 };
2840
2841 let eligibility_decay_bursts = rule_obj
2843 .get("eligibility_decay_bursts")
2844 .and_then(|v| v.as_u64())
2845 .map(|n| n as u32)
2846 .unwrap_or(0);
2847 let reward_source_area_id = rule_obj
2848 .get("reward_source_area")
2849 .and_then(|v| v.as_str())
2850 .map(str::to_string);
2851 let punishment_source_area_id = rule_obj
2852 .get("punishment_source_area")
2853 .and_then(|v| v.as_str())
2854 .map(str::to_string);
2855
2856 let max_weight_provided = rule_obj.get("max_weight").is_some()
2861 && !rule_obj
2862 .get("max_weight")
2863 .map(|v| v.is_null())
2864 .unwrap_or(true);
2865 let max_weight: f32 = if max_weight_provided {
2866 let raw = rule_obj
2867 .get("max_weight")
2868 .and_then(|v| v.as_f64())
2869 .ok_or_else(|| {
2870 BduError::Internal(format!(
2871 "max_weight must be a number on mapping {} -> {}",
2872 src_area_id, dst_area_id
2873 ))
2874 })?;
2875 if raw.is_nan() || raw <= 0.0 {
2876 return Err(BduError::Internal(format!(
2877 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
2878 raw, src_area_id, dst_area_id
2879 )));
2880 }
2881 raw as f32
2882 } else {
2883 f32::INFINITY
2884 };
2885
2886 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
2889 && !rule_obj
2890 .get("plasticity_eta")
2891 .map(|v| v.is_null())
2892 .unwrap_or(true);
2893 let plasticity_eta: f32 = if plasticity_eta_provided {
2894 let raw = rule_obj
2895 .get("plasticity_eta")
2896 .and_then(|v| v.as_f64())
2897 .ok_or_else(|| {
2898 BduError::Internal(format!(
2899 "plasticity_eta must be a number on mapping {} -> {}",
2900 src_area_id, dst_area_id
2901 ))
2902 })?;
2903 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
2904 return Err(BduError::Internal(format!(
2905 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
2906 raw, src_area_id, dst_area_id
2907 )));
2908 }
2909 raw as f32
2910 } else {
2911 1.0
2912 };
2913
2914 if !matches!(
2916 plasticity_mode,
2917 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2918 ) && (reward_source_area_id.is_some()
2919 || punishment_source_area_id.is_some()
2920 || eligibility_decay_bursts != 0)
2921 {
2922 return Err(BduError::Internal(format!(
2923 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
2924 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
2925 src_area_id, dst_area_id
2926 )));
2927 }
2928
2929 if matches!(
2933 plasticity_mode,
2934 feagi_npu_burst_engine::npu::PlasticityMode::Off
2935 ) && max_weight_provided
2936 {
2937 return Err(BduError::Internal(format!(
2938 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2939 src_area_id, dst_area_id
2940 )));
2941 }
2942
2943 if matches!(
2944 plasticity_mode,
2945 feagi_npu_burst_engine::npu::PlasticityMode::Off
2946 ) && plasticity_eta_provided
2947 {
2948 return Err(BduError::Internal(format!(
2949 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2950 src_area_id, dst_area_id
2951 )));
2952 }
2953
2954 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2955 let mut npu_lock = npu
2956 .lock()
2957 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2958 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2959
2960 let resolve_optional_area =
2965 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
2966 let Some(name) = name_opt else {
2967 return Ok(None);
2968 };
2969 match npu_lock.get_cortical_area_id(name.as_str()) {
2970 Some(idx) => Ok(Some(idx)),
2971 None => Err(BduError::Internal(format!(
2972 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
2973 label, name, src_area_id, dst_area_id
2974 ))),
2975 }
2976 };
2977 let reward_source_area =
2978 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
2979 let punishment_source_area =
2980 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
2981
2982 if matches!(
2984 plasticity_mode,
2985 feagi_npu_burst_engine::npu::PlasticityMode::Off
2986 ) {
2987 return Ok(());
2988 }
2989
2990 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2991 plasticity_window,
2992 plasticity_constant,
2993 ltp_multiplier,
2994 ltd_multiplier,
2995 bidirectional_stdp,
2996 associative_memory_mapping: rule_obj
2997 .get("morphology_id")
2998 .and_then(|value| value.as_str())
2999 == Some("associative_memory"),
3000 synapse_psp,
3001 synapse_type,
3002 plasticity_mode,
3003 eligibility_decay_bursts,
3004 reward_source_area,
3005 punishment_source_area,
3006 max_weight,
3007 plasticity_eta,
3008 };
3009
3010 npu_lock
3011 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
3012 .map_err(|e| {
3013 BduError::Internal(format!(
3014 "Failed to register STDP mapping {} -> {}: {}",
3015 src_area_id, dst_area_id, e
3016 ))
3017 })?;
3018
3019 let mut areas_to_track: Vec<(u32, usize)> = vec![
3023 (src_cortical_idx, plasticity_window),
3024 (dst_cortical_idx, plasticity_window),
3025 ];
3026 if let Some(area) = reward_source_area {
3027 areas_to_track.push((area, 1));
3028 }
3029 if let Some(area) = punishment_source_area {
3030 areas_to_track.push((area, 1));
3031 }
3032
3033 let existing_configs = npu_lock.get_all_fire_ledger_configs();
3034 for (area_idx, required_depth) in areas_to_track {
3035 let existing = existing_configs
3036 .iter()
3037 .find(|(idx, _)| *idx == area_idx)
3038 .map(|(_, w)| *w)
3039 .unwrap_or(0);
3040 let resolved = existing.max(required_depth);
3041 if resolved != existing {
3042 npu_lock
3043 .configure_fire_ledger_window(area_idx, resolved)
3044 .map_err(|e| {
3045 BduError::Internal(format!(
3046 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
3047 area_idx, resolved, e
3048 ))
3049 })?;
3050 }
3051 }
3052
3053 Ok(())
3054 }
3055
3056 fn resolve_synapse_params_for_rule(
3058 &self,
3059 src_area_id: &CorticalID,
3060 rule: &serde_json::Value,
3061 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
3062 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3064 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3065 })?;
3066
3067 let (weight, synapse_type) = {
3069 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
3070 if let Some(i) = v.as_i64() {
3071 return Some(i as f64);
3072 }
3073 v.as_f64()
3074 };
3075
3076 let mult: f64 = if let Some(obj) = rule.as_object() {
3077 obj.get("postSynapticCurrent_multiplier")
3078 .and_then(parse_f64)
3079 .unwrap_or(1.0)
3080 } else if let Some(arr) = rule.as_array() {
3081 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
3082 } else {
3083 128.0
3084 };
3085
3086 if mult < 0.0 {
3087 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
3088 } else {
3089 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
3090 }
3091 };
3092
3093 use crate::models::cortical_area::CorticalAreaExt;
3095 let psp_f32 = src_area.postsynaptic_current();
3096
3097 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
3098 obj.get("synaptic_delay_bursts")
3099 .and_then(|v| v.as_u64())
3100 .map(|d| u8::try_from(d).unwrap_or(1))
3101 .unwrap_or(1)
3102 } else if let Some(arr) = rule.as_array() {
3103 arr.get(8)
3104 .and_then(|v| v.as_u64())
3105 .map(|d| u8::try_from(d).unwrap_or(1))
3106 .unwrap_or(1)
3107 } else {
3108 1
3109 };
3110 if delay_bursts < 1 {
3111 return Err(crate::types::BduError::Internal(format!(
3112 "synaptic_delay_bursts must be >= 1 (src area {})",
3113 src_area_id.as_base_64()
3114 )));
3115 }
3116
3117 tracing::debug!(
3118 target: "feagi-bdu",
3119 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
3120 src_area_id.as_base_64(),
3121 weight,
3122 psp_f32,
3123 synapse_type,
3124 delay_bursts
3125 );
3126
3127 Ok((weight, psp_f32, synapse_type, delay_bursts))
3128 }
3129
3130 fn apply_cortical_mapping_for_pair(
3132 &mut self,
3133 src_area_id: &CorticalID,
3134 dst_area_id: &CorticalID,
3135 ) -> BduResult<usize> {
3136 let rules = {
3142 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3143 crate::types::BduError::InvalidArea(format!(
3144 "Source area not found: {}",
3145 src_area_id
3146 ))
3147 })?;
3148
3149 let Some(mapping_dst) = src_area
3150 .properties
3151 .get("cortical_mapping_dst")
3152 .and_then(|v| v.as_object())
3153 else {
3154 return Ok(0);
3155 };
3156
3157 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
3158 else {
3159 return Ok(0);
3160 };
3161
3162 rules.clone()
3163 }; if rules.is_empty() {
3166 return Ok(0);
3167 }
3168
3169 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3171 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
3172 })?;
3173 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3174 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
3175 })?;
3176
3177 let npu_arc = self
3179 .npu
3180 .as_ref()
3181 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
3182 .clone();
3183
3184 tracing::debug!(
3185 target: "feagi-bdu",
3186 "Applying {} mapping rule(s) for {} -> {}",
3187 rules.len(),
3188 src_area_id,
3189 dst_area_id
3190 );
3191 let mut total_synapses = 0;
3193 for rule in &rules {
3194 let morphology_id = if let Some(rule_obj) = rule.as_object() {
3195 rule_obj
3196 .get("morphology_id")
3197 .and_then(|v| v.as_str())
3198 .unwrap_or("unknown")
3199 .to_string()
3200 } else if let Some(rule_arr) = rule.as_array() {
3201 rule_arr
3202 .first()
3203 .and_then(|v| v.as_str())
3204 .unwrap_or("unknown")
3205 .to_string()
3206 } else {
3207 "unknown".to_string()
3208 };
3209
3210 let rule_keys: Vec<String> = rule
3211 .as_object()
3212 .map(|obj| obj.keys().cloned().collect())
3213 .unwrap_or_default();
3214
3215 let mut plasticity_flag = rule
3217 .as_object()
3218 .and_then(|obj| obj.get("plasticity_flag"))
3219 .and_then(|v| v.as_bool())
3220 .unwrap_or(false);
3221 if morphology_id == "associative_memory" {
3222 plasticity_flag = true;
3223 }
3224 if plasticity_flag {
3225 let Some(rule_obj) = rule.as_object() else {
3226 return Err(crate::types::BduError::InvalidMorphology(
3227 "Plasticity mapping rule must be an object format".to_string(),
3228 ));
3229 };
3230 let (_weight, psp, synapse_type, _delay_bursts) =
3231 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3232 let bidirectional_stdp = false;
3235 if let Err(e) = Self::register_stdp_mapping_for_rule(
3236 &npu_arc,
3237 src_area_id,
3238 dst_area_id,
3239 src_cortical_idx,
3240 dst_cortical_idx,
3241 rule_obj,
3242 bidirectional_stdp,
3243 psp,
3244 synapse_type,
3245 ) {
3246 tracing::error!(
3247 target: "feagi-bdu",
3248 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
3249 src_area_id,
3250 dst_area_id,
3251 morphology_id,
3252 rule_keys,
3253 e
3254 );
3255 return Err(e);
3256 }
3257 }
3258
3259 if let Some(gate_area_str) = rule
3263 .as_object()
3264 .and_then(|obj| obj.get("gate_source_area"))
3265 .and_then(|v| v.as_str())
3266 {
3267 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
3268 crate::types::BduError::Internal(format!(
3269 "Invalid gate_source_area '{}' on mapping {} -> {}",
3270 gate_area_str, src_area_id, dst_area_id
3271 ))
3272 })?;
3273 let gate_cortical_idx =
3274 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
3275 crate::types::BduError::Internal(format!(
3276 "Unknown gate_source_area '{}' on mapping {} -> {}",
3277 gate_area_str, src_area_id, dst_area_id
3278 ))
3279 })?;
3280
3281 let mut npu_lock = npu_arc.lock().map_err(|_| {
3282 crate::types::BduError::Internal(
3283 "Failed to acquire NPU lock for gate registration".to_string(),
3284 )
3285 })?;
3286 if let Err(e) = npu_lock.register_gate_mapping(
3287 src_cortical_idx,
3288 dst_cortical_idx,
3289 *gate_cortical_idx,
3290 ) {
3291 tracing::error!(
3292 target: "feagi-bdu",
3293 "Gate mapping registration failed for {} -> {} (gate={}): {}",
3294 src_area_id,
3295 dst_area_id,
3296 gate_area_str,
3297 e
3298 );
3299 return Err(crate::types::BduError::Internal(format!(
3300 "Gate registration failed: {}",
3301 e
3302 )));
3303 }
3304 }
3305
3306 let synapse_count = match self.apply_single_morphology_rule(
3308 src_area_id,
3309 dst_area_id,
3310 rule,
3311 ) {
3312 Ok(count) => count,
3313 Err(e) => {
3314 tracing::error!(
3315 target: "feagi-bdu",
3316 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
3317 src_area_id,
3318 dst_area_id,
3319 morphology_id,
3320 rule_keys,
3321 e
3322 );
3323 return Err(e);
3324 }
3325 };
3326 total_synapses += synapse_count;
3327 tracing::debug!(
3328 target: "feagi-bdu",
3329 "Rule {} created {} synapses for {} -> {}",
3330 morphology_id,
3331 synapse_count,
3332 src_area_id,
3333 dst_area_id
3334 );
3335 }
3336
3337 Ok(total_synapses)
3338 }
3339
3340 #[allow(clippy::too_many_arguments)]
3354 fn apply_function_morphology(
3355 &self,
3356 morphology_id: &str,
3357 rule: &serde_json::Value,
3358 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3359 npu: &mut feagi_npu_burst_engine::DynamicNPU,
3360 src_area_id: &CorticalID,
3361 dst_area_id: &CorticalID,
3362 src_idx: u32,
3363 dst_idx: u32,
3364 weight: f32,
3365 psp: f32,
3366 synapse_attractivity: u8,
3367 synapse_type: feagi_npu_neural::SynapseType,
3368 delay_bursts: u8,
3369 ) -> BduResult<usize> {
3370 match morphology_id {
3371 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
3372 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3374 crate::types::BduError::InvalidArea(format!(
3375 "Source area not found: {}",
3376 src_area_id
3377 ))
3378 })?;
3379 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3380 crate::types::BduError::InvalidArea(format!(
3381 "Destination area not found: {}",
3382 dst_area_id
3383 ))
3384 })?;
3385
3386 let src_dimensions = (
3387 src_area.dimensions.width as usize,
3388 src_area.dimensions.height as usize,
3389 src_area.dimensions.depth as usize,
3390 );
3391 let dst_dimensions = (
3392 dst_area.dimensions.width as usize,
3393 dst_area.dimensions.height as usize,
3394 dst_area.dimensions.depth as usize,
3395 );
3396
3397 let transpose = match morphology_id {
3400 "transpose_xy" => Some((1, 0, 2)),
3401 "transpose_yz" => Some((0, 2, 1)),
3402 "transpose_xz" => Some((2, 1, 0)),
3403 _ => None,
3404 };
3405
3406 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3407 let count = apply_projector_morphology_with_dimensions(
3408 npu,
3409 src_idx,
3410 dst_idx,
3411 src_dimensions,
3412 dst_dimensions,
3413 transpose,
3414 None, weight,
3416 psp,
3417 synapse_attractivity,
3418 synapse_type,
3419 0,
3420 delay_bursts,
3421 )?;
3422 npu.rebuild_synapse_index();
3424 Ok(count as usize)
3425 }
3426 "centered_projector" => {
3427 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3428 crate::types::BduError::InvalidArea(format!(
3429 "Source area not found: {}",
3430 src_area_id
3431 ))
3432 })?;
3433 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3434 crate::types::BduError::InvalidArea(format!(
3435 "Destination area not found: {}",
3436 dst_area_id
3437 ))
3438 })?;
3439
3440 let src_dimensions = (
3441 src_area.dimensions.width as usize,
3442 src_area.dimensions.height as usize,
3443 src_area.dimensions.depth as usize,
3444 );
3445 let dst_dimensions = (
3446 dst_area.dimensions.width as usize,
3447 dst_area.dimensions.height as usize,
3448 dst_area.dimensions.depth as usize,
3449 );
3450
3451 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
3452 npu,
3453 src_idx,
3454 dst_idx,
3455 src_dimensions,
3456 dst_dimensions,
3457 weight,
3458 psp,
3459 synapse_attractivity,
3460 synapse_type,
3461 delay_bursts,
3462 )?;
3463 if count > 0 {
3464 npu.rebuild_synapse_index();
3465 }
3466 Ok(count as usize)
3467 }
3468 "episodic_memory" => {
3469 use tracing::trace;
3472 trace!(
3473 target: "feagi-bdu",
3474 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
3475 src_idx, dst_idx
3476 );
3477 Ok(0)
3478 }
3479 "memory_replay" => {
3480 use tracing::trace;
3482 trace!(
3483 target: "feagi-bdu",
3484 "Memory replay morphology: {} -> {} (no physical synapses)",
3485 src_idx, dst_idx
3486 );
3487 Ok(0)
3488 }
3489 "associative_memory" => {
3490 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3494 crate::types::BduError::InvalidArea(format!(
3495 "Source area not found: {}",
3496 src_area_id
3497 ))
3498 })?;
3499 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3500 crate::types::BduError::InvalidArea(format!(
3501 "Destination area not found: {}",
3502 dst_area_id
3503 ))
3504 })?;
3505
3506 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3507 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3508 {
3509 let src_dimensions = (
3510 src_area.dimensions.width as usize,
3511 src_area.dimensions.height as usize,
3512 src_area.dimensions.depth as usize,
3513 );
3514 let dst_dimensions = (
3515 dst_area.dimensions.width as usize,
3516 dst_area.dimensions.height as usize,
3517 dst_area.dimensions.depth as usize,
3518 );
3519 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3520 let count = apply_projector_morphology_with_dimensions(
3521 npu,
3522 src_idx,
3523 dst_idx,
3524 src_dimensions,
3525 dst_dimensions,
3526 None,
3527 None,
3528 weight,
3529 psp,
3530 synapse_attractivity,
3531 synapse_type,
3532 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
3533 delay_bursts,
3534 )?;
3535 npu.rebuild_synapse_index();
3536 Ok(count as usize)
3537 } else {
3538 Ok(0)
3539 }
3540 }
3541 "block_to_block" => {
3542 tracing::warn!(
3543 target: "feagi-bdu",
3544 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
3545 src_idx, dst_idx
3546 );
3547 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3549 crate::types::BduError::InvalidArea(format!(
3550 "Source area not found: {}",
3551 src_area_id
3552 ))
3553 })?;
3554 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3555 crate::types::BduError::InvalidArea(format!(
3556 "Destination area not found: {}",
3557 dst_area_id
3558 ))
3559 })?;
3560
3561 let src_dimensions = (
3562 src_area.dimensions.width as usize,
3563 src_area.dimensions.height as usize,
3564 src_area.dimensions.depth as usize,
3565 );
3566 let dst_dimensions = (
3567 dst_area.dimensions.width as usize,
3568 dst_area.dimensions.height as usize,
3569 dst_area.dimensions.depth as usize,
3570 );
3571
3572 let scalar = if let Some(obj) = rule.as_object() {
3574 if let Some(scalar_arr) =
3576 obj.get("morphology_scalar").and_then(|v| v.as_array())
3577 {
3578 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
3580 } else {
3581 1 }
3583 } else if let Some(arr) = rule.as_array() {
3584 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
3586 } else {
3587 1 };
3589
3590 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
3593 let count = if estimated_neurons > 100_000 {
3594 let _ = npu;
3596
3597 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
3598 npu_arc,
3599 src_idx,
3600 dst_idx,
3601 src_dimensions,
3602 dst_dimensions,
3603 scalar, weight,
3605 psp,
3606 synapse_attractivity,
3607 synapse_type,
3608 delay_bursts,
3609 )? as usize
3610 } else {
3611 tracing::warn!(
3613 target: "feagi-bdu",
3614 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
3615 src_idx, dst_idx, src_dimensions, dst_dimensions
3616 );
3617 let count =
3618 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
3619 npu,
3620 src_idx,
3621 dst_idx,
3622 src_dimensions,
3623 dst_dimensions,
3624 scalar, weight,
3626 psp,
3627 synapse_attractivity,
3628 synapse_type,
3629 delay_bursts,
3630 )? as usize;
3631 tracing::warn!(
3632 target: "feagi-bdu",
3633 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
3634 count
3635 );
3636 if count > 0 {
3638 npu.rebuild_synapse_index();
3639 }
3640 count
3641 };
3642
3643 if count > 0 && estimated_neurons > 100_000 {
3645 let mut npu_lock = npu_arc.lock().unwrap();
3646 npu_lock.rebuild_synapse_index();
3647 }
3648
3649 Ok(count)
3650 }
3651 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
3652 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
3653 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3654 crate::types::BduError::InvalidArea(format!(
3655 "Source area not found: {}",
3656 src_area_id
3657 ))
3658 })?;
3659 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3660 crate::types::BduError::InvalidArea(format!(
3661 "Destination area not found: {}",
3662 dst_area_id
3663 ))
3664 })?;
3665
3666 let src_dimensions = (
3667 src_area.dimensions.width as usize,
3668 src_area.dimensions.height as usize,
3669 src_area.dimensions.depth as usize,
3670 );
3671 let dst_dimensions = (
3672 dst_area.dimensions.width as usize,
3673 dst_area.dimensions.height as usize,
3674 dst_area.dimensions.depth as usize,
3675 );
3676
3677 let (axis, mode) = match morphology_id {
3678 "bitmask_encoder_x" => (
3679 crate::connectivity::core_morphologies::BitmaskAxis::X,
3680 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3681 ),
3682 "bitmask_encoder_y" => (
3683 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3684 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3685 ),
3686 "bitmask_encoder_z" => (
3687 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3688 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3689 ),
3690 "bitmask_decoder_x" => (
3691 crate::connectivity::core_morphologies::BitmaskAxis::X,
3692 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3693 ),
3694 "bitmask_decoder_y" => (
3695 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3696 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3697 ),
3698 "bitmask_decoder_z" => (
3699 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3700 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3701 ),
3702 _ => unreachable!("matched bitmask morphology above"),
3703 };
3704
3705 let count =
3706 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
3707 npu,
3708 src_idx,
3709 dst_idx,
3710 src_dimensions,
3711 dst_dimensions,
3712 axis,
3713 mode,
3714 weight,
3715 psp,
3716 synapse_attractivity,
3717 synapse_type,
3718 delay_bursts,
3719 )?;
3720 if count > 0 {
3721 npu.rebuild_synapse_index();
3722 }
3723 Ok(count as usize)
3724 }
3725 "sweeper" => {
3726 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3727 crate::types::BduError::InvalidArea(format!(
3728 "Destination area not found: {}",
3729 dst_area_id
3730 ))
3731 })?;
3732 let dst_dimensions = (
3733 dst_area.dimensions.width as usize,
3734 dst_area.dimensions.height as usize,
3735 dst_area.dimensions.depth as usize,
3736 );
3737
3738 let count =
3739 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3740 npu,
3741 src_idx,
3742 dst_idx,
3743 dst_dimensions,
3744 weight,
3745 psp,
3746 synapse_attractivity,
3747 synapse_type,
3748 delay_bursts,
3749 )?;
3750 if count > 0 {
3751 npu.rebuild_synapse_index();
3752 }
3753 Ok(count as usize)
3754 }
3755 "last_to_first" => {
3756 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3757 crate::types::BduError::InvalidArea(format!(
3758 "Source area not found: {}",
3759 src_area_id
3760 ))
3761 })?;
3762 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3763 crate::types::BduError::InvalidArea(format!(
3764 "Destination area not found: {}",
3765 dst_area_id
3766 ))
3767 })?;
3768 let src_dimensions = (
3769 src_area.dimensions.width as usize,
3770 src_area.dimensions.height as usize,
3771 src_area.dimensions.depth as usize,
3772 );
3773 let dst_dimensions = (
3774 dst_area.dimensions.width as usize,
3775 dst_area.dimensions.height as usize,
3776 dst_area.dimensions.depth as usize,
3777 );
3778
3779 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3780 npu,
3781 src_idx,
3782 dst_idx,
3783 src_dimensions,
3784 dst_dimensions,
3785 weight,
3786 psp,
3787 synapse_attractivity,
3788 synapse_type,
3789 delay_bursts,
3790 )?;
3791 if count > 0 {
3792 npu.rebuild_synapse_index();
3793 }
3794 Ok(count as usize)
3795 }
3796 "first_to_last" => {
3797 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3798 crate::types::BduError::InvalidArea(format!(
3799 "Source area not found: {}",
3800 src_area_id
3801 ))
3802 })?;
3803 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3804 crate::types::BduError::InvalidArea(format!(
3805 "Destination area not found: {}",
3806 dst_area_id
3807 ))
3808 })?;
3809 let src_dimensions = (
3810 src_area.dimensions.width as usize,
3811 src_area.dimensions.height as usize,
3812 src_area.dimensions.depth as usize,
3813 );
3814 let dst_dimensions = (
3815 dst_area.dimensions.width as usize,
3816 dst_area.dimensions.height as usize,
3817 dst_area.dimensions.depth as usize,
3818 );
3819
3820 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
3821 npu,
3822 src_idx,
3823 dst_idx,
3824 src_dimensions,
3825 dst_dimensions,
3826 weight,
3827 psp,
3828 synapse_attractivity,
3829 synapse_type,
3830 delay_bursts,
3831 )?;
3832 if count > 0 {
3833 npu.rebuild_synapse_index();
3834 }
3835 Ok(count as usize)
3836 }
3837 "rotator_z" => {
3838 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3839 crate::types::BduError::InvalidArea(format!(
3840 "Source area not found: {}",
3841 src_area_id
3842 ))
3843 })?;
3844 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3845 crate::types::BduError::InvalidArea(format!(
3846 "Destination area not found: {}",
3847 dst_area_id
3848 ))
3849 })?;
3850 let src_dimensions = (
3851 src_area.dimensions.width as usize,
3852 src_area.dimensions.height as usize,
3853 src_area.dimensions.depth as usize,
3854 );
3855 let dst_dimensions = (
3856 dst_area.dimensions.width as usize,
3857 dst_area.dimensions.height as usize,
3858 dst_area.dimensions.depth as usize,
3859 );
3860
3861 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3862 npu,
3863 src_idx,
3864 dst_idx,
3865 src_dimensions,
3866 dst_dimensions,
3867 weight,
3868 psp,
3869 synapse_attractivity,
3870 synapse_type,
3871 delay_bursts,
3872 )?;
3873 if count > 0 {
3874 npu.rebuild_synapse_index();
3875 }
3876 Ok(count as usize)
3877 }
3878 _ => {
3879 use tracing::debug;
3882 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3883 Ok(0)
3884 }
3885 }
3886 }
3887
3888 fn apply_single_morphology_rule(
3890 &mut self,
3891 src_area_id: &CorticalID,
3892 dst_area_id: &CorticalID,
3893 rule: &serde_json::Value,
3894 ) -> BduResult<usize> {
3895 let morphology_id = if let Some(arr) = rule.as_array() {
3897 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3898 } else if let Some(obj) = rule.as_object() {
3899 obj.get("morphology_id")
3900 .and_then(|v| v.as_str())
3901 .unwrap_or("")
3902 } else {
3903 return Ok(0);
3904 };
3905
3906 if morphology_id.is_empty() {
3907 return Ok(0);
3908 }
3909
3910 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3912 crate::types::BduError::InvalidMorphology(format!(
3913 "Morphology not found: {}",
3914 morphology_id
3915 ))
3916 })?;
3917
3918 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3920 crate::types::BduError::InvalidArea(format!(
3921 "Source area ID not found: {}",
3922 src_area_id
3923 ))
3924 })?;
3925 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3926 crate::types::BduError::InvalidArea(format!(
3927 "Destination area ID not found: {}",
3928 dst_area_id
3929 ))
3930 })?;
3931
3932 if let Some(ref npu_arc) = self.npu {
3934 let lock_start = std::time::Instant::now();
3935 let mut npu = npu_arc.lock().unwrap();
3936 let lock_wait = lock_start.elapsed();
3937 tracing::debug!(
3938 target: "feagi-bdu",
3939 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3940 lock_wait.as_secs_f64() * 1000.0,
3941 src_area_id,
3942 dst_area_id,
3943 morphology_id
3944 );
3945
3946 let (weight, psp, synapse_type, delay_bursts) =
3947 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3948
3949 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3951 obj.get("synapse_attractivity")
3952 .and_then(|v| v.as_u64())
3953 .unwrap_or(100) as u8
3954 } else {
3955 100 };
3957
3958 match morphology.morphology_type {
3959 feagi_evolutionary::MorphologyType::Functions => {
3960 tracing::debug!(
3961 target: "feagi-bdu",
3962 "apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3963 morphology_id
3964 );
3965 self.apply_function_morphology(
3968 morphology_id,
3969 rule,
3970 npu_arc,
3971 &mut npu,
3972 src_area_id,
3973 dst_area_id,
3974 *src_idx,
3975 *dst_idx,
3976 weight,
3977 psp,
3978 synapse_attractivity,
3979 synapse_type,
3980 delay_bursts,
3981 )
3982 }
3983 feagi_evolutionary::MorphologyType::Vectors => {
3984 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3985
3986 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3988 crate::types::BduError::InvalidArea(format!(
3989 "Destination area not found: {}",
3990 dst_area_id
3991 ))
3992 })?;
3993
3994 let dst_dimensions = (
3995 dst_area.dimensions.width as usize,
3996 dst_area.dimensions.height as usize,
3997 dst_area.dimensions.depth as usize,
3998 );
3999
4000 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
4001 morphology.parameters
4002 {
4003 let vectors_tuples: Vec<(i32, i32, i32)> =
4005 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
4006
4007 let count = apply_vectors_morphology_with_dimensions(
4008 &mut npu,
4009 *src_idx,
4010 *dst_idx,
4011 vectors_tuples,
4012 dst_dimensions,
4013 weight, psp, synapse_attractivity, synapse_type,
4017 delay_bursts,
4018 )?;
4019 npu.rebuild_synapse_index();
4022 Ok(count as usize)
4023 } else {
4024 Ok(0)
4025 }
4026 }
4027 feagi_evolutionary::MorphologyType::Patterns => {
4028 use crate::connectivity::core_morphologies::apply_patterns_morphology;
4029 use crate::connectivity::rules::patterns::{
4030 Pattern3D, PatternElement as RulePatternElement,
4031 };
4032 use feagi_evolutionary::PatternElement as EvoPatternElement;
4033
4034 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
4035 morphology.parameters
4036 else {
4037 return Ok(0);
4038 };
4039
4040 let convert_element =
4041 |element: &EvoPatternElement|
4042 -> crate::types::BduResult<RulePatternElement> {
4043 match element {
4044 EvoPatternElement::Value(value) => {
4045 if *value < 0 {
4046 return Err(crate::types::BduError::InvalidMorphology(
4047 format!(
4048 "Pattern morphology {} contains negative voxel coordinate {}",
4049 morphology_id, value
4050 ),
4051 ));
4052 }
4053 Ok(RulePatternElement::Exact(*value))
4054 }
4055 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
4056 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
4057 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
4058 EvoPatternElement::DirectionPositive => {
4059 Ok(RulePatternElement::DirectionExclusive(
4060 crate::connectivity::rules::patterns::Direction::Positive,
4061 ))
4062 }
4063 EvoPatternElement::DirectionNegative => {
4064 Ok(RulePatternElement::DirectionExclusive(
4065 crate::connectivity::rules::patterns::Direction::Negative,
4066 ))
4067 }
4068 EvoPatternElement::DirectionPositiveInclusive => {
4069 Ok(RulePatternElement::DirectionInclusive(
4070 crate::connectivity::rules::patterns::Direction::Positive,
4071 ))
4072 }
4073 EvoPatternElement::DirectionNegativeInclusive => {
4074 Ok(RulePatternElement::DirectionInclusive(
4075 crate::connectivity::rules::patterns::Direction::Negative,
4076 ))
4077 }
4078 EvoPatternElement::Offset(off) => {
4079 Ok(RulePatternElement::Offset(*off))
4080 }
4081 EvoPatternElement::Range(lo, hi) => {
4082 Ok(RulePatternElement::Range(*lo, *hi))
4083 }
4084 }
4085 };
4086
4087 let mut converted_patterns = Vec::with_capacity(patterns.len());
4088 for pattern_pair in patterns {
4089 if pattern_pair.len() != 2 {
4090 return Err(crate::types::BduError::InvalidMorphology(format!(
4091 "Pattern morphology {} must contain [src, dst] pairs",
4092 morphology_id
4093 )));
4094 }
4095
4096 let src_pattern = &pattern_pair[0];
4097 let dst_pattern = &pattern_pair[1];
4098
4099 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
4100 return Err(crate::types::BduError::InvalidMorphology(format!(
4101 "Pattern morphology {} requires 3-axis patterns",
4102 morphology_id
4103 )));
4104 }
4105
4106 let src: Pattern3D = (
4107 convert_element(&src_pattern[0])?,
4108 convert_element(&src_pattern[1])?,
4109 convert_element(&src_pattern[2])?,
4110 );
4111 let dst: Pattern3D = (
4112 convert_element(&dst_pattern[0])?,
4113 convert_element(&dst_pattern[1])?,
4114 convert_element(&dst_pattern[2])?,
4115 );
4116
4117 converted_patterns.push((src, dst));
4118 }
4119
4120 let count = apply_patterns_morphology(
4121 &mut npu,
4122 *src_idx,
4123 *dst_idx,
4124 converted_patterns,
4125 weight,
4126 psp,
4127 synapse_attractivity,
4128 synapse_type,
4129 delay_bursts,
4130 )?;
4131 if count > 0 {
4132 npu.rebuild_synapse_index();
4133 }
4134 Ok(count as usize)
4135 }
4136 feagi_evolutionary::MorphologyType::Composite => {
4137 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
4138 morphology.parameters
4139 else {
4140 return Ok(0);
4141 };
4142
4143 if morphology_id != "tile" {
4144 use tracing::debug;
4145 debug!(
4146 target: "feagi-bdu",
4147 "Composite morphology {} not yet implemented",
4148 morphology_id
4149 );
4150 return Ok(0);
4151 }
4152
4153 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4154 crate::types::BduError::InvalidArea(format!(
4155 "Source area not found: {}",
4156 src_area_id
4157 ))
4158 })?;
4159 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4160 crate::types::BduError::InvalidArea(format!(
4161 "Destination area not found: {}",
4162 dst_area_id
4163 ))
4164 })?;
4165 let src_dimensions = (
4166 src_area.dimensions.width as usize,
4167 src_area.dimensions.height as usize,
4168 src_area.dimensions.depth as usize,
4169 );
4170 let dst_dimensions = (
4171 dst_area.dimensions.width as usize,
4172 dst_area.dimensions.height as usize,
4173 dst_area.dimensions.depth as usize,
4174 );
4175
4176 let count =
4177 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
4178 &mut npu,
4179 *src_idx,
4180 *dst_idx,
4181 src_dimensions,
4182 dst_dimensions,
4183 weight,
4184 psp,
4185 synapse_attractivity,
4186 synapse_type,
4187 delay_bursts,
4188 )?;
4189 if count > 0 {
4190 npu.rebuild_synapse_index();
4191 }
4192 Ok(count as usize)
4193 }
4194 }
4195 } else {
4196 Ok(0) }
4198 }
4199
4200 pub fn set_npu(
4213 &mut self,
4214 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4215 ) {
4216 self.npu = Some(Arc::clone(&npu));
4217 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
4218
4219 #[cfg(not(feature = "wasm"))]
4222 {
4223 use feagi_state_manager::StateManager;
4224 let state_manager = StateManager::instance();
4225 let state_manager = state_manager.read();
4226 let core_state = state_manager.get_core_state();
4227 core_state.set_neuron_capacity(self.config.max_neurons as u32);
4229 core_state.set_synapse_capacity(self.config.max_synapses as u32);
4230 info!(
4231 target: "feagi-bdu",
4232 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
4233 self.config.max_neurons, self.config.max_synapses
4234 );
4235 }
4236
4237 let existing_area_count = self.cortical_id_to_idx.len();
4244 if existing_area_count > 0 {
4245 match npu.lock() {
4246 Ok(mut npu_lock) => {
4247 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
4248 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
4249 }
4250 info!(
4251 target: "feagi-bdu",
4252 "🔁 Backfilled {} cortical area registrations into NPU",
4253 existing_area_count
4254 );
4255 }
4256 Err(e) => {
4257 warn!(
4258 target: "feagi-bdu",
4259 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
4260 e
4261 );
4262 }
4263 }
4264 }
4265
4266 self.update_all_cached_stats();
4268 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
4269 self.get_neuron_count(), self.get_synapse_count());
4270 }
4271
4272 pub fn has_npu(&self) -> bool {
4274 self.npu.is_some()
4275 }
4276
4277 pub fn get_npu(
4284 &self,
4285 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
4286 {
4287 self.npu.as_ref()
4288 }
4289
4290 #[cfg(feature = "plasticity")]
4293 pub fn set_plasticity_executor(
4294 &mut self,
4295 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
4296 ) {
4297 if let Ok(mut exec) = executor.lock() {
4298 use feagi_npu_plasticity::executor::PlasticityExecutor;
4299 if !exec.is_running() {
4300 exec.start();
4301 }
4302 } else {
4303 warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
4304 }
4305 self.plasticity_executor = Some(executor);
4306 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
4307 }
4308
4309 #[cfg(feature = "plasticity")]
4311 pub fn get_plasticity_executor(
4312 &self,
4313 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
4314 self.plasticity_executor.as_ref()
4315 }
4316
4317 pub fn get_neuron_capacity(&self) -> usize {
4329 self.config.max_neurons
4331 }
4332
4333 pub fn get_synapse_capacity(&self) -> usize {
4345 self.config.max_synapses
4347 }
4348
4349 pub fn update_fatigue_index(&self) -> Option<u8> {
4364 let mut last_calc = match self.last_fatigue_calculation.lock() {
4366 Ok(guard) => guard,
4367 Err(_) => return None, };
4369
4370 let now = std::time::Instant::now();
4371 if now.duration_since(*last_calc).as_secs() < 2 {
4372 return None; }
4374 *last_calc = now;
4375 drop(last_calc);
4376
4377 let regular_neuron_count = self.get_neuron_count();
4379 let regular_neuron_capacity = self.get_neuron_capacity();
4380 let regular_neuron_util = if regular_neuron_capacity > 0 {
4381 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
4382 } else {
4383 0
4384 };
4385
4386 let memory_neuron_util = match StateManager::instance().try_read() {
4390 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
4391 None => {
4392 return None;
4394 }
4395 };
4396
4397 let synapse_count = self.get_synapse_count();
4399 let synapse_capacity = self.get_synapse_capacity();
4400 let synapse_util = if synapse_capacity > 0 {
4401 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
4402 } else {
4403 0
4404 };
4405
4406 let fatigue_index = regular_neuron_util
4408 .max(memory_neuron_util)
4409 .max(synapse_util);
4410
4411 let current_fatigue_active = {
4413 StateManager::instance()
4415 .try_read()
4416 .map(|m| m.get_core_state().is_fatigue_active())
4417 .unwrap_or(false)
4418 };
4419
4420 let new_fatigue_active = if fatigue_index >= 85 {
4421 true
4422 } else if fatigue_index < 80 {
4423 false
4424 } else {
4425 current_fatigue_active };
4427
4428 if let Some(state_manager) = StateManager::instance().try_write() {
4432 let core_state = state_manager.get_core_state();
4433 core_state.set_fatigue_index(fatigue_index);
4434 core_state.set_fatigue_active(new_fatigue_active);
4435 core_state.set_regular_neuron_util(regular_neuron_util);
4436 core_state.set_memory_neuron_util(memory_neuron_util);
4437 core_state.set_synapse_util(synapse_util);
4438 } else {
4439 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
4441 }
4442
4443 if let Some(ref npu) = self.npu {
4445 if let Ok(mut npu_lock) = npu.lock() {
4446 npu_lock.set_fatigue_active(new_fatigue_active);
4447 }
4448 }
4449
4450 trace!(
4451 target: "feagi-bdu",
4452 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
4453 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
4454 );
4455
4456 Some(fatigue_index)
4457 }
4458
4459 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
4476 let area = self
4478 .cortical_areas
4479 .get(cortical_id)
4480 .ok_or_else(|| {
4481 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
4482 })?
4483 .clone();
4484
4485 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
4487 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
4488 })?;
4489
4490 let npu = self
4492 .npu
4493 .as_ref()
4494 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4495
4496 use crate::models::CorticalAreaExt;
4499 let per_voxel_cnt = area.neurons_per_voxel();
4500 let firing_threshold = area.firing_threshold();
4501 let firing_threshold_increment_x = area.firing_threshold_increment_x();
4502 let firing_threshold_increment_y = area.firing_threshold_increment_y();
4503 let firing_threshold_increment_z = area.firing_threshold_increment_z();
4504 let firing_threshold_limit_raw = area.firing_threshold_limit();
4506 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
4507 f32::MAX } else {
4509 firing_threshold_limit_raw
4510 };
4511
4512 if firing_threshold_increment_x != 0.0
4514 || firing_threshold_increment_y != 0.0
4515 || firing_threshold_increment_z != 0.0
4516 {
4517 info!(
4518 target: "feagi-bdu",
4519 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
4520 cortical_id.as_base_64(),
4521 firing_threshold_increment_x,
4522 firing_threshold_increment_y,
4523 firing_threshold_increment_z
4524 );
4525 } else {
4526 if area.properties.contains_key("firing_threshold_increment_x")
4528 || area.properties.contains_key("firing_threshold_increment_y")
4529 || area.properties.contains_key("firing_threshold_increment_z")
4530 {
4531 info!(
4532 target: "feagi-bdu",
4533 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
4534 cortical_id.as_base_64(),
4535 area.properties.get("firing_threshold_increment_x"),
4536 area.properties.get("firing_threshold_increment_y"),
4537 area.properties.get("firing_threshold_increment_z")
4538 );
4539 }
4540 }
4541
4542 let leak_coefficient = area.leak_coefficient();
4543 let excitability = area.neuron_excitability();
4544 let refractory_period = area.refractory_period();
4545 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
4547 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
4548 u16::MAX } else {
4550 consecutive_fire_limit_raw
4551 };
4552 let snooze_length = area.snooze_period();
4553 let mp_charge_accumulation = area.mp_charge_accumulation();
4554
4555 let voxels = area.dimensions.width as usize
4557 * area.dimensions.height as usize
4558 * area.dimensions.depth as usize;
4559 let expected_neurons = voxels * per_voxel_cnt as usize;
4560
4561 trace!(
4562 target: "feagi-bdu",
4563 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
4564 cortical_id.as_base_64(),
4565 area.dimensions.width,
4566 area.dimensions.height,
4567 area.dimensions.depth,
4568 per_voxel_cnt,
4569 expected_neurons
4570 );
4571
4572 let neuron_count: u32 = {
4577 let mut npu_lock = npu
4578 .lock()
4579 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4580 npu_lock
4581 .create_cortical_area_neurons(
4582 *cortical_idx,
4583 area.dimensions.width,
4584 area.dimensions.height,
4585 area.dimensions.depth,
4586 per_voxel_cnt,
4587 firing_threshold,
4588 firing_threshold_increment_x,
4589 firing_threshold_increment_y,
4590 firing_threshold_increment_z,
4591 firing_threshold_limit,
4592 leak_coefficient,
4593 0.0, 0, refractory_period,
4596 excitability,
4597 consecutive_fire_limit,
4598 snooze_length,
4599 mp_charge_accumulation,
4600 )
4601 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
4602 };
4603
4604 trace!(
4605 target: "feagi-bdu",
4606 "Created {} neurons for area {} via NPU",
4607 neuron_count,
4608 cortical_id.as_base_64()
4609 );
4610
4611 {
4614 let mut cache = self.cached_neuron_counts_per_area.write();
4615 cache
4616 .entry(*cortical_id)
4617 .or_insert_with(|| AtomicUsize::new(0))
4618 .store(neuron_count as usize, Ordering::Relaxed);
4619 }
4620
4621 let state_manager = StateManager::instance();
4623 let state_manager = state_manager.read();
4624 state_manager
4625 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
4626
4627 self.cached_neuron_count
4629 .fetch_add(neuron_count as usize, Ordering::Relaxed);
4630
4631 let state_manager = StateManager::instance();
4633 let state_manager = state_manager.read();
4634 let core_state = state_manager.get_core_state();
4635 core_state.add_neuron_count(neuron_count);
4636 core_state.add_regular_neuron_count(neuron_count);
4637
4638 if let Some(npu) = &self.npu {
4640 if let Ok(mut npl) = npu.lock() {
4641 if let Some(v) = area.properties.get("rate_modulated_leak") {
4642 use crate::models::CorticalAreaExt;
4643 let idxs: Vec<usize> = npl
4644 .get_neurons_in_cortical_area(*cortical_idx)
4645 .into_iter()
4646 .map(|id| id as usize)
4647 .collect();
4648 npl.sync_rate_modulated_leak_from_cortical_property(
4649 *cortical_idx,
4650 v,
4651 area.leak_coefficient(),
4652 idxs,
4653 );
4654 } else {
4655 npl.remove_rate_modulated_leak(*cortical_idx);
4656 }
4657 }
4658 }
4659
4660 Ok(neuron_count)
4668 }
4669
4670 #[allow(clippy::too_many_arguments)]
4694 pub fn add_neuron(
4695 &mut self,
4696 cortical_id: &CorticalID,
4697 x: u32,
4698 y: u32,
4699 z: u32,
4700 firing_threshold: f32,
4701 firing_threshold_limit: f32,
4702 leak_coefficient: f32,
4703 resting_potential: f32,
4704 neuron_type: u8,
4705 refractory_period: u16,
4706 excitability: f32,
4707 consecutive_fire_limit: u16,
4708 snooze_length: u16,
4709 mp_charge_accumulation: bool,
4710 ) -> BduResult<u64> {
4711 self.add_neuron_with_area_stats(
4712 cortical_id,
4713 x,
4714 y,
4715 z,
4716 firing_threshold,
4717 firing_threshold_limit,
4718 leak_coefficient,
4719 resting_potential,
4720 neuron_type,
4721 refractory_period,
4722 excitability,
4723 consecutive_fire_limit,
4724 snooze_length,
4725 mp_charge_accumulation,
4726 true,
4727 )
4728 }
4729
4730 #[allow(clippy::too_many_arguments)]
4736 pub fn add_auxiliary_neuron(
4737 &mut self,
4738 cortical_id: &CorticalID,
4739 x: u32,
4740 y: u32,
4741 z: u32,
4742 firing_threshold: f32,
4743 firing_threshold_limit: f32,
4744 leak_coefficient: f32,
4745 resting_potential: f32,
4746 neuron_type: u8,
4747 refractory_period: u16,
4748 excitability: f32,
4749 consecutive_fire_limit: u16,
4750 snooze_length: u16,
4751 mp_charge_accumulation: bool,
4752 ) -> BduResult<u64> {
4753 self.add_neuron_with_area_stats(
4754 cortical_id,
4755 x,
4756 y,
4757 z,
4758 firing_threshold,
4759 firing_threshold_limit,
4760 leak_coefficient,
4761 resting_potential,
4762 neuron_type,
4763 refractory_period,
4764 excitability,
4765 consecutive_fire_limit,
4766 snooze_length,
4767 mp_charge_accumulation,
4768 false,
4769 )
4770 }
4771
4772 #[allow(clippy::too_many_arguments)]
4773 fn add_neuron_with_area_stats(
4774 &mut self,
4775 cortical_id: &CorticalID,
4776 x: u32,
4777 y: u32,
4778 z: u32,
4779 firing_threshold: f32,
4780 firing_threshold_limit: f32,
4781 leak_coefficient: f32,
4782 resting_potential: f32,
4783 neuron_type: u8,
4784 refractory_period: u16,
4785 excitability: f32,
4786 consecutive_fire_limit: u16,
4787 snooze_length: u16,
4788 mp_charge_accumulation: bool,
4789 update_area_stats: bool,
4790 ) -> BduResult<u64> {
4791 if !self.cortical_areas.contains_key(cortical_id) {
4793 return Err(BduError::InvalidArea(format!(
4794 "Cortical area {} not found",
4795 cortical_id
4796 )));
4797 }
4798
4799 let cortical_idx = *self
4800 .cortical_id_to_idx
4801 .get(cortical_id)
4802 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
4803
4804 let npu = self
4806 .npu
4807 .as_ref()
4808 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4809
4810 let mut npu_lock = npu
4811 .lock()
4812 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4813
4814 let neuron_id = npu_lock
4816 .add_neuron(
4817 firing_threshold,
4818 firing_threshold_limit,
4819 leak_coefficient,
4820 resting_potential,
4821 neuron_type as i32,
4822 refractory_period,
4823 excitability,
4824 consecutive_fire_limit,
4825 snooze_length,
4826 mp_charge_accumulation,
4827 cortical_idx,
4828 x,
4829 y,
4830 z,
4831 )
4832 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
4833
4834 trace!(
4835 target: "feagi-bdu",
4836 "Created neuron {} in area {} at ({}, {}, {})",
4837 neuron_id.0,
4838 cortical_id,
4839 x,
4840 y,
4841 z
4842 );
4843
4844 let state_manager = StateManager::instance();
4846 let state_manager = state_manager.read();
4847 let core_state = state_manager.get_core_state();
4848 core_state.add_neuron_count(1);
4849 core_state.add_regular_neuron_count(1);
4850 if update_area_stats {
4851 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4852 }
4853
4854 Ok(neuron_id.0 as u64)
4855 }
4856
4857 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
4868 let npu = self
4870 .npu
4871 .as_ref()
4872 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4873
4874 let mut npu_lock = npu
4875 .lock()
4876 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4877
4878 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
4879 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
4880
4881 let deleted = npu_lock.delete_neuron(neuron_id as u32);
4882
4883 if deleted {
4884 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
4885
4886 let state_manager = StateManager::instance();
4888 let state_manager = state_manager.read();
4889 let core_state = state_manager.get_core_state();
4890 core_state.subtract_neuron_count(1);
4891 core_state.subtract_regular_neuron_count(1);
4892 if let Some(cortical_id) = cortical_id {
4893 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4894 }
4895
4896 }
4900
4901 Ok(deleted)
4902 }
4903
4904 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
4918 let src_area = self
4920 .cortical_areas
4921 .get(src_cortical_id)
4922 .ok_or_else(|| {
4923 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
4924 })?
4925 .clone();
4926
4927 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
4929 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
4930 _ => return Ok(0), };
4932
4933 let src_cortical_idx = *self
4934 .cortical_id_to_idx
4935 .get(src_cortical_id)
4936 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
4937
4938 let mut total_synapses = 0u32;
4939 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
4943 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
4945 Ok(id) => id,
4946 Err(_) => {
4947 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
4948 continue;
4949 }
4950 };
4951
4952 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
4954 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
4955 continue;
4956 }
4957
4958 let synapse_count =
4960 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
4961 total_synapses += synapse_count as u32;
4962
4963 upstream_updates.push((dst_cortical_id, src_cortical_idx));
4966 }
4967
4968 for (dst_id, src_idx) in upstream_updates {
4970 self.add_upstream_area(&dst_id, src_idx);
4971 }
4972
4973 trace!(
4974 target: "feagi-bdu",
4975 "Created {} synapses for area {} via NPU",
4976 total_synapses,
4977 src_cortical_id
4978 );
4979
4980 if total_synapses > 0 {
4983 let mut cache = self.cached_synapse_counts_per_area.write();
4984 cache
4985 .entry(*src_cortical_id)
4986 .or_insert_with(|| AtomicUsize::new(0))
4987 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4988 }
4989
4990 self.cached_synapse_count
4992 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4993
4994 if total_synapses > 0 {
4996 let state_manager = StateManager::instance();
4997 let state_manager = state_manager.read();
4998 let core_state = state_manager.get_core_state();
4999 core_state.add_synapse_count(total_synapses);
5000 }
5001
5002 Ok(total_synapses)
5003 }
5004
5005 pub fn has_neuron(&self, neuron_id: u64) -> bool {
5024 if let Some(ref npu) = self.npu {
5025 if let Ok(npu_lock) = npu.lock() {
5026 npu_lock.is_neuron_valid(neuron_id as u32)
5028 } else {
5029 false
5030 }
5031 } else {
5032 false
5033 }
5034 }
5035
5036 pub fn get_neuron_count(&self) -> usize {
5048 if let Some(ref npu) = self.npu {
5050 if let Ok(npu_lock) = npu.try_lock() {
5051 let fresh_count = npu_lock.get_neuron_count();
5052 self.cached_neuron_count
5053 .store(fresh_count, Ordering::Relaxed);
5054 }
5055 }
5057
5058 self.cached_neuron_count.load(Ordering::Relaxed)
5060 }
5061
5062 pub fn update_cached_neuron_count(&self) {
5068 if let Some(ref npu) = self.npu {
5069 if let Ok(npu_lock) = npu.try_lock() {
5070 let count = npu_lock.get_neuron_count();
5071 self.cached_neuron_count.store(count, Ordering::Relaxed);
5072 }
5073 }
5074 }
5075
5076 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
5080 let npu = self.npu.as_ref()?;
5081 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
5082 let npu_lock = npu.lock().ok()?;
5083 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
5084 drop(npu_lock);
5085
5086 let mut cache = self.cached_neuron_counts_per_area.write();
5087 cache
5088 .entry(*cortical_id)
5089 .or_insert_with(|| AtomicUsize::new(0))
5090 .store(count, Ordering::Relaxed);
5091
5092 let state_manager = StateManager::instance();
5094 let state_manager = state_manager.read();
5095 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5096
5097 self.update_cached_neuron_count();
5098
5099 Some(count)
5100 }
5101
5102 pub fn get_synapse_count(&self) -> usize {
5114 if let Some(ref npu) = self.npu {
5116 if let Ok(npu_lock) = npu.try_lock() {
5117 let fresh_count = npu_lock.get_synapse_count();
5118 self.cached_synapse_count
5119 .store(fresh_count, Ordering::Relaxed);
5120 }
5121 }
5123
5124 self.cached_synapse_count.load(Ordering::Relaxed)
5126 }
5127
5128 pub fn update_cached_synapse_count(&self) {
5134 if let Some(ref npu) = self.npu {
5135 if let Ok(npu_lock) = npu.try_lock() {
5136 let count = npu_lock.get_synapse_count();
5137 self.cached_synapse_count.store(count, Ordering::Relaxed);
5138 }
5139 }
5140 }
5141
5142 pub fn update_all_cached_stats(&self) {
5148 self.update_cached_neuron_count();
5149 self.update_cached_synapse_count();
5150 }
5151
5152 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
5163 #[cfg(feature = "plasticity")]
5168 {
5169 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
5170 return (0, 0, 0);
5171 }
5172 }
5173 if let Some(ref npu) = self.npu {
5174 if let Ok(npu_lock) = npu.lock() {
5175 npu_lock
5176 .get_neuron_coordinates(neuron_id as u32)
5177 .unwrap_or((0, 0, 0))
5178 } else {
5179 (0, 0, 0)
5180 }
5181 } else {
5182 (0, 0, 0)
5183 }
5184 }
5185
5186 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
5197 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
5198 }
5199
5200 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
5206 #[cfg(feature = "plasticity")]
5207 {
5208 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
5209 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
5210 }
5211 }
5212 if let Some(ref npu) = self.npu {
5213 if let Ok(npu_lock) = npu.lock() {
5214 npu_lock.get_neuron_cortical_area(neuron_id as u32)
5215 } else {
5216 None
5217 }
5218 } else {
5219 None
5220 }
5221 }
5222
5223 #[cfg(feature = "plasticity")]
5225 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
5226 let exec = self.get_plasticity_executor()?;
5227 let guard = exec.lock().ok()?;
5228 guard
5229 .memory_neuron_detail(neuron_id)
5230 .map(|d| d.cortical_area_idx)
5231 }
5232
5233 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
5244 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
5246 Some(idx) => *idx,
5247 None => return Vec::new(),
5248 };
5249
5250 if let Some(ref npu) = self.npu {
5251 if let Ok(npu_lock) = npu.lock() {
5252 npu_lock
5254 .get_neurons_in_cortical_area(cortical_idx)
5255 .into_iter()
5256 .map(|id| id as u64)
5257 .collect()
5258 } else {
5259 Vec::new()
5260 }
5261 } else {
5262 Vec::new()
5263 }
5264 }
5265
5266 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
5277 if let Some(ref npu) = self.npu {
5278 if let Ok(npu_lock) = npu.lock() {
5279 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
5280 } else {
5281 Vec::new()
5282 }
5283 } else {
5284 Vec::new()
5285 }
5286 }
5287
5288 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
5299 if let Some(ref npu) = self.npu {
5300 if let Ok(npu_lock) = npu.lock() {
5301 npu_lock.get_incoming_synapses(target_neuron_id as u32)
5302 } else {
5303 Vec::new()
5304 }
5305 } else {
5306 Vec::new()
5307 }
5308 }
5309
5310 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5336 let is_memory_area = self
5337 .cortical_areas
5338 .get(cortical_id)
5339 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
5340 .is_some();
5341
5342 if is_memory_area {
5343 #[cfg(feature = "plasticity")]
5347 if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
5348 return count;
5349 }
5350
5351 return StateManager::instance()
5353 .try_read()
5354 .and_then(|state_manager| {
5355 state_manager
5356 .get_cortical_area_stats(&cortical_id.as_base_64())
5357 .map(|stats| stats.neuron_count)
5358 })
5359 .unwrap_or(0);
5360 }
5361
5362 let cache = self.cached_neuron_counts_per_area.read();
5364 cache
5365 .get(cortical_id)
5366 .map(|count| count.load(Ordering::Relaxed))
5367 .unwrap_or(0)
5368 }
5369
5370 #[cfg(feature = "plasticity")]
5372 fn active_memory_neuron_count_from_plasticity(
5373 &self,
5374 cortical_id: &CorticalID,
5375 ) -> Option<usize> {
5376 let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
5377 let exec = self.get_plasticity_executor()?;
5378 let guard = exec.lock().ok()?;
5379 let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
5380 Some(runtime.active_memory_neuron_count())
5381 }
5382
5383 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
5390 let mut result = Vec::new();
5391
5392 for cortical_id in self.cortical_areas.keys() {
5393 let count = self.get_neuron_count_in_area(cortical_id);
5394 if count > 0 {
5395 result.push((cortical_id.to_string(), count));
5396 }
5397 }
5398
5399 result
5400 }
5401
5402 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
5413 self.get_neuron_count_in_area(cortical_id) > 0
5414 }
5415
5416 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5432 let cache = self.cached_synapse_counts_per_area.read();
5434 cache
5435 .get(cortical_id)
5436 .map(|count| count.load(Ordering::Relaxed))
5437 .unwrap_or(0)
5438 }
5439
5440 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5450 if !self.cortical_id_to_idx.contains_key(cortical_id) {
5451 return 0;
5452 }
5453
5454 if let Some(state_manager) = StateManager::instance().try_read() {
5455 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
5456 return stats.incoming_synapse_count;
5457 }
5458 }
5459
5460 0
5461 }
5462
5463 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5473 if !self.cortical_id_to_idx.contains_key(cortical_id) {
5474 return 0;
5475 }
5476
5477 if let Some(state_manager) = StateManager::instance().try_read() {
5478 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
5479 return stats.outgoing_synapse_count;
5480 }
5481 }
5482
5483 0
5484 }
5485
5486 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
5498 let synapses = self.get_outgoing_synapses(source_neuron_id);
5499 synapses
5500 .iter()
5501 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
5502 }
5503
5504 pub fn get_connection_weight(
5516 &self,
5517 source_neuron_id: u64,
5518 target_neuron_id: u64,
5519 ) -> Option<f32> {
5520 let synapses = self.get_outgoing_synapses(source_neuron_id);
5521 synapses
5522 .iter()
5523 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
5524 .map(|(_, weight, _, _)| *weight)
5525 }
5526
5527 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
5538 let neurons = self.get_neurons_in_area(cortical_id);
5539 let neuron_count = neurons.len();
5540
5541 if neuron_count == 0 {
5542 return (0, 0, 0.0);
5543 }
5544
5545 let mut total_synapses = 0;
5546 for neuron_id in neurons {
5547 total_synapses += self.get_outgoing_synapses(neuron_id).len();
5548 }
5549
5550 let avg_synapses = total_synapses as f32 / neuron_count as f32;
5551
5552 (neuron_count, total_synapses, avg_synapses)
5553 }
5554
5555 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
5566 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
5567 self.cortical_idx_to_id.get(&cortical_idx).copied()
5568 }
5569
5570 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
5581 let area = match self.cortical_areas.get(cortical_id) {
5582 Some(a) => a,
5583 None => return 0.0,
5584 };
5585
5586 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5587 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5588
5589 if volume == 0 {
5590 return 0.0;
5591 }
5592
5593 neuron_count as f32 / volume as f32
5594 }
5595
5596 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
5603 let mut stats = Vec::new();
5604
5605 for cortical_id in self.cortical_areas.keys() {
5606 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5607 let synapse_count = self.get_synapse_count_in_area(cortical_id);
5608 let density = self.get_neuron_density(cortical_id);
5609
5610 stats.push((
5611 cortical_id.to_string(),
5612 neuron_count,
5613 synapse_count,
5614 density,
5615 ));
5616 }
5617
5618 stats
5619 }
5620
5621 pub fn get_config(&self) -> &ConnectomeConfig {
5627 &self.config
5628 }
5629
5630 pub fn set_config(&mut self, config: ConnectomeConfig) {
5632 self.config = config;
5633 }
5634
5635 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
5658 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
5659
5660 use feagi_structures::genomic::cortical_area::{
5661 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
5662 };
5663
5664 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
5666 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
5667 })?;
5668
5669 let core_position = (0, 0, 0).into();
5671
5672 let death_id = CoreCorticalType::Death.to_cortical_id();
5674 if !self.cortical_areas.contains_key(&death_id) {
5675 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
5676 let death_area = CorticalArea::new(
5677 death_id,
5678 0, "_death".to_string(),
5680 core_dimensions,
5681 core_position,
5682 CorticalAreaType::Core(CoreCorticalType::Death),
5683 )
5684 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
5685 match self.add_cortical_area(death_area) {
5686 Ok(idx) => {
5687 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
5688 }
5689 Err(e) => {
5690 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
5691 return Err(e);
5692 }
5693 }
5694 } else {
5695 info!(target: "feagi-bdu", " ✓ _death area already exists");
5696 }
5697
5698 let power_id = CoreCorticalType::Power.to_cortical_id();
5700 if !self.cortical_areas.contains_key(&power_id) {
5701 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
5702 let power_area = CorticalArea::new(
5703 power_id,
5704 1, "_power".to_string(),
5706 core_dimensions,
5707 core_position,
5708 CorticalAreaType::Core(CoreCorticalType::Power),
5709 )
5710 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
5711 match self.add_cortical_area(power_area) {
5712 Ok(idx) => {
5713 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
5714 }
5715 Err(e) => {
5716 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
5717 return Err(e);
5718 }
5719 }
5720 } else {
5721 info!(target: "feagi-bdu", " ✓ _power area already exists");
5722 }
5723
5724 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
5726 let pain_id = CoreCorticalType::Pain.to_cortical_id();
5727 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
5728 let fear_id = CoreCorticalType::Fear.to_cortical_id();
5729 let hope_id = CoreCorticalType::Hope.to_cortical_id();
5730 if !self.cortical_areas.contains_key(&fatigue_id) {
5731 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
5732 let fatigue_area = CorticalArea::new(
5733 fatigue_id,
5734 2, "_fatigue".to_string(),
5736 core_dimensions,
5737 core_position,
5738 CorticalAreaType::Core(CoreCorticalType::Fatigue),
5739 )
5740 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
5741 match self.add_cortical_area(fatigue_area) {
5742 Ok(idx) => {
5743 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
5744 }
5745 Err(e) => {
5746 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
5747 return Err(e);
5748 }
5749 }
5750 } else {
5751 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
5752 }
5753
5754 if !self.cortical_areas.contains_key(&pain_id) {
5756 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
5757 let pain_area = CorticalArea::new(
5758 pain_id,
5759 3, "_pain".to_string(),
5761 core_dimensions,
5762 core_position,
5763 CorticalAreaType::Core(CoreCorticalType::Pain),
5764 )
5765 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
5766 match self.add_cortical_area(pain_area) {
5767 Ok(idx) => {
5768 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
5769 }
5770 Err(e) => {
5771 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
5772 return Err(e);
5773 }
5774 }
5775 } else {
5776 info!(target: "feagi-bdu", " ✓ _pain area already exists");
5777 }
5778
5779 if !self.cortical_areas.contains_key(&pleasure_id) {
5781 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
5782 let pleasure_area = CorticalArea::new(
5783 pleasure_id,
5784 4, "_pleasure".to_string(),
5786 core_dimensions,
5787 core_position,
5788 CorticalAreaType::Core(CoreCorticalType::Pleasure),
5789 )
5790 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
5791 match self.add_cortical_area(pleasure_area) {
5792 Ok(idx) => {
5793 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
5794 }
5795 Err(e) => {
5796 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
5797 return Err(e);
5798 }
5799 }
5800 } else {
5801 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
5802 }
5803
5804 if !self.cortical_areas.contains_key(&fear_id) {
5806 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
5807 let fear_area = CorticalArea::new(
5808 fear_id,
5809 5, "_fear".to_string(),
5811 core_dimensions,
5812 core_position,
5813 CorticalAreaType::Core(CoreCorticalType::Fear),
5814 )
5815 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
5816 match self.add_cortical_area(fear_area) {
5817 Ok(idx) => {
5818 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
5819 }
5820 Err(e) => {
5821 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
5822 return Err(e);
5823 }
5824 }
5825 } else {
5826 info!(target: "feagi-bdu", " ✓ _fear area already exists");
5827 }
5828
5829 if !self.cortical_areas.contains_key(&hope_id) {
5831 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
5832 let hope_area = CorticalArea::new(
5833 hope_id,
5834 6, "_hope".to_string(),
5836 core_dimensions,
5837 core_position,
5838 CorticalAreaType::Core(CoreCorticalType::Hope),
5839 )
5840 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
5841 match self.add_cortical_area(hope_area) {
5842 Ok(idx) => {
5843 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
5844 }
5845 Err(e) => {
5846 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
5847 return Err(e);
5848 }
5849 }
5850 } else {
5851 info!(target: "feagi-bdu", " ✓ _hope area already exists");
5852 }
5853
5854 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
5855 Ok(())
5856 }
5857
5858 #[deprecated(
5875 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
5876 )]
5877 #[allow(deprecated)]
5878 pub fn save_genome_to_json(
5879 &self,
5880 genome_id: Option<String>,
5881 genome_title: Option<String>,
5882 ) -> BduResult<String> {
5883 let mut brain_regions_with_parents = std::collections::HashMap::new();
5885
5886 for region_id in self.brain_regions.get_all_region_ids() {
5887 if let Some(region) = self.brain_regions.get_region(region_id) {
5888 let parent_id = self
5889 .brain_regions
5890 .get_parent(region_id)
5891 .map(|s| s.to_string());
5892 brain_regions_with_parents
5893 .insert(region_id.to_string(), (region.clone(), parent_id));
5894 }
5895 }
5896
5897 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
5899 &self.cortical_areas,
5900 &brain_regions_with_parents,
5901 genome_id,
5902 genome_title,
5903 )?)
5904 }
5905
5906 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
5937 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
5938
5939 self.cortical_areas.clear();
5941 self.cortical_id_to_idx.clear();
5942 self.cortical_idx_to_id.clear();
5943 self.next_cortical_idx = 7;
5945 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)");
5946
5947 self.brain_regions = BrainRegionHierarchy::new();
5949
5950 if let Some(ref npu) = self.npu {
5952 let mut npu_lock = npu
5953 .lock()
5954 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5955 npu_lock
5956 .reset_for_new_genome()
5957 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
5958 }
5959
5960 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
5961 Ok(())
5962 }
5963
5964 pub fn resize_for_genome(
5974 &mut self,
5975 genome: &feagi_evolutionary::RuntimeGenome,
5976 ) -> BduResult<()> {
5977 self.morphology_registry = genome.morphologies.clone();
5979 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
5980
5981 let required_neurons = genome.stats.innate_neuron_count;
5983 let required_synapses = genome.stats.innate_synapse_count;
5984
5985 info!(target: "feagi-bdu",
5986 "Genome requires: {} neurons, {} synapses",
5987 required_neurons,
5988 required_synapses
5989 );
5990
5991 let mut total_voxels = 0;
5993 for area in genome.cortical_areas.values() {
5994 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5995 }
5996
5997 info!(target: "feagi-bdu",
5998 "Genome has {} cortical areas with {} total voxels",
5999 genome.cortical_areas.len(),
6000 total_voxels
6001 );
6002
6003 Ok(())
6008 }
6009
6010 pub fn create_synapse(
6029 &mut self,
6030 source_neuron_id: u64,
6031 target_neuron_id: u64,
6032 weight: f32,
6033 psp: f32,
6034 synapse_type: u8,
6035 ) -> BduResult<()> {
6036 let npu = self
6038 .npu
6039 .as_ref()
6040 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6041
6042 let mut npu_lock = npu
6043 .lock()
6044 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6045
6046 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
6048 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
6049
6050 if !source_exists {
6051 return Err(BduError::InvalidNeuron(format!(
6052 "Source neuron {} not found",
6053 source_neuron_id
6054 )));
6055 }
6056 if !target_exists {
6057 return Err(BduError::InvalidNeuron(format!(
6058 "Target neuron {} not found",
6059 target_neuron_id
6060 )));
6061 }
6062
6063 let syn_type = if synapse_type == 0 {
6065 feagi_npu_neural::synapse::SynapseType::Excitatory
6066 } else {
6067 feagi_npu_neural::synapse::SynapseType::Inhibitory
6068 };
6069
6070 let synapse_idx = npu_lock
6071 .add_synapse(
6072 NeuronId(source_neuron_id as u32),
6073 NeuronId(target_neuron_id as u32),
6074 feagi_npu_neural::types::SynapticWeight(weight),
6075 feagi_npu_neural::types::SynapticPsp(psp),
6076 syn_type,
6077 0,
6078 1,
6079 )
6080 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
6081
6082 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
6083 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
6084
6085 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
6086 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
6087 let source_cortical_id =
6088 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6089 let target_cortical_id =
6090 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6091
6092 let state_manager = StateManager::instance();
6093 let state_manager = state_manager.read();
6094 let core_state = state_manager.get_core_state();
6095 core_state.add_synapse_count(1);
6096 if let Some(cortical_id) = source_cortical_id {
6097 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
6098 }
6099 if let Some(cortical_id) = target_cortical_id {
6100 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
6101 }
6102
6103 Ok(())
6108 }
6109
6110 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
6113 if let Some(ref npu) = self.npu {
6114 if let Ok(mut npu_lock) = npu.lock() {
6115 let mut psp_uniform_flags = ahash::AHashMap::new();
6117 let mut mp_driven_psp_flags = ahash::AHashMap::new();
6118 let mut postsynaptic_current_flags = ahash::AHashMap::new();
6119 let mut degeneration_flags = ahash::AHashMap::new();
6120
6121 for (cortical_id, area) in &self.cortical_areas {
6122 let default_psp_uniform = *cortical_id
6125 == CoreCorticalType::Power.to_cortical_id()
6126 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
6127 let psp_uniform = area
6128 .get_property("psp_uniform_distribution")
6129 .and_then(|v| v.as_bool())
6130 .unwrap_or(default_psp_uniform);
6131 psp_uniform_flags.insert(*cortical_id, psp_uniform);
6132
6133 let mp_driven_psp = area
6135 .get_property("mp_driven_psp")
6136 .and_then(|v| v.as_bool())
6137 .unwrap_or(false);
6138 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
6139
6140 let postsynaptic_current = area
6142 .get_property("postsynaptic_current")
6143 .and_then(|v| v.as_f64())
6144 .unwrap_or(1.0) as f32;
6145 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
6146
6147 let degeneration = area
6149 .get_property("degeneration")
6150 .and_then(|v| v.as_f64())
6151 .unwrap_or(0.0) as f32;
6152 if degeneration > 0.0 {
6153 degeneration_flags.insert(*cortical_id, degeneration);
6154 }
6155 }
6156
6157 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
6159 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
6160 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
6161 npu_lock.set_degeneration_flags(degeneration_flags);
6162
6163 trace!(
6164 target: "feagi-bdu",
6165 "Synchronized cortical area flags to NPU ({} areas)",
6166 self.cortical_areas.len()
6167 );
6168 }
6169 }
6170
6171 Ok(())
6172 }
6173
6174 pub fn get_synapse(
6186 &self,
6187 source_neuron_id: u64,
6188 target_neuron_id: u64,
6189 ) -> Option<(f32, f32, u8)> {
6190 let npu = self.npu.as_ref()?;
6192 let npu_lock = npu.lock().ok()?;
6193
6194 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
6197
6198 for (source_id, weight, psp, synapse_type) in incoming {
6200 if source_id == source_neuron_id as u32 {
6201 return Some((weight, psp, synapse_type));
6202 }
6203 }
6204
6205 None
6206 }
6207
6208 pub fn update_synapse_weight(
6221 &mut self,
6222 source_neuron_id: u64,
6223 target_neuron_id: u64,
6224 new_weight: f32,
6225 ) -> BduResult<()> {
6226 let npu = self
6228 .npu
6229 .as_ref()
6230 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6231
6232 let mut npu_lock = npu
6233 .lock()
6234 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6235
6236 let updated = npu_lock.update_synapse_weight(
6238 NeuronId(source_neuron_id as u32),
6239 NeuronId(target_neuron_id as u32),
6240 feagi_npu_neural::types::SynapticWeight(new_weight),
6241 );
6242
6243 if updated {
6244 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
6245 Ok(())
6246 } else {
6247 Err(BduError::InvalidSynapse(format!(
6248 "Synapse {} -> {} not found",
6249 source_neuron_id, target_neuron_id
6250 )))
6251 }
6252 }
6253
6254 pub fn remove_synapse(
6266 &mut self,
6267 source_neuron_id: u64,
6268 target_neuron_id: u64,
6269 ) -> BduResult<bool> {
6270 let npu = self
6272 .npu
6273 .as_ref()
6274 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6275
6276 let mut npu_lock = npu
6277 .lock()
6278 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6279
6280 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
6281 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
6282 let source_cortical_id =
6283 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6284 let target_cortical_id =
6285 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6286
6287 let removed = npu_lock.remove_synapse(
6289 NeuronId(source_neuron_id as u32),
6290 NeuronId(target_neuron_id as u32),
6291 );
6292
6293 if removed {
6294 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
6295
6296 let state_manager = StateManager::instance();
6298 let state_manager = state_manager.read();
6299 let core_state = state_manager.get_core_state();
6300 core_state.subtract_synapse_count(1);
6301 if let Some(cortical_id) = source_cortical_id {
6302 state_manager
6303 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
6304 }
6305 if let Some(cortical_id) = target_cortical_id {
6306 state_manager
6307 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
6308 }
6309 }
6310
6311 Ok(removed)
6312 }
6313
6314 pub fn batch_create_neurons(
6332 &mut self,
6333 cortical_id: &CorticalID,
6334 neurons: Vec<NeuronData>,
6335 ) -> BduResult<Vec<u64>> {
6336 let npu = self
6338 .npu
6339 .as_ref()
6340 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6341
6342 let mut npu_lock = npu
6343 .lock()
6344 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6345
6346 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
6348 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6349 })?;
6350 let cortical_idx = area.cortical_idx;
6351
6352 let count = neurons.len();
6353
6354 let mut x_coords = Vec::with_capacity(count);
6356 let mut y_coords = Vec::with_capacity(count);
6357 let mut z_coords = Vec::with_capacity(count);
6358 let mut firing_thresholds = Vec::with_capacity(count);
6359 let mut threshold_limits = Vec::with_capacity(count);
6360 let mut leak_coeffs = Vec::with_capacity(count);
6361 let mut resting_potentials = Vec::with_capacity(count);
6362 let mut neuron_types = Vec::with_capacity(count);
6363 let mut refractory_periods = Vec::with_capacity(count);
6364 let mut excitabilities = Vec::with_capacity(count);
6365 let mut consec_fire_limits = Vec::with_capacity(count);
6366 let mut snooze_lengths = Vec::with_capacity(count);
6367 let mut mp_accums = Vec::with_capacity(count);
6368 let mut cortical_areas = Vec::with_capacity(count);
6369
6370 for (
6371 x,
6372 y,
6373 z,
6374 threshold,
6375 threshold_limit,
6376 leak,
6377 resting,
6378 ntype,
6379 refract,
6380 excit,
6381 consec_limit,
6382 snooze,
6383 mp_accum,
6384 ) in neurons
6385 {
6386 x_coords.push(x);
6387 y_coords.push(y);
6388 z_coords.push(z);
6389 firing_thresholds.push(threshold);
6390 threshold_limits.push(threshold_limit);
6391 leak_coeffs.push(leak);
6392 resting_potentials.push(resting);
6393 neuron_types.push(ntype);
6394 refractory_periods.push(refract);
6395 excitabilities.push(excit);
6396 consec_fire_limits.push(consec_limit);
6397 snooze_lengths.push(snooze);
6398 mp_accums.push(mp_accum);
6399 cortical_areas.push(cortical_idx);
6400 }
6401
6402 let first_neuron_id = npu_lock.get_neuron_count() as u32;
6404
6405 let firing_thresholds_t = firing_thresholds;
6410 let threshold_limits_t = threshold_limits;
6411 let resting_potentials_t = resting_potentials;
6412 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
6413 firing_thresholds_t,
6414 threshold_limits_t,
6415 leak_coeffs,
6416 resting_potentials_t,
6417 neuron_types,
6418 refractory_periods,
6419 excitabilities,
6420 consec_fire_limits,
6421 snooze_lengths,
6422 mp_accums,
6423 cortical_areas,
6424 x_coords,
6425 y_coords,
6426 z_coords,
6427 );
6428
6429 let mut neuron_ids = Vec::with_capacity(count);
6431 for i in 0..neurons_created {
6432 neuron_ids.push((first_neuron_id + i) as u64);
6433 }
6434
6435 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
6436
6437 let state_manager = StateManager::instance();
6439 let state_manager = state_manager.read();
6440 let core_state = state_manager.get_core_state();
6441 core_state.add_neuron_count(neurons_created);
6442 core_state.add_regular_neuron_count(neurons_created);
6443 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
6444
6445 {
6447 let mut cache = self.cached_neuron_counts_per_area.write();
6448 cache
6449 .entry(*cortical_id)
6450 .or_insert_with(|| AtomicUsize::new(0))
6451 .fetch_add(count, Ordering::Relaxed);
6452 }
6453
6454 Ok(neuron_ids)
6455 }
6456
6457 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
6468 let npu = self
6470 .npu
6471 .as_ref()
6472 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6473
6474 let mut npu_lock = npu
6475 .lock()
6476 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6477
6478 let mut deleted_count = 0;
6479 let mut per_area_deleted: std::collections::HashMap<String, usize> =
6480 std::collections::HashMap::new();
6481
6482 for neuron_id in neuron_ids {
6485 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
6486 let cortical_id =
6487 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6488
6489 if npu_lock.delete_neuron(neuron_id as u32) {
6490 deleted_count += 1;
6491 if let Some(cortical_id) = cortical_id {
6492 let key = cortical_id.as_base_64();
6493 *per_area_deleted.entry(key).or_insert(0) += 1;
6494 }
6495 }
6496 }
6497
6498 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
6499
6500 if deleted_count > 0 {
6502 let state_manager = StateManager::instance();
6503 let state_manager = state_manager.read();
6504 let core_state = state_manager.get_core_state();
6505 core_state.subtract_neuron_count(deleted_count as u32);
6506 core_state.subtract_regular_neuron_count(deleted_count as u32);
6507 for (cortical_id, count) in per_area_deleted {
6508 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
6509 }
6510 }
6511
6512 Ok(deleted_count)
6519 }
6520
6521 pub fn update_neuron_properties(
6540 &mut self,
6541 neuron_id: u64,
6542 firing_threshold: Option<f32>,
6543 leak_coefficient: Option<f32>,
6544 resting_potential: Option<f32>,
6545 excitability: Option<f32>,
6546 ) -> BduResult<()> {
6547 let npu = self
6549 .npu
6550 .as_ref()
6551 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6552
6553 let mut npu_lock = npu
6554 .lock()
6555 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6556
6557 let neuron_id_u32 = neuron_id as u32;
6558
6559 let mut updated = false;
6561
6562 if let Some(threshold) = firing_threshold {
6564 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
6565 updated = true;
6566 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
6567 } else if !updated {
6568 return Err(BduError::InvalidNeuron(format!(
6569 "Neuron {} not found",
6570 neuron_id
6571 )));
6572 }
6573 }
6574
6575 if let Some(leak) = leak_coefficient {
6576 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
6577 updated = true;
6578 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
6579 } else if !updated {
6580 return Err(BduError::InvalidNeuron(format!(
6581 "Neuron {} not found",
6582 neuron_id
6583 )));
6584 }
6585 }
6586
6587 if let Some(resting) = resting_potential {
6588 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
6589 updated = true;
6590 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
6591 } else if !updated {
6592 return Err(BduError::InvalidNeuron(format!(
6593 "Neuron {} not found",
6594 neuron_id
6595 )));
6596 }
6597 }
6598
6599 if let Some(excit) = excitability {
6600 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
6601 updated = true;
6602 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
6603 } else if !updated {
6604 return Err(BduError::InvalidNeuron(format!(
6605 "Neuron {} not found",
6606 neuron_id
6607 )));
6608 }
6609 }
6610
6611 if !updated {
6612 return Err(BduError::Internal(
6613 "No properties provided for update".to_string(),
6614 ));
6615 }
6616
6617 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
6618
6619 Ok(())
6620 }
6621
6622 pub fn set_neuron_firing_threshold(
6634 &mut self,
6635 neuron_id: u64,
6636 new_threshold: f32,
6637 ) -> BduResult<()> {
6638 let npu = self
6640 .npu
6641 .as_ref()
6642 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6643
6644 let mut npu_lock = npu
6645 .lock()
6646 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6647
6648 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
6650 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
6651 Ok(())
6652 } else {
6653 Err(BduError::InvalidNeuron(format!(
6654 "Neuron {} not found",
6655 neuron_id
6656 )))
6657 }
6658 }
6659
6660 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
6675 self.cortical_areas
6676 .values()
6677 .find(|area| area.name == name)
6678 .cloned()
6679 }
6680
6681 pub fn resize_cortical_area(
6698 &mut self,
6699 cortical_id: &CorticalID,
6700 new_dimensions: CorticalAreaDimensions,
6701 ) -> BduResult<()> {
6702 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
6704 return Err(BduError::InvalidArea(format!(
6705 "Invalid dimensions: {:?} (all must be > 0)",
6706 new_dimensions
6707 )));
6708 }
6709
6710 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
6712 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6713 })?;
6714
6715 let old_dimensions = area.dimensions;
6716 area.dimensions = new_dimensions;
6717
6718 info!(target: "feagi-bdu",
6722 "Resized cortical area {} from {:?} to {:?}",
6723 cortical_id,
6724 old_dimensions,
6725 new_dimensions
6726 );
6727
6728 self.refresh_cortical_area_hashes(false, true);
6729
6730 Ok(())
6731 }
6732
6733 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
6744 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
6745 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6746 })?;
6747
6748 Ok(region
6750 .cortical_areas
6751 .iter()
6752 .map(|id| id.as_base_64())
6753 .collect())
6754 }
6755
6756 pub fn update_brain_region(
6769 &mut self,
6770 region_id: &str,
6771 new_name: Option<String>,
6772 new_description: Option<String>,
6773 ) -> BduResult<()> {
6774 let region = self
6775 .brain_regions
6776 .get_region_mut(region_id)
6777 .ok_or_else(|| {
6778 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6779 })?;
6780
6781 if let Some(name) = new_name {
6782 region.name = name;
6783 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
6784 }
6785
6786 if let Some(desc) = new_description {
6787 region
6789 .properties
6790 .insert("description".to_string(), serde_json::json!(desc));
6791 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
6792 }
6793
6794 info!(target: "feagi-bdu","Updated brain region {}", region_id);
6795
6796 self.refresh_brain_regions_hash();
6797
6798 Ok(())
6799 }
6800
6801 pub fn update_brain_region_properties(
6815 &mut self,
6816 region_id: &str,
6817 properties: std::collections::HashMap<String, serde_json::Value>,
6818 ) -> BduResult<Option<BrainRegionIoRegistry>> {
6819 use tracing::{debug, info};
6820
6821 let should_recompute_io = properties
6822 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6823 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
6824
6825 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6826 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
6827 {
6828 let region_snapshot = self
6829 .brain_regions
6830 .get_region(region_id)
6831 .ok_or_else(|| {
6832 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6833 })?
6834 .clone();
6835 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
6836 ®ion_snapshot,
6837 &properties,
6838 )?;
6839 crate::region_io_designation::validate_merged_designations_against_connectivity(
6840 self,
6841 ®ion_snapshot,
6842 &merged_in,
6843 &merged_out,
6844 )?;
6845 }
6846
6847 let region = self
6848 .brain_regions
6849 .get_region_mut(region_id)
6850 .ok_or_else(|| {
6851 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6852 })?;
6853
6854 for (key, value) in properties {
6855 match key.as_str() {
6856 "title" | "name" | "region_title" => {
6858 if let Some(name) = value.as_str() {
6859 region.name = name.to_string();
6860 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
6861 }
6862 }
6863 "coordinate_3d" | "coordinates_3d" => {
6864 region
6865 .properties
6866 .insert("coordinate_3d".to_string(), value.clone());
6867 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
6868 }
6869 "coordinate_2d" | "coordinates_2d" => {
6870 region
6871 .properties
6872 .insert("coordinate_2d".to_string(), value.clone());
6873 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
6874 }
6875 "description" => {
6876 region
6877 .properties
6878 .insert("description".to_string(), value.clone());
6879 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
6880 }
6881 "region_type" => {
6882 if let Some(type_str) = value.as_str() {
6883 region.region_type = feagi_structures::genomic::RegionType::Undefined;
6886 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
6887 }
6888 }
6889 _ => {
6891 region.properties.insert(key.clone(), value.clone());
6892 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
6893 }
6894 }
6895 }
6896
6897 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
6898
6899 if should_recompute_io {
6902 let registry = self.recompute_brain_region_io_registry()?;
6903 return Ok(Some(registry));
6904 }
6905
6906 self.refresh_brain_regions_hash();
6910
6911 Ok(None)
6912 }
6913
6914 pub fn get_neuron_by_coordinates(
6932 &self,
6933 cortical_id: &CorticalID,
6934 x: u32,
6935 y: u32,
6936 z: u32,
6937 ) -> Option<u64> {
6938 let area = self.get_cortical_area(cortical_id)?;
6940 let cortical_idx = area.cortical_idx;
6941
6942 let npu = self.npu.as_ref()?;
6944 let npu_lock = npu.lock().ok()?;
6945
6946 npu_lock
6947 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
6948 .map(|id| id as u64)
6949 }
6950
6951 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
6962 let npu = self.npu.as_ref()?;
6963 let npu_lock = npu.lock().ok()?;
6964
6965 let neuron_count = npu_lock.get_neuron_count();
6967 if (neuron_id as usize) >= neuron_count {
6968 return None;
6969 }
6970
6971 Some(
6972 npu_lock
6973 .get_neuron_coordinates(neuron_id as u32)
6974 .unwrap_or((0, 0, 0)),
6975 )
6976 }
6977
6978 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
6989 let npu = self.npu.as_ref()?;
6990 let npu_lock = npu.lock().ok()?;
6991
6992 let neuron_count = npu_lock.get_neuron_count();
6994 if (neuron_id as usize) >= neuron_count {
6995 return None;
6996 }
6997
6998 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
6999
7000 self.cortical_areas
7002 .values()
7003 .find(|area| area.cortical_idx == cortical_idx)
7004 .map(|area| area.cortical_id)
7005 }
7006
7007 pub fn get_neuron_properties(
7018 &self,
7019 neuron_id: u64,
7020 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7021 let npu = self.npu.as_ref()?;
7022 let npu_lock = npu.lock().ok()?;
7023
7024 let neuron_id_u32 = neuron_id as u32;
7025 let idx = neuron_id as usize;
7026
7027 let neuron_count = npu_lock.get_neuron_count();
7029 if idx >= neuron_count {
7030 return None;
7031 }
7032
7033 let mut properties = std::collections::HashMap::new();
7034
7035 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
7037
7038 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
7040 properties.insert("x".to_string(), serde_json::json!(x));
7041 properties.insert("y".to_string(), serde_json::json!(y));
7042 properties.insert("z".to_string(), serde_json::json!(z));
7043
7044 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
7046 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
7047
7048 properties.insert(
7050 "mp_charge_accumulation".to_string(),
7051 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
7052 );
7053 properties.insert(
7054 "neuron_type".to_string(),
7055 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
7056 );
7057 let (mp_drv, psp_uni) = self
7058 .cortical_idx_to_id
7059 .get(&cortical_idx)
7060 .map(|cid| {
7061 (
7062 npu_lock.get_mp_driven_psp_for_cortical(cid),
7063 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
7064 )
7065 })
7066 .unwrap_or((false, false));
7067 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
7068 properties.insert(
7069 "psp_uniform_distribution".to_string(),
7070 serde_json::json!(psp_uni),
7071 );
7072
7073 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
7076 .get_neuron_state(NeuronId(neuron_id_u32))
7077 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
7078 properties.insert(
7079 "consecutive_fire_count".to_string(),
7080 serde_json::json!(consec_count),
7081 );
7082 properties.insert(
7083 "consecutive_fire_limit".to_string(),
7084 serde_json::json!(consec_limit),
7085 );
7086 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
7087 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
7088 properties.insert("threshold".to_string(), serde_json::json!(threshold));
7089 properties.insert(
7090 "refractory_countdown".to_string(),
7091 serde_json::json!(refract_countdown),
7092 );
7093
7094 properties.insert(
7096 "leak_coefficient".to_string(),
7097 serde_json::json!(npu_lock
7098 .get_neuron_property_by_index(idx, "leak_coefficient")
7099 .unwrap_or(0.0)),
7100 );
7101 properties.insert(
7102 "resting_potential".to_string(),
7103 serde_json::json!(npu_lock
7104 .get_neuron_property_by_index(idx, "resting_potential")
7105 .unwrap_or(0.0)),
7106 );
7107 properties.insert(
7108 "excitability".to_string(),
7109 serde_json::json!(npu_lock
7110 .get_neuron_property_by_index(idx, "excitability")
7111 .unwrap_or(0.0)),
7112 );
7113 properties.insert(
7114 "threshold_limit".to_string(),
7115 serde_json::json!(npu_lock
7116 .get_neuron_property_by_index(idx, "threshold_limit")
7117 .unwrap_or(0.0)),
7118 );
7119 properties.insert(
7120 "refractory_period".to_string(),
7121 serde_json::json!(npu_lock
7122 .get_neuron_property_u16_by_index(idx, "refractory_period")
7123 .unwrap_or(0)),
7124 );
7125
7126 Some(properties)
7127 }
7128
7129 pub fn get_neuron_property(
7141 &self,
7142 neuron_id: u64,
7143 property_name: &str,
7144 ) -> Option<serde_json::Value> {
7145 self.get_neuron_properties(neuron_id)?
7146 .get(property_name)
7147 .cloned()
7148 }
7149
7150 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
7161 self.cortical_areas.keys().copied().collect()
7162 }
7163
7164 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
7171 self.cortical_areas
7172 .values()
7173 .map(|area| area.cortical_idx)
7174 .collect()
7175 }
7176
7177 pub fn get_cortical_area_names(&self) -> Vec<String> {
7184 self.cortical_areas
7185 .values()
7186 .map(|area| area.name.clone())
7187 .collect()
7188 }
7189
7190 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
7197 use crate::models::CorticalAreaExt;
7198 self.cortical_areas
7199 .values()
7200 .filter(|area| area.is_input_area())
7201 .map(|area| area.cortical_id)
7202 .collect()
7203 }
7204
7205 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
7212 use crate::models::CorticalAreaExt;
7213 self.cortical_areas
7214 .values()
7215 .filter(|area| area.is_output_area())
7216 .map(|area| area.cortical_id)
7217 .collect()
7218 }
7219
7220 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
7227 self.cortical_areas
7228 .values()
7229 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
7230 (
7231 max_w.max(area.dimensions.width as usize),
7232 max_h.max(area.dimensions.height as usize),
7233 max_d.max(area.dimensions.depth as usize),
7234 )
7235 })
7236 }
7237
7238 pub fn get_cortical_area_properties(
7249 &self,
7250 cortical_id: &CorticalID,
7251 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7252 let area = self.get_cortical_area(cortical_id)?;
7253
7254 let mut properties = std::collections::HashMap::new();
7255 properties.insert(
7256 "cortical_id".to_string(),
7257 serde_json::json!(area.cortical_id),
7258 );
7259 properties.insert(
7260 "cortical_id_s".to_string(),
7261 serde_json::json!(area.cortical_id.to_string()),
7262 );
7263 properties.insert(
7264 "cortical_idx".to_string(),
7265 serde_json::json!(area.cortical_idx),
7266 );
7267 properties.insert("name".to_string(), serde_json::json!(area.name));
7268 use crate::models::CorticalAreaExt;
7269 properties.insert(
7270 "area_type".to_string(),
7271 serde_json::json!(area.get_cortical_group()),
7272 );
7273 properties.insert(
7274 "dimensions".to_string(),
7275 serde_json::json!({
7276 "width": area.dimensions.width,
7277 "height": area.dimensions.height,
7278 "depth": area.dimensions.depth,
7279 }),
7280 );
7281 properties.insert("position".to_string(), serde_json::json!(area.position));
7282
7283 for (key, value) in &area.properties {
7285 properties.insert(key.clone(), value.clone());
7286 }
7287
7288 properties.extend(area.properties.clone());
7290
7291 Some(properties)
7292 }
7293
7294 pub fn get_all_cortical_area_properties(
7301 &self,
7302 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
7303 self.cortical_areas
7304 .keys()
7305 .filter_map(|id| self.get_cortical_area_properties(id))
7306 .collect()
7307 }
7308
7309 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
7320 self.brain_regions
7321 .get_all_region_ids()
7322 .into_iter()
7323 .cloned()
7324 .collect()
7325 }
7326
7327 pub fn get_brain_region_names(&self) -> Vec<String> {
7334 self.brain_regions
7335 .get_all_region_ids()
7336 .iter()
7337 .filter_map(|id| {
7338 self.brain_regions
7339 .get_region(id)
7340 .map(|region| region.name.clone())
7341 })
7342 .collect()
7343 }
7344
7345 pub fn get_brain_region_properties(
7356 &self,
7357 region_id: &str,
7358 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7359 let region = self.brain_regions.get_region(region_id)?;
7360
7361 let mut properties = std::collections::HashMap::new();
7362 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
7363 properties.insert("name".to_string(), serde_json::json!(region.name));
7364 properties.insert(
7365 "region_type".to_string(),
7366 serde_json::json!(format!("{:?}", region.region_type)),
7367 );
7368 properties.insert(
7369 "cortical_areas".to_string(),
7370 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
7371 );
7372
7373 properties.extend(region.properties.clone());
7375
7376 Some(properties)
7377 }
7378
7379 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
7390 self.cortical_areas.contains_key(cortical_id)
7391 }
7392
7393 pub fn brain_region_exists(&self, region_id: &str) -> bool {
7404 self.brain_regions.get_region(region_id).is_some()
7405 }
7406
7407 pub fn get_brain_region_count(&self) -> usize {
7414 self.brain_regions.region_count()
7415 }
7416
7417 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
7428 self.get_neurons_in_area(cortical_id)
7432 }
7433}
7434
7435impl std::fmt::Debug for ConnectomeManager {
7437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7438 f.debug_struct("ConnectomeManager")
7439 .field("cortical_areas", &self.cortical_areas.len())
7440 .field("next_cortical_idx", &self.next_cortical_idx)
7441 .field("brain_regions", &self.brain_regions)
7442 .field(
7443 "npu",
7444 &if self.npu.is_some() {
7445 "Connected"
7446 } else {
7447 "Not connected"
7448 },
7449 )
7450 .field("initialized", &self.initialized)
7451 .finish()
7452 }
7453}
7454
7455#[cfg(test)]
7456mod tests {
7457 use super::*;
7458 use feagi_structures::genomic::cortical_area::CoreCorticalType;
7459
7460 #[test]
7461 fn test_singleton_instance() {
7462 let instance1 = ConnectomeManager::instance();
7463 let instance2 = ConnectomeManager::instance();
7464
7465 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
7467 }
7468
7469 #[test]
7470 fn test_add_cortical_area() {
7471 ConnectomeManager::reset_for_testing();
7472
7473 let instance = ConnectomeManager::instance();
7474 let mut manager = instance.write();
7475
7476 use feagi_structures::genomic::cortical_area::{
7477 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7478 };
7479 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7481 let area = CorticalArea::new(
7482 cortical_id,
7483 0,
7484 "Visual Input".to_string(),
7485 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
7486 (0, 0, 0).into(),
7487 cortical_type,
7488 )
7489 .unwrap();
7490
7491 let initial_count = manager.get_cortical_area_count();
7492 let _cortical_idx = manager.add_cortical_area(area).unwrap();
7493
7494 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
7495 assert!(manager.has_cortical_area(&cortical_id));
7496 assert!(manager.is_initialized());
7497 }
7498
7499 #[test]
7500 fn test_cortical_area_lookups() {
7501 ConnectomeManager::reset_for_testing();
7502
7503 let instance = ConnectomeManager::instance();
7504 let mut manager = instance.write();
7505
7506 use feagi_structures::genomic::cortical_area::{
7507 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7508 };
7509 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7511 let area = CorticalArea::new(
7512 cortical_id,
7513 0,
7514 "Test Area".to_string(),
7515 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7516 (0, 0, 0).into(),
7517 cortical_type,
7518 )
7519 .unwrap();
7520
7521 let cortical_idx = manager.add_cortical_area(area).unwrap();
7522
7523 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
7525
7526 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
7528
7529 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
7531 assert_eq!(retrieved_area.name, "Test Area");
7532 }
7533
7534 #[test]
7535 fn test_remove_cortical_area() {
7536 ConnectomeManager::reset_for_testing();
7537
7538 let instance = ConnectomeManager::instance();
7539 let mut manager = instance.write();
7540
7541 use feagi_structures::genomic::cortical_area::{
7542 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7543 };
7544 let cortical_id = CoreCorticalType::Power.to_cortical_id();
7545
7546 if manager.has_cortical_area(&cortical_id) {
7548 manager.remove_cortical_area(&cortical_id).unwrap();
7549 }
7550
7551 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7552 let area = CorticalArea::new(
7553 cortical_id,
7554 0,
7555 "Test".to_string(),
7556 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7557 (0, 0, 0).into(),
7558 cortical_type,
7559 )
7560 .unwrap();
7561
7562 let initial_count = manager.get_cortical_area_count();
7563 manager.add_cortical_area(area).unwrap();
7564 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
7565
7566 manager.remove_cortical_area(&cortical_id).unwrap();
7567 assert_eq!(manager.get_cortical_area_count(), initial_count);
7568 assert!(!manager.has_cortical_area(&cortical_id));
7569 }
7570
7571 #[test]
7572 fn test_duplicate_area_error() {
7573 ConnectomeManager::reset_for_testing();
7574
7575 let instance = ConnectomeManager::instance();
7576 let mut manager = instance.write();
7577
7578 use feagi_structures::genomic::cortical_area::{
7579 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7580 };
7581 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
7584 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7585 let area1 = CorticalArea::new(
7586 cortical_id,
7587 0,
7588 "First".to_string(),
7589 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7590 (0, 0, 0).into(),
7591 cortical_type,
7592 )
7593 .unwrap();
7594
7595 let area2 = CorticalArea::new(
7596 cortical_id, 1,
7598 "Second".to_string(),
7599 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7600 (0, 0, 0).into(),
7601 cortical_type,
7602 )
7603 .unwrap();
7604
7605 manager.add_cortical_area(area1).unwrap();
7606 let result = manager.add_cortical_area(area2);
7607
7608 assert!(result.is_err());
7609 }
7610
7611 #[test]
7612 fn test_brain_region_management() {
7613 ConnectomeManager::reset_for_testing();
7614
7615 let instance = ConnectomeManager::instance();
7616 let mut manager = instance.write();
7617
7618 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
7619 let region_id_str = region_id.to_string();
7620 let root = BrainRegion::new(
7621 region_id,
7622 "Root".to_string(),
7623 feagi_structures::genomic::brain_regions::RegionType::Undefined,
7624 )
7625 .unwrap();
7626
7627 let initial_count = manager.get_brain_region_ids().len();
7628 manager.add_brain_region(root, None).unwrap();
7629
7630 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
7631 assert!(manager.get_brain_region(®ion_id_str).is_some());
7632 }
7633
7634 #[test]
7635 fn test_synapse_operations() {
7636 use feagi_npu_burst_engine::npu::RustNPU;
7637 use feagi_npu_burst_engine::TracingMutex;
7638 use std::sync::Arc;
7639
7640 use feagi_npu_burst_engine::backend::CPUBackend;
7642 use feagi_npu_burst_engine::DynamicNPU;
7643 use feagi_npu_runtime::StdRuntime;
7644
7645 let runtime = StdRuntime;
7646 let backend = CPUBackend::new();
7647 let npu_result =
7648 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
7649 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
7650 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
7651
7652 use feagi_structures::genomic::cortical_area::{
7654 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7655 };
7656 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7658 let area = CorticalArea::new(
7659 cortical_id,
7660 0, "Test Area".to_string(),
7662 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
7663 (0, 0, 0).into(), cortical_type,
7665 )
7666 .unwrap();
7667 let cortical_idx = manager.add_cortical_area(area).unwrap();
7668
7669 if let Some(npu_arc) = manager.get_npu() {
7671 if let Ok(mut npu_guard) = npu_arc.try_lock() {
7672 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
7673 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
7674 }
7675 }
7676 }
7677
7678 let neuron1_id = manager
7680 .add_neuron(
7681 &cortical_id,
7682 0,
7683 0,
7684 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
7696 .unwrap();
7697
7698 let neuron2_id = manager
7699 .add_neuron(
7700 &cortical_id,
7701 1,
7702 0,
7703 0, 100.0,
7705 f32::MAX, 0.1,
7707 -60.0,
7708 0,
7709 2,
7710 1.0,
7711 5,
7712 10,
7713 false,
7714 )
7715 .unwrap();
7716
7717 manager
7719 .create_synapse(
7720 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
7724 .unwrap();
7725
7726 println!("✅ Synapse creation test passed");
7729 }
7730
7731 #[test]
7732 fn test_apply_cortical_mapping_missing_rules_is_ok() {
7733 let mut manager = ConnectomeManager::new_for_testing();
7736
7737 use feagi_structures::genomic::cortical_area::{
7738 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7739 };
7740
7741 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
7742 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
7743
7744 let src_area = CorticalArea::new(
7745 src_id,
7746 0,
7747 "src".to_string(),
7748 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7749 (0, 0, 0).into(),
7750 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7751 )
7752 .unwrap();
7753
7754 let dst_area = CorticalArea::new(
7755 dst_id,
7756 1,
7757 "dst".to_string(),
7758 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7759 (0, 0, 0).into(),
7760 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7761 )
7762 .unwrap();
7763
7764 manager.add_cortical_area(src_area).unwrap();
7765 manager.add_cortical_area(dst_area).unwrap();
7766
7767 let count = manager
7769 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7770 .unwrap();
7771 assert_eq!(count, 0);
7772
7773 manager
7775 .update_cortical_mapping(
7776 &src_id,
7777 &dst_id,
7778 vec![serde_json::json!({"morphology_id":"m1"})],
7779 )
7780 .unwrap();
7781 manager
7782 .update_cortical_mapping(&src_id, &dst_id, vec![])
7783 .unwrap();
7784
7785 let count2 = manager
7786 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7787 .unwrap();
7788 assert_eq!(count2, 0);
7789 }
7790
7791 #[test]
7792 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
7793 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7794 let mapping_dst = serde_json::json!({
7795 "csrc0002": [
7796 {"morphology_id": "m1"}
7797 ]
7798 });
7799 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
7800
7801 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
7802 .expect("legacy destination key should resolve");
7803 assert_eq!(rules.len(), 1);
7804 assert_eq!(
7805 rules[0].get("morphology_id").and_then(|v| v.as_str()),
7806 Some("m1")
7807 );
7808 }
7809
7810 #[test]
7811 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
7812 use feagi_npu_burst_engine::backend::CPUBackend;
7813 use feagi_npu_burst_engine::RustNPU;
7814 use feagi_npu_burst_engine::TracingMutex;
7815 use feagi_npu_runtime::StdRuntime;
7816 use feagi_structures::genomic::cortical_area::{
7817 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7818 };
7819 use std::sync::Arc;
7820
7821 let runtime = StdRuntime;
7822 let backend = CPUBackend::new();
7823 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7824 let dyn_npu = Arc::new(TracingMutex::new(
7825 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7826 "TestNPU",
7827 ));
7828 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7829
7830 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
7831 let area = CorticalArea::new(
7832 area_id,
7833 0,
7834 "n".to_string(),
7835 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7836 (0, 0, 0).into(),
7837 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7838 )
7839 .unwrap();
7840
7841 manager.add_cortical_area(area).unwrap();
7842 let nid = manager
7843 .add_neuron(
7844 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
7845 )
7846 .unwrap();
7847
7848 let props = manager
7849 .get_neuron_properties(nid)
7850 .expect("neuron properties");
7851 for key in [
7852 "consecutive_fire_count",
7853 "consecutive_fire_limit",
7854 "snooze_period",
7855 "membrane_potential",
7856 "threshold",
7857 "refractory_countdown",
7858 "mp_charge_accumulation",
7859 "neuron_type",
7860 "mp_driven_psp",
7861 "psp_uniform_distribution",
7862 "leak_coefficient",
7863 "resting_potential",
7864 "excitability",
7865 "threshold_limit",
7866 "refractory_period",
7867 ] {
7868 assert!(props.contains_key(key), "missing neuron state key: {key}");
7869 }
7870 }
7871
7872 #[test]
7873 fn test_mapping_deletion_prunes_synapses_between_areas() {
7874 use feagi_npu_burst_engine::backend::CPUBackend;
7875 use feagi_npu_burst_engine::RustNPU;
7876 use feagi_npu_burst_engine::TracingMutex;
7877 use feagi_npu_runtime::StdRuntime;
7878 use feagi_structures::genomic::cortical_area::{
7879 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7880 };
7881 use std::sync::Arc;
7882
7883 let runtime = StdRuntime;
7885 let backend = CPUBackend::new();
7886 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7887 let dyn_npu = Arc::new(TracingMutex::new(
7888 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7889 "TestNPU",
7890 ));
7891 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7892
7893 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
7895 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
7896
7897 let src_area = CorticalArea::new(
7898 src_id,
7899 0,
7900 "src".to_string(),
7901 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7902 (0, 0, 0).into(),
7903 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7904 )
7905 .unwrap();
7906 let dst_area = CorticalArea::new(
7907 dst_id,
7908 1,
7909 "dst".to_string(),
7910 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7911 (0, 0, 0).into(),
7912 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7913 )
7914 .unwrap();
7915
7916 manager.add_cortical_area(src_area).unwrap();
7917 manager.add_cortical_area(dst_area).unwrap();
7918
7919 let s0 = manager
7921 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7922 .unwrap();
7923 let s1 = manager
7924 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7925 .unwrap();
7926 let t0 = manager
7927 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7928 .unwrap();
7929 let t1 = manager
7930 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7931 .unwrap();
7932
7933 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7935 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7936
7937 {
7939 let mut npu = dyn_npu.lock().unwrap();
7940 npu.rebuild_synapse_index();
7941 assert_eq!(npu.get_synapse_count(), 2);
7942 }
7943
7944 manager
7946 .update_cortical_mapping(&src_id, &dst_id, vec![])
7947 .unwrap();
7948 let created = manager
7949 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7950 .unwrap();
7951 assert_eq!(created, 0);
7952
7953 {
7955 let mut npu = dyn_npu.lock().unwrap();
7956 npu.rebuild_synapse_index();
7958 assert_eq!(npu.get_synapse_count(), 0);
7959 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7960 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7961 }
7962 }
7963
7964 #[test]
7965 fn test_mapping_update_prunes_synapses_between_areas() {
7966 use feagi_npu_burst_engine::backend::CPUBackend;
7967 use feagi_npu_burst_engine::RustNPU;
7968 use feagi_npu_burst_engine::TracingMutex;
7969 use feagi_npu_runtime::StdRuntime;
7970 use feagi_structures::genomic::cortical_area::{
7971 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7972 };
7973 use std::sync::Arc;
7974
7975 let runtime = StdRuntime;
7977 let backend = CPUBackend::new();
7978 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7979 let dyn_npu = Arc::new(TracingMutex::new(
7980 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7981 "TestNPU",
7982 ));
7983 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7984
7985 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7987
7988 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
7991 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
7992
7993 let src_area = CorticalArea::new(
7994 src_id,
7995 0,
7996 "src".to_string(),
7997 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7998 (0, 0, 0).into(),
7999 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8000 )
8001 .unwrap();
8002 let dst_area = CorticalArea::new(
8003 dst_id,
8004 0,
8005 "dst".to_string(),
8006 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8007 (0, 0, 0).into(),
8008 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8009 )
8010 .unwrap();
8011
8012 manager.add_cortical_area(src_area).unwrap();
8013 manager.add_cortical_area(dst_area).unwrap();
8014
8015 let s0 = manager
8017 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8018 .unwrap();
8019 let s1 = manager
8020 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8021 .unwrap();
8022 let t0 = manager
8023 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8024 .unwrap();
8025 let t1 = manager
8026 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8027 .unwrap();
8028
8029 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
8031 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
8032
8033 {
8035 let mut npu = dyn_npu.lock().unwrap();
8036 npu.rebuild_synapse_index();
8037 assert_eq!(npu.get_synapse_count(), 2);
8038 }
8039
8040 manager
8046 .update_cortical_mapping(
8047 &src_id,
8048 &dst_id,
8049 vec![serde_json::json!({
8050 "morphology_id": "episodic_memory",
8051 "morphology_scalar": [1],
8052 "postSynapticCurrent_multiplier": 1,
8053 "plasticity_flag": false,
8054 "plasticity_constant": 0,
8055 "ltp_multiplier": 0,
8056 "ltd_multiplier": 0,
8057 "plasticity_window": 0,
8058 })],
8059 )
8060 .unwrap();
8061 let created = manager
8062 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8063 .unwrap();
8064 assert_eq!(created, 0);
8065
8066 {
8068 let mut npu = dyn_npu.lock().unwrap();
8069 npu.rebuild_synapse_index();
8071 assert_eq!(npu.get_synapse_count(), 0);
8072 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
8073 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
8074 }
8075 }
8076
8077 #[test]
8078 fn test_upstream_area_tracking() {
8079 use crate::models::cortical_area::CorticalArea;
8081 use feagi_npu_burst_engine::backend::CPUBackend;
8082 use feagi_npu_burst_engine::TracingMutex;
8083 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8084 use feagi_npu_runtime::StdRuntime;
8085 use feagi_structures::genomic::cortical_area::{
8086 CorticalAreaDimensions, CorticalAreaType, CorticalID,
8087 };
8088
8089 let runtime = StdRuntime;
8091 let backend = CPUBackend::new();
8092 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8093 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8094 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8095
8096 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8099
8100 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
8102 let src_area = CorticalArea::new(
8103 src_id,
8104 0,
8105 "Source Area".to_string(),
8106 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8107 (0, 0, 0).into(),
8108 CorticalAreaType::Custom(
8109 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8110 ),
8111 )
8112 .unwrap();
8113 let src_idx = manager.add_cortical_area(src_area).unwrap();
8114
8115 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
8117 let dst_area = CorticalArea::new(
8118 dst_id,
8119 0,
8120 "Dest Area".to_string(),
8121 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8122 (0, 0, 0).into(),
8123 CorticalAreaType::Custom(
8124 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8125 ),
8126 )
8127 .unwrap();
8128 manager.add_cortical_area(dst_area).unwrap();
8129
8130 {
8132 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
8133 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
8134 assert!(
8135 upstream.as_array().unwrap().is_empty(),
8136 "Upstream areas should be empty initially"
8137 );
8138 }
8139
8140 let mapping_data = vec![serde_json::json!({
8142 "morphology_id": "episodic_memory",
8143 "morphology_scalar": 1,
8144 "postSynapticCurrent_multiplier": 1.0,
8145 })];
8146 manager
8147 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
8148 .unwrap();
8149 manager
8150 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8151 .unwrap();
8152
8153 {
8155 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
8156 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
8157 assert_eq!(
8158 upstream_areas[0], src_idx,
8159 "Upstream area should be src_idx"
8160 );
8161 }
8162
8163 manager
8165 .update_cortical_mapping(&src_id, &dst_id, vec![])
8166 .unwrap();
8167 manager
8168 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8169 .unwrap();
8170
8171 {
8173 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
8174 assert_eq!(
8175 upstream_areas.len(),
8176 0,
8177 "Should have 0 upstream areas after deletion"
8178 );
8179 }
8180 }
8181
8182 #[test]
8183 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
8184 use crate::models::cortical_area::CorticalArea;
8185 use feagi_npu_burst_engine::backend::CPUBackend;
8186 use feagi_npu_burst_engine::TracingMutex;
8187 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8188 use feagi_npu_runtime::StdRuntime;
8189 use feagi_structures::genomic::cortical_area::{
8190 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8191 };
8192 use std::sync::Arc;
8193
8194 let runtime = StdRuntime;
8195 let backend = CPUBackend::new();
8196 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8197 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8198 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8199 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8200
8201 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8202 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
8203 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
8204 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
8205
8206 let a1_area = CorticalArea::new(
8207 a1_id,
8208 0,
8209 "A1".to_string(),
8210 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8211 (0, 0, 0).into(),
8212 CorticalAreaType::Custom(
8213 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8214 ),
8215 )
8216 .unwrap();
8217 let a2_area = CorticalArea::new(
8218 a2_id,
8219 0,
8220 "A2".to_string(),
8221 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8222 (0, 0, 0).into(),
8223 CorticalAreaType::Custom(
8224 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8225 ),
8226 )
8227 .unwrap();
8228
8229 let mut m1_area = CorticalArea::new(
8230 m1_id,
8231 0,
8232 "M1".to_string(),
8233 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8234 (0, 0, 0).into(),
8235 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8236 )
8237 .unwrap();
8238 m1_area
8239 .properties
8240 .insert("is_mem_type".to_string(), serde_json::json!(true));
8241 m1_area
8242 .properties
8243 .insert("temporal_depth".to_string(), serde_json::json!(1));
8244
8245 let mut m2_area = CorticalArea::new(
8246 m2_id,
8247 0,
8248 "M2".to_string(),
8249 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8250 (0, 0, 0).into(),
8251 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8252 )
8253 .unwrap();
8254 m2_area
8255 .properties
8256 .insert("is_mem_type".to_string(), serde_json::json!(true));
8257 m2_area
8258 .properties
8259 .insert("temporal_depth".to_string(), serde_json::json!(1));
8260
8261 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
8262 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
8263 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
8264 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
8265
8266 manager
8267 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8268 .unwrap();
8269 manager
8270 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8271 .unwrap();
8272
8273 let episodic_mapping = vec![serde_json::json!({
8274 "morphology_id": "episodic_memory",
8275 "morphology_scalar": 1,
8276 "postSynapticCurrent_multiplier": 1.0,
8277 })];
8278 manager
8279 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
8280 .unwrap();
8281 manager
8282 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
8283 .unwrap();
8284 manager
8285 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
8286 .unwrap();
8287 manager
8288 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
8289 .unwrap();
8290
8291 let assoc_mapping = vec![serde_json::json!({
8292 "morphology_id": "associative_memory",
8293 "morphology_scalar": 1,
8294 "postSynapticCurrent_multiplier": 1.0,
8295 "plasticity_flag": true,
8296 "plasticity_constant": 1,
8297 "ltp_multiplier": 1,
8298 "ltd_multiplier": 1,
8299 "plasticity_window": 5,
8300 })];
8301 manager
8302 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
8303 .unwrap();
8304 manager
8305 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8306 .unwrap();
8307 manager
8309 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
8310 .unwrap();
8311 manager
8312 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
8313 .unwrap();
8314
8315 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8316 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8317 assert_eq!(
8318 upstream_m1.len(),
8319 2,
8320 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
8321 );
8322 assert_eq!(
8323 upstream_m2.len(),
8324 2,
8325 "M2 should have A2 and M1 as upstreams"
8326 );
8327
8328 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
8329 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
8330
8331 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8332 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8333 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
8334 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
8335 assert!(upstream_m1.contains(&a1_idx));
8336 assert!(upstream_m1.contains(&m2_idx));
8337 assert!(upstream_m2.contains(&a2_idx));
8338 assert!(upstream_m2.contains(&m1_idx));
8339
8340 {
8342 let mut npu_lock = dyn_npu.lock().unwrap();
8343 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
8344 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
8345 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
8346 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
8347 npu_lock.process_burst().expect("Burst processing failed");
8348 }
8349
8350 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8351 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8352 assert_eq!(
8353 upstream_m1.len(),
8354 2,
8355 "M1 should keep 2 upstreams after firing"
8356 );
8357 assert_eq!(
8358 upstream_m2.len(),
8359 2,
8360 "M2 should keep 2 upstreams after firing"
8361 );
8362
8363 let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
8364 let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
8365 assert_eq!(
8366 episodic_upstream_m1,
8367 vec![a1_idx],
8368 "Episodic upstream list for M1 should exclude associative-only memory source M2"
8369 );
8370 assert_eq!(
8371 episodic_upstream_m2,
8372 vec![a2_idx],
8373 "Episodic upstream list for M2 should exclude associative-only memory source M1"
8374 );
8375 }
8376
8377 #[test]
8378 fn test_memory_to_non_memory_requires_associative_morphology() {
8379 use crate::models::cortical_area::CorticalArea;
8380 use feagi_structures::genomic::cortical_area::{
8381 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8382 MemoryCorticalType,
8383 };
8384
8385 let mut manager = ConnectomeManager::new_for_testing();
8386 let memory_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
8387 let destination_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
8388 let memory_area = CorticalArea::new(
8389 memory_id,
8390 0,
8391 "Memory Area".to_string(),
8392 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8393 (0, 0, 0).into(),
8394 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8395 )
8396 .unwrap();
8397 let destination_area = CorticalArea::new(
8398 destination_id,
8399 0,
8400 "Destination Area".to_string(),
8401 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8402 (1, 0, 0).into(),
8403 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8404 )
8405 .unwrap();
8406 manager.add_cortical_area(memory_area).unwrap();
8407 manager.add_cortical_area(destination_area).unwrap();
8408
8409 let invalid = manager.update_cortical_mapping(
8410 &memory_id,
8411 &destination_id,
8412 vec![serde_json::json!({"morphology_id": "episodic_memory"})],
8413 );
8414 assert!(matches!(invalid, Err(BduError::InvalidMorphology(_))));
8415
8416 manager
8417 .update_cortical_mapping(
8418 &memory_id,
8419 &destination_id,
8420 vec![serde_json::json!({"morphology_id": "associative_memory"})],
8421 )
8422 .unwrap();
8423 }
8424
8425 #[test]
8426 fn test_memory_twin_created_for_memory_mapping() {
8427 use crate::models::cortical_area::CorticalArea;
8428 use feagi_npu_burst_engine::backend::CPUBackend;
8429 use feagi_npu_burst_engine::TracingMutex;
8430 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8431 use feagi_npu_runtime::StdRuntime;
8432 use feagi_structures::genomic::cortical_area::{
8433 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8434 MemoryCorticalType,
8435 };
8436 use std::sync::Arc;
8437
8438 let runtime = StdRuntime;
8439 let backend = CPUBackend::new();
8440 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8441 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8442 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8443 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8444
8445 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
8446 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
8447
8448 let src_area = CorticalArea::new(
8449 src_id,
8450 0,
8451 "Source Area".to_string(),
8452 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8453 (0, 0, 0).into(),
8454 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8455 )
8456 .unwrap();
8457 let mut dst_area = CorticalArea::new(
8458 dst_id,
8459 0,
8460 "Memory Area".to_string(),
8461 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8462 (0, 0, 0).into(),
8463 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8464 )
8465 .unwrap();
8466 dst_area
8467 .properties
8468 .insert("is_mem_type".to_string(), serde_json::json!(true));
8469 dst_area
8470 .properties
8471 .insert("temporal_depth".to_string(), serde_json::json!(1));
8472
8473 manager.add_cortical_area(src_area).unwrap();
8474 manager.add_cortical_area(dst_area).unwrap();
8475
8476 let mapping_data = vec![serde_json::json!({
8477 "morphology_id": "episodic_memory",
8478 "morphology_scalar": 1,
8479 "postSynapticCurrent_multiplier": 1.0,
8480 })];
8481 manager
8482 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
8483 .unwrap();
8484 manager
8485 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8486 .unwrap();
8487
8488 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
8489 let twin_map = memory_area
8490 .properties
8491 .get("memory_twin_areas")
8492 .and_then(|v| v.as_object())
8493 .expect("memory_twin_areas should be set");
8494 let twin_id_str = twin_map
8495 .get(&src_id.as_base_64())
8496 .and_then(|v| v.as_str())
8497 .expect("Missing twin entry for upstream area");
8498 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
8499 let mapping = memory_area
8500 .properties
8501 .get("cortical_mapping_dst")
8502 .and_then(|v| v.as_object())
8503 .and_then(|map| map.get(&twin_id.as_base_64()))
8504 .and_then(|v| v.as_array())
8505 .expect("Missing memory replay mapping for twin area");
8506 let uses_replay = mapping.iter().any(|rule| {
8507 rule.get("morphology_id")
8508 .and_then(|v| v.as_str())
8509 .is_some_and(|id| id == "memory_replay")
8510 });
8511 assert!(uses_replay, "Expected memory_replay mapping for twin area");
8512
8513 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8514 assert!(matches!(
8515 twin_area.cortical_type,
8516 CorticalAreaType::Custom(_)
8517 ));
8518 assert_eq!(
8519 twin_area
8520 .properties
8521 .get("memory_twin_of")
8522 .and_then(|v| v.as_str()),
8523 Some(src_id.as_base_64().as_str())
8524 );
8525 assert_eq!(
8526 twin_area
8527 .properties
8528 .get("memory_twin_for")
8529 .and_then(|v| v.as_str()),
8530 Some(dst_id.as_base_64().as_str())
8531 );
8532 }
8533
8534 #[test]
8535 fn test_associative_memory_between_memory_areas_creates_synapses() {
8536 use crate::models::cortical_area::CorticalArea;
8537 use feagi_npu_burst_engine::backend::CPUBackend;
8538 use feagi_npu_burst_engine::TracingMutex;
8539 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8540 use feagi_npu_runtime::StdRuntime;
8541 use feagi_structures::genomic::cortical_area::{
8542 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8543 };
8544 use std::sync::Arc;
8545
8546 let runtime = StdRuntime;
8547 let backend = CPUBackend::new();
8548 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8549 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8550 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8551 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8552
8553 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
8554 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
8555
8556 let mut m1_area = CorticalArea::new(
8557 m1_id,
8558 0,
8559 "Memory M1".to_string(),
8560 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8561 (0, 0, 0).into(),
8562 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8563 )
8564 .unwrap();
8565 m1_area
8566 .properties
8567 .insert("is_mem_type".to_string(), serde_json::json!(true));
8568 m1_area
8569 .properties
8570 .insert("temporal_depth".to_string(), serde_json::json!(1));
8571
8572 let mut m2_area = CorticalArea::new(
8573 m2_id,
8574 0,
8575 "Memory M2".to_string(),
8576 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8577 (0, 0, 0).into(),
8578 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8579 )
8580 .unwrap();
8581 m2_area
8582 .properties
8583 .insert("is_mem_type".to_string(), serde_json::json!(true));
8584 m2_area
8585 .properties
8586 .insert("temporal_depth".to_string(), serde_json::json!(1));
8587
8588 manager.add_cortical_area(m1_area).unwrap();
8589 manager.add_cortical_area(m2_area).unwrap();
8590
8591 manager
8592 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8593 .unwrap();
8594 manager
8595 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8596 .unwrap();
8597
8598 let mapping_data = vec![serde_json::json!({
8599 "morphology_id": "associative_memory",
8600 "morphology_scalar": 1,
8601 "postSynapticCurrent_multiplier": 1.0,
8602 "plasticity_flag": true,
8603 "plasticity_constant": 1,
8604 "ltp_multiplier": 1,
8605 "ltd_multiplier": 1,
8606 "plasticity_window": 5,
8607 })];
8608 manager
8609 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
8610 .unwrap();
8611 let created = manager
8612 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8613 .unwrap();
8614 assert!(
8615 created > 0,
8616 "Expected associative memory mapping between memory areas to create synapses"
8617 );
8618 let npu_guard = dyn_npu.lock().unwrap();
8619 let assoc_tagged =
8620 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
8621 assert!(
8622 assoc_tagged >= 1,
8623 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
8624 );
8625 }
8626
8627 #[test]
8628 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
8629 use crate::models::cortical_area::CorticalArea;
8630 use feagi_npu_burst_engine::backend::CPUBackend;
8631 use feagi_npu_burst_engine::TracingMutex;
8632 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8633 use feagi_npu_runtime::StdRuntime;
8634 use feagi_structures::genomic::cortical_area::{
8635 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8636 MemoryCorticalType,
8637 };
8638 use std::sync::Arc;
8639
8640 let runtime = StdRuntime;
8641 let backend = CPUBackend::new();
8642 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8643 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8644 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8645 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8646
8647 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8648 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
8649
8650 let src_area = CorticalArea::new(
8651 src_id,
8652 0,
8653 "Source Area".to_string(),
8654 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8655 (0, 0, 0).into(),
8656 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8657 )
8658 .unwrap();
8659 let mut mem_area = CorticalArea::new(
8660 mem_id,
8661 0,
8662 "Memory Area".to_string(),
8663 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8664 (0, 0, 0).into(),
8665 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8666 )
8667 .unwrap();
8668 mem_area
8669 .properties
8670 .insert("is_mem_type".to_string(), serde_json::json!(true));
8671 mem_area
8672 .properties
8673 .insert("temporal_depth".to_string(), serde_json::json!(1));
8674
8675 manager.add_cortical_area(src_area).unwrap();
8676 manager.add_cortical_area(mem_area).unwrap();
8677
8678 let twin_id = manager
8679 .build_memory_twin_id(&mem_id, &src_id)
8680 .expect("Failed to build twin id");
8681 let twin_area = CorticalArea::new(
8682 twin_id,
8683 0,
8684 "Source Area_twin".to_string(),
8685 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8686 (0, 0, 0).into(),
8687 CorticalAreaType::Custom(
8688 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8689 ),
8690 )
8691 .unwrap();
8692 manager.add_cortical_area(twin_area).unwrap();
8693
8694 let repaired = manager
8695 .ensure_memory_twin_area(&mem_id, &src_id)
8696 .expect("Failed to repair twin");
8697 assert_eq!(repaired, twin_id);
8698
8699 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
8700 let twin_map = mem_area
8701 .properties
8702 .get("memory_twin_areas")
8703 .and_then(|v| v.as_object())
8704 .expect("memory_twin_areas should be set");
8705 let twin_id_str = twin_map
8706 .get(&src_id.as_base_64())
8707 .and_then(|v| v.as_str())
8708 .expect("Missing twin entry for upstream area");
8709 assert_eq!(twin_id_str, twin_id.as_base_64());
8710
8711 let replay_map = mem_area
8712 .properties
8713 .get("cortical_mapping_dst")
8714 .and_then(|v| v.as_object())
8715 .and_then(|map| map.get(&twin_id.as_base_64()))
8716 .and_then(|v| v.as_array())
8717 .expect("Missing memory replay mapping for twin area");
8718 let uses_replay = replay_map.iter().any(|rule| {
8719 rule.get("morphology_id")
8720 .and_then(|v| v.as_str())
8721 .is_some_and(|id| id == "memory_replay")
8722 });
8723 assert!(uses_replay, "Expected memory_replay mapping for twin area");
8724
8725 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8726 assert_eq!(
8727 twin_area
8728 .properties
8729 .get("memory_twin_of")
8730 .and_then(|v| v.as_str()),
8731 Some(src_id.as_base_64().as_str())
8732 );
8733 assert_eq!(
8734 twin_area
8735 .properties
8736 .get("memory_twin_for")
8737 .and_then(|v| v.as_str()),
8738 Some(mem_id.as_base_64().as_str())
8739 );
8740 }
8741
8742 #[test]
8743 fn test_memory_twin_inherits_upstream_parent_region() {
8744 use crate::models::cortical_area::CorticalArea;
8745 use crate::models::BrainRegion;
8746 use feagi_npu_burst_engine::backend::CPUBackend;
8747 use feagi_npu_burst_engine::TracingMutex;
8748 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8749 use feagi_npu_runtime::StdRuntime;
8750 use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
8751 use feagi_structures::genomic::cortical_area::{
8752 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8753 MemoryCorticalType,
8754 };
8755 use std::sync::Arc;
8756
8757 let runtime = StdRuntime;
8758 let backend = CPUBackend::new();
8759 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8760 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8761 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8762 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8763 let src_region_id = RegionID::new();
8764 let src_region_id_str = src_region_id.to_string();
8765 manager
8766 .add_brain_region(
8767 BrainRegion::new(
8768 src_region_id,
8769 "Src Region".to_string(),
8770 RegionType::Undefined,
8771 )
8772 .unwrap(),
8773 None,
8774 )
8775 .unwrap();
8776 let mem_region_id = RegionID::new();
8777 let mem_region_id_str = mem_region_id.to_string();
8778 manager
8779 .add_brain_region(
8780 BrainRegion::new(
8781 mem_region_id,
8782 "Mem Region".to_string(),
8783 RegionType::Undefined,
8784 )
8785 .unwrap(),
8786 None,
8787 )
8788 .unwrap();
8789
8790 let src_id = CorticalID::try_from_bytes(b"csrc1001").unwrap();
8791 let mem_id = CorticalID::try_from_bytes(b"mmem1001").unwrap();
8792
8793 let mut src_area = CorticalArea::new(
8794 src_id,
8795 0,
8796 "Origin".to_string(),
8797 CorticalAreaDimensions::new(4, 4, 1).unwrap(),
8798 (10, 20, 0).into(),
8799 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8800 )
8801 .unwrap();
8802 src_area.properties.insert(
8803 "parent_region_id".to_string(),
8804 serde_json::json!(src_region_id_str.clone()),
8805 );
8806
8807 let mut mem_area = CorticalArea::new(
8808 mem_id,
8809 0,
8810 "Memory".to_string(),
8811 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8812 (200, 300, 0).into(),
8813 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8814 )
8815 .unwrap();
8816 mem_area
8817 .properties
8818 .insert("is_mem_type".to_string(), serde_json::json!(true));
8819 mem_area
8820 .properties
8821 .insert("temporal_depth".to_string(), serde_json::json!(1));
8822 mem_area.properties.insert(
8823 "parent_region_id".to_string(),
8824 serde_json::json!(mem_region_id_str.clone()),
8825 );
8826
8827 manager.add_cortical_area(src_area).unwrap();
8828 manager.add_cortical_area(mem_area).unwrap();
8829
8830 let mapping_data = vec![serde_json::json!({
8831 "morphology_id": "episodic_memory",
8832 "morphology_scalar": 1,
8833 "postSynapticCurrent_multiplier": 1.0,
8834 })];
8835 manager
8836 .update_cortical_mapping(&src_id, &mem_id, mapping_data)
8837 .unwrap();
8838 manager
8839 .regenerate_synapses_for_mapping(&src_id, &mem_id)
8840 .unwrap();
8841
8842 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
8843 let twin_map = memory_area
8844 .properties
8845 .get("memory_twin_areas")
8846 .and_then(|v| v.as_object())
8847 .expect("memory_twin_areas should be set");
8848 let twin_id = CorticalID::try_from_base_64(
8849 twin_map
8850 .get(&src_id.as_base_64())
8851 .and_then(|v| v.as_str())
8852 .expect("missing twin for source"),
8853 )
8854 .unwrap();
8855 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8856
8857 assert_eq!(
8858 twin_area
8859 .properties
8860 .get("parent_region_id")
8861 .and_then(|v| v.as_str()),
8862 Some(src_region_id_str.as_str()),
8863 "Twin should inherit upstream/source parent region"
8864 );
8865 assert_ne!(
8866 twin_area
8867 .properties
8868 .get("parent_region_id")
8869 .and_then(|v| v.as_str()),
8870 Some(mem_region_id_str.as_str()),
8871 "Twin must not inherit memory area's parent region"
8872 );
8873 }
8874
8875 #[test]
8876 fn test_memory_twin_diagnostic_explains_memory_upstream_blocker() {
8877 use crate::models::cortical_area::CorticalArea;
8878 use feagi_npu_burst_engine::backend::CPUBackend;
8879 use feagi_npu_burst_engine::TracingMutex;
8880 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8881 use feagi_npu_runtime::StdRuntime;
8882 use feagi_structures::genomic::cortical_area::{
8883 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8884 };
8885 use std::sync::Arc;
8886
8887 let runtime = StdRuntime;
8888 let backend = CPUBackend::new();
8889 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8890 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8891 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8892 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8893
8894 let m1_id = CorticalID::try_from_bytes(b"mmem2001").unwrap();
8895 let m2_id = CorticalID::try_from_bytes(b"mmem2002").unwrap();
8896
8897 let mut m1_area = CorticalArea::new(
8898 m1_id,
8899 0,
8900 "Memory A".to_string(),
8901 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8902 (0, 0, 0).into(),
8903 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8904 )
8905 .unwrap();
8906 m1_area
8907 .properties
8908 .insert("is_mem_type".to_string(), serde_json::json!(true));
8909 m1_area
8910 .properties
8911 .insert("temporal_depth".to_string(), serde_json::json!(1));
8912
8913 let mut m2_area = CorticalArea::new(
8914 m2_id,
8915 0,
8916 "Memory B".to_string(),
8917 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8918 (0, 0, 0).into(),
8919 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8920 )
8921 .unwrap();
8922 m2_area
8923 .properties
8924 .insert("is_mem_type".to_string(), serde_json::json!(true));
8925 m2_area
8926 .properties
8927 .insert("temporal_depth".to_string(), serde_json::json!(1));
8928
8929 manager.add_cortical_area(m1_area).unwrap();
8930 manager.add_cortical_area(m2_area).unwrap();
8931
8932 let mapping_data = vec![serde_json::json!({
8933 "morphology_id": "episodic_memory",
8934 "morphology_scalar": 1,
8935 "postSynapticCurrent_multiplier": 1.0,
8936 })];
8937 manager
8938 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
8939 .unwrap();
8940 manager
8941 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8942 .unwrap();
8943
8944 let diagnostic = manager
8945 .diagnose_memory_twin_for_mapping(&m1_id, &m2_id)
8946 .expect("diagnostic should succeed");
8947 assert!(diagnostic.mapping_exists);
8948 assert_eq!(diagnostic.episodic_rule_count, 1);
8949 assert!(!diagnostic.twin_expected);
8950 assert!(!diagnostic.twin_present);
8951 assert_eq!(
8952 diagnostic.reason.as_deref(),
8953 Some("upstream_is_memory_area_twin_not_supported")
8954 );
8955 }
8956
8957 fn build_max_weight_test_manager() -> (
8962 ConnectomeManager,
8963 CorticalID,
8964 CorticalID,
8965 CorticalID,
8966 CorticalID,
8967 ) {
8968 use feagi_npu_burst_engine::backend::CPUBackend;
8969 use feagi_npu_burst_engine::TracingMutex;
8970 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8971 use feagi_npu_runtime::StdRuntime;
8972 use feagi_structures::genomic::cortical_area::{
8973 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8974 };
8975
8976 let runtime = StdRuntime;
8977 let backend = CPUBackend::new();
8978 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("npu");
8979 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8980 let mut mgr = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
8981 feagi_evolutionary::templates::add_core_morphologies(&mut mgr.morphology_registry);
8985
8986 let src = CorticalID::try_from_bytes(b"cstmwsrc").unwrap();
8987 let dst = CorticalID::try_from_bytes(b"cstmwdst").unwrap();
8988 let reward = CorticalID::try_from_bytes(b"cstmwrwd").unwrap();
8989 let pain = CorticalID::try_from_bytes(b"cstmwpan").unwrap();
8990
8991 for (id, label, kind) in [
8992 (
8993 src,
8994 "src",
8995 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8996 ),
8997 (
8998 dst,
8999 "dst",
9000 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9001 ),
9002 (
9003 reward,
9004 "reward",
9005 CorticalAreaType::Custom(
9006 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9007 ),
9008 ),
9009 (
9010 pain,
9011 "pain",
9012 CorticalAreaType::Custom(
9013 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9014 ),
9015 ),
9016 ] {
9017 mgr.add_cortical_area(
9018 CorticalArea::new(
9019 id,
9020 0,
9021 label.to_string(),
9022 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9023 (0, 0, 0).into(),
9024 kind,
9025 )
9026 .unwrap(),
9027 )
9028 .unwrap();
9029 mgr.add_neuron(&id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9030 .unwrap();
9031 }
9032 (mgr, src, dst, reward, pain)
9033 }
9034
9035 fn write_and_regenerate_mapping(
9040 mgr: &mut ConnectomeManager,
9041 src: &CorticalID,
9042 dst: &CorticalID,
9043 rule: serde_json::Value,
9044 ) -> BduResult<usize> {
9045 mgr.update_cortical_mapping(src, dst, vec![rule])?;
9046 mgr.regenerate_synapses_for_mapping(src, dst)
9047 }
9048
9049 #[test]
9053 fn test_max_weight_finite_positive_accepted_on_rstdp_rule() {
9054 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9055
9056 let result = write_and_regenerate_mapping(
9057 &mut mgr,
9058 &src,
9059 &dst,
9060 serde_json::json!({
9061 "morphology_id": "block_to_block",
9062 "morphology_scalar": [1, 1, 1],
9063 "postSynapticCurrent_multiplier": 1,
9064 "plasticity_flag": true,
9065 "plasticity_constant": 1,
9066 "ltp_multiplier": 1,
9067 "ltd_multiplier": 1,
9068 "plasticity_window": 10,
9069 "synaptic_delay_bursts": 1,
9070 "plasticity_mode": "rstdp",
9071 "eligibility_decay_bursts": 50,
9072 "reward_source_area": reward.as_base_64(),
9073 "punishment_source_area": pain.as_base_64(),
9074 "max_weight": 12.5,
9075 }),
9076 );
9077 assert!(
9078 result.is_ok(),
9079 "valid max_weight=12.5 must be accepted, got {:?}",
9080 result
9081 );
9082 }
9083
9084 #[test]
9089 fn test_max_weight_invalid_values_rejected() {
9090 for bad in &[
9091 serde_json::json!(0.0),
9092 serde_json::json!(-1.5),
9093 serde_json::json!("not_a_number"),
9094 ] {
9095 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9096 let result = write_and_regenerate_mapping(
9097 &mut mgr,
9098 &src,
9099 &dst,
9100 serde_json::json!({
9101 "morphology_id": "block_to_block",
9102 "morphology_scalar": [1, 1, 1],
9103 "postSynapticCurrent_multiplier": 1,
9104 "plasticity_flag": true,
9105 "plasticity_constant": 1,
9106 "ltp_multiplier": 1,
9107 "ltd_multiplier": 1,
9108 "plasticity_window": 10,
9109 "synaptic_delay_bursts": 1,
9110 "plasticity_mode": "rstdp",
9111 "eligibility_decay_bursts": 50,
9112 "reward_source_area": reward.as_base_64(),
9113 "punishment_source_area": pain.as_base_64(),
9114 "max_weight": bad,
9115 }),
9116 );
9117 assert!(
9118 result.is_err(),
9119 "max_weight={:?} should have been rejected, got {:?}",
9120 bad,
9121 result
9122 );
9123 }
9124 }
9125
9126 #[test]
9129 fn test_ltp_ltd_multiplier_out_of_i8_range_rejected() {
9130 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9131 let result = write_and_regenerate_mapping(
9132 &mut mgr,
9133 &src,
9134 &dst,
9135 serde_json::json!({
9136 "morphology_id": "block_to_block",
9137 "morphology_scalar": [1, 1, 1],
9138 "postSynapticCurrent_multiplier": 1,
9139 "plasticity_flag": true,
9140 "plasticity_constant": 1,
9141 "ltp_multiplier": 200,
9142 "ltd_multiplier": 1,
9143 "plasticity_window": 10,
9144 "synaptic_delay_bursts": 1,
9145 "plasticity_mode": "rstdp",
9146 "eligibility_decay_bursts": 50,
9147 "reward_source_area": reward.as_base_64(),
9148 "punishment_source_area": pain.as_base_64(),
9149 }),
9150 );
9151 assert!(
9152 result.is_err(),
9153 "ltp_multiplier=200 must be rejected (i8 range); got {:?}",
9154 result
9155 );
9156 }
9157
9158 #[test]
9161 fn test_max_weight_rejected_when_plasticity_off() {
9162 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
9163
9164 let result = write_and_regenerate_mapping(
9165 &mut mgr,
9166 &src,
9167 &dst,
9168 serde_json::json!({
9169 "morphology_id": "block_to_block",
9170 "morphology_scalar": [1, 1, 1],
9171 "postSynapticCurrent_multiplier": 1,
9172 "plasticity_flag": true,
9177 "plasticity_constant": 0,
9178 "ltp_multiplier": 0,
9179 "ltd_multiplier": 0,
9180 "plasticity_window": 0,
9181 "synaptic_delay_bursts": 1,
9182 "plasticity_mode": "off",
9183 "max_weight": 10.0,
9184 }),
9185 );
9186 assert!(
9187 result.is_err(),
9188 "max_weight on off-mode rule must be rejected; got {:?}",
9189 result
9190 );
9191 }
9192
9193 #[test]
9194 fn test_plasticity_eta_rejected_when_plasticity_off() {
9195 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
9196
9197 let result = write_and_regenerate_mapping(
9198 &mut mgr,
9199 &src,
9200 &dst,
9201 serde_json::json!({
9202 "morphology_id": "block_to_block",
9203 "morphology_scalar": [1, 1, 1],
9204 "postSynapticCurrent_multiplier": 1,
9205 "plasticity_flag": true,
9206 "plasticity_constant": 0,
9207 "ltp_multiplier": 0,
9208 "ltd_multiplier": 0,
9209 "plasticity_window": 0,
9210 "synaptic_delay_bursts": 1,
9211 "plasticity_mode": "off",
9212 "plasticity_eta": 0.5,
9213 }),
9214 );
9215 assert!(
9216 result.is_err(),
9217 "plasticity_eta on off-mode rule must be rejected; got {:?}",
9218 result
9219 );
9220 }
9221
9222 #[test]
9223 fn test_plasticity_eta_non_positive_rejected() {
9224 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
9225
9226 let result = write_and_regenerate_mapping(
9227 &mut mgr,
9228 &src,
9229 &dst,
9230 serde_json::json!({
9231 "morphology_id": "block_to_block",
9232 "morphology_scalar": [1, 1, 1],
9233 "postSynapticCurrent_multiplier": 1,
9234 "plasticity_flag": true,
9235 "plasticity_constant": 1,
9236 "ltp_multiplier": 1,
9237 "ltd_multiplier": 1,
9238 "plasticity_window": 10,
9239 "synaptic_delay_bursts": 1,
9240 "plasticity_mode": "rstdp",
9241 "eligibility_decay_bursts": 50,
9242 "reward_source_area": reward.as_base_64(),
9243 "punishment_source_area": pain.as_base_64(),
9244 "plasticity_eta": 0.0,
9245 }),
9246 );
9247 assert!(
9248 result.is_err(),
9249 "plasticity_eta=0 must be rejected; got {:?}",
9250 result
9251 );
9252 }
9253}