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
41type BrainRegionIoRegistry = HashMap<String, (Vec<String>, Vec<String>)>;
42
43use crate::models::{BrainRegion, BrainRegionHierarchy, CorticalArea, CorticalAreaDimensions};
44use crate::types::{BduError, BduResult};
45use feagi_npu_neural::types::NeuronId;
46use feagi_structures::genomic::cortical_area::{
47 CoreCorticalType, CorticalAreaType, CorticalID, CustomCorticalType,
48};
49use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
50
51use feagi_state_manager::StateManager;
54
55const DATA_HASH_SEED: u64 = 0;
56const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
58
59static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
64 Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ConnectomeConfig {
69 pub max_neurons: usize,
71
72 pub max_synapses: usize,
74
75 pub backend: String,
77}
78
79impl Default for ConnectomeConfig {
80 fn default() -> Self {
81 Self {
82 max_neurons: 10_000_000,
83 max_synapses: 100_000_000,
84 backend: "cpu".to_string(),
85 }
86 }
87}
88
89pub struct ConnectomeManager {
111 cortical_areas: HashMap<CorticalID, CorticalArea>,
113
114 cortical_id_to_idx: HashMap<CorticalID, u32>,
116
117 cortical_idx_to_id: HashMap<u32, CorticalID>,
119
120 next_cortical_idx: u32,
122
123 brain_regions: BrainRegionHierarchy,
125
126 morphology_registry: feagi_evolutionary::MorphologyRegistry,
128
129 config: ConnectomeConfig,
131
132 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
138
139 #[cfg(feature = "plasticity")]
141 plasticity_executor:
142 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
143
144 cached_neuron_count: Arc<AtomicUsize>,
147
148 cached_synapse_count: Arc<AtomicUsize>,
151
152 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
155
156 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
159
160 initialized: bool,
162
163 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
165}
166
167type NeuronData = (
169 u32,
170 u32,
171 u32,
172 f32,
173 f32,
174 f32,
175 f32,
176 i32,
177 u16,
178 f32,
179 u16,
180 u16,
181 bool,
182);
183
184impl ConnectomeManager {
185 fn get_mapping_rules_for_destination<'a>(
186 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
187 dst_area_id: &CorticalID,
188 ) -> Option<&'a Vec<serde_json::Value>> {
189 if let Some(rules) = mapping_dst
190 .get(&dst_area_id.as_base_64())
191 .and_then(|value| value.as_array())
192 {
193 return Some(rules);
194 }
195
196 for (raw_dst_key, rules_value) in mapping_dst {
199 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
200 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
201 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
202 continue;
203 }
204 if let Some(rules) = rules_value.as_array() {
205 return Some(rules);
206 }
207 }
208
209 None
210 }
211
212 fn new() -> Self {
214 Self {
215 cortical_areas: HashMap::new(),
216 cortical_id_to_idx: HashMap::new(),
217 cortical_idx_to_id: HashMap::new(),
218 next_cortical_idx: 3, brain_regions: BrainRegionHierarchy::new(),
221 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
222 config: ConnectomeConfig::default(),
223 npu: None,
224 #[cfg(feature = "plasticity")]
225 plasticity_executor: None,
226 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
227 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
228 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
229 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
230 initialized: false,
231 last_fatigue_calculation: Arc::new(Mutex::new(
232 std::time::Instant::now() - std::time::Duration::from_secs(10),
233 )), }
235 }
236
237 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
254 Arc::clone(&*INSTANCE)
257 }
258
259 pub fn new_for_testing() -> Self {
287 Self {
288 cortical_areas: HashMap::new(),
289 cortical_id_to_idx: HashMap::new(),
290 cortical_idx_to_id: HashMap::new(),
291 next_cortical_idx: 0,
292 brain_regions: BrainRegionHierarchy::new(),
293 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
294 config: ConnectomeConfig::default(),
295 npu: None,
296 #[cfg(feature = "plasticity")]
297 plasticity_executor: None,
298 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
299 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
300 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
301 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
302 initialized: false,
303 last_fatigue_calculation: Arc::new(Mutex::new(
304 std::time::Instant::now() - std::time::Duration::from_secs(10),
305 )),
306 }
307 }
308
309 pub fn new_for_testing_with_npu(
325 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
326 ) -> Self {
327 Self {
328 cortical_areas: HashMap::new(),
329 cortical_id_to_idx: HashMap::new(),
330 cortical_idx_to_id: HashMap::new(),
331 next_cortical_idx: 3,
332 brain_regions: BrainRegionHierarchy::new(),
333 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
334 config: ConnectomeConfig::default(),
335 npu: Some(npu),
336 #[cfg(feature = "plasticity")]
337 plasticity_executor: None,
338 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
339 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
340 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
341 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
342 initialized: false,
343 last_fatigue_calculation: Arc::new(Mutex::new(
344 std::time::Instant::now() - std::time::Duration::from_secs(10),
345 )),
346 }
347 }
348
349 pub fn setup_core_morphologies_for_testing(&mut self) {
359 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
360 }
361
362 #[cfg(test)]
371 pub fn reset_for_testing() {
372 let mut instance = INSTANCE.write();
373 *instance = Self::new();
374 }
375
376 fn update_state_hashes(
382 &self,
383 brain_regions: Option<u64>,
384 cortical_areas: Option<u64>,
385 brain_geometry: Option<u64>,
386 morphologies: Option<u64>,
387 cortical_mappings: Option<u64>,
388 ) {
389 let state_manager = StateManager::instance();
390 let state_manager = state_manager.read();
391 if let Some(value) = brain_regions {
392 state_manager.set_brain_regions_hash(value);
393 }
394 if let Some(value) = cortical_areas {
395 state_manager.set_cortical_areas_hash(value);
396 }
397 if let Some(value) = brain_geometry {
398 state_manager.set_brain_geometry_hash(value);
399 }
400 if let Some(value) = morphologies {
401 state_manager.set_morphologies_hash(value);
402 }
403 if let Some(value) = cortical_mappings {
404 state_manager.set_cortical_mappings_hash(value);
405 }
406 }
407
408 fn refresh_brain_regions_hash(&self) {
410 let hash = self.compute_brain_regions_hash();
411 self.update_state_hashes(Some(hash), None, None, None, None);
412 }
413
414 #[allow(dead_code)]
415 fn refresh_cortical_areas_hash(&self) {
417 let hash = self.compute_cortical_areas_hash();
418 self.update_state_hashes(None, Some(hash), None, None, None);
419 }
420
421 #[allow(dead_code)]
422 fn refresh_brain_geometry_hash(&self) {
424 let hash = self.compute_brain_geometry_hash();
425 self.update_state_hashes(None, None, Some(hash), None, None);
426 }
427
428 fn refresh_morphologies_hash(&self) {
430 let hash = self.compute_morphologies_hash();
431 self.update_state_hashes(None, None, None, Some(hash), None);
432 }
433
434 fn refresh_cortical_mappings_hash(&self) {
436 let hash = self.compute_cortical_mappings_hash();
437 self.update_state_hashes(None, None, None, None, Some(hash));
438 }
439
440 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
442 let cortical_hash = if properties_changed {
443 Some(self.compute_cortical_areas_hash())
444 } else {
445 None
446 };
447 let geometry_hash = if geometry_changed {
448 Some(self.compute_brain_geometry_hash())
449 } else {
450 None
451 };
452 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
453 }
454
455 fn compute_brain_regions_hash(&self) -> u64 {
457 let mut hasher = Xxh64::new(DATA_HASH_SEED);
458 let mut region_ids: Vec<String> = self
459 .brain_regions
460 .get_all_region_ids()
461 .into_iter()
462 .cloned()
463 .collect();
464 region_ids.sort();
465
466 for region_id in region_ids {
467 let Some(region) = self.brain_regions.get_region(®ion_id) else {
468 continue;
469 };
470 Self::hash_str(&mut hasher, ®ion_id);
471 Self::hash_str(&mut hasher, ®ion.name);
472 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
473 let parent_id = self.brain_regions.get_parent(®ion_id);
474 match parent_id {
475 Some(parent) => Self::hash_str(&mut hasher, parent),
476 None => Self::hash_str(&mut hasher, "null"),
477 }
478
479 let mut cortical_ids: Vec<String> = region
480 .cortical_areas
481 .iter()
482 .map(|id| id.as_base_64())
483 .collect();
484 cortical_ids.sort();
485 for cortical_id in cortical_ids {
486 Self::hash_str(&mut hasher, &cortical_id);
487 }
488
489 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
490 }
491
492 hasher.finish() & HASH_SAFE_MASK
493 }
494
495 fn compute_cortical_areas_hash(&self) -> u64 {
497 let mut hasher = Xxh64::new(DATA_HASH_SEED);
498 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
499 areas.sort_by_key(|area| area.cortical_id.as_base_64());
500
501 for area in areas {
502 let cortical_id = area.cortical_id.as_base_64();
503 Self::hash_str(&mut hasher, &cortical_id);
504 hasher.write_u32(area.cortical_idx);
505 Self::hash_str(&mut hasher, &area.name);
506 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
507
508 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
509 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
510 }
511
512 hasher.finish() & HASH_SAFE_MASK
513 }
514
515 fn compute_brain_geometry_hash(&self) -> u64 {
517 let mut hasher = Xxh64::new(DATA_HASH_SEED);
518 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
519 areas.sort_by_key(|area| area.cortical_id.as_base_64());
520
521 for area in areas {
522 let cortical_id = area.cortical_id.as_base_64();
523 Self::hash_str(&mut hasher, &cortical_id);
524
525 Self::hash_i32(&mut hasher, area.position.x);
526 Self::hash_i32(&mut hasher, area.position.y);
527 Self::hash_i32(&mut hasher, area.position.z);
528
529 Self::hash_u32(&mut hasher, area.dimensions.width);
530 Self::hash_u32(&mut hasher, area.dimensions.height);
531 Self::hash_u32(&mut hasher, area.dimensions.depth);
532
533 let coord_2d = area
534 .properties
535 .get("coordinate_2d")
536 .or_else(|| area.properties.get("coordinates_2d"));
537 match coord_2d {
538 Some(value) => Self::hash_json_value(&mut hasher, value),
539 None => Self::hash_str(&mut hasher, "null"),
540 }
541 }
542
543 hasher.finish() & HASH_SAFE_MASK
544 }
545
546 fn compute_morphologies_hash(&self) -> u64 {
548 let mut hasher = Xxh64::new(DATA_HASH_SEED);
549 let mut morphology_ids = self.morphology_registry.morphology_ids();
550 morphology_ids.sort();
551
552 for morphology_id in morphology_ids {
553 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
554 Self::hash_str(&mut hasher, &morphology_id);
555 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
556 Self::hash_str(&mut hasher, &morphology.class);
557 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
558 Self::hash_json_value(&mut hasher, &value);
559 }
560 }
561 }
562
563 hasher.finish() & HASH_SAFE_MASK
564 }
565
566 fn compute_cortical_mappings_hash(&self) -> u64 {
568 let mut hasher = Xxh64::new(DATA_HASH_SEED);
569 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
570 areas.sort_by_key(|area| area.cortical_id.as_base_64());
571
572 for area in areas {
573 let cortical_id = area.cortical_id.as_base_64();
574 Self::hash_str(&mut hasher, &cortical_id);
575 if let Some(serde_json::Value::Object(map)) =
576 area.properties.get("cortical_mapping_dst")
577 {
578 let mut dest_ids: Vec<&String> = map.keys().collect();
579 dest_ids.sort();
580 for dest_id in dest_ids {
581 Self::hash_str(&mut hasher, dest_id);
582 if let Some(value) = map.get(dest_id) {
583 Self::hash_json_value(&mut hasher, value);
584 }
585 }
586 } else {
587 Self::hash_str(&mut hasher, "null");
588 }
589 }
590
591 hasher.finish() & HASH_SAFE_MASK
592 }
593
594 fn hash_str(hasher: &mut Xxh64, value: &str) {
596 hasher.write(value.as_bytes());
597 hasher.write_u8(0);
598 }
599
600 fn hash_i32(hasher: &mut Xxh64, value: i32) {
602 hasher.write(&value.to_le_bytes());
603 }
604
605 fn hash_u32(hasher: &mut Xxh64, value: u32) {
607 hasher.write(&value.to_le_bytes());
608 }
609
610 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
612 match value {
613 serde_json::Value::Null => {
614 hasher.write_u8(0);
615 }
616 serde_json::Value::Bool(val) => {
617 hasher.write_u8(1);
618 hasher.write_u8(*val as u8);
619 }
620 serde_json::Value::Number(num) => {
621 hasher.write_u8(2);
622 Self::hash_str(hasher, &num.to_string());
623 }
624 serde_json::Value::String(val) => {
625 hasher.write_u8(3);
626 Self::hash_str(hasher, val);
627 }
628 serde_json::Value::Array(items) => {
629 hasher.write_u8(4);
630 for item in items {
631 Self::hash_json_value(hasher, item);
632 }
633 }
634 serde_json::Value::Object(map) => {
635 hasher.write_u8(5);
636 let mut keys: Vec<&String> = map.keys().collect();
637 keys.sort();
638 for key in keys {
639 Self::hash_str(hasher, key);
640 if let Some(val) = map.get(key) {
641 Self::hash_json_value(hasher, val);
642 }
643 }
644 }
645 }
646 }
647
648 fn hash_properties_filtered(
650 hasher: &mut Xxh64,
651 properties: &HashMap<String, serde_json::Value>,
652 excluded_keys: &[&str],
653 ) {
654 let mut keys: Vec<&String> = properties.keys().collect();
655 keys.sort();
656 for key in keys {
657 if excluded_keys.contains(&key.as_str()) {
658 continue;
659 }
660 Self::hash_str(hasher, key);
661 if let Some(value) = properties.get(key) {
662 Self::hash_json_value(hasher, value);
663 }
664 }
665 }
666
667 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
688 if self.cortical_areas.contains_key(&area.cortical_id) {
690 return Err(BduError::InvalidArea(format!(
691 "Cortical area {} already exists",
692 area.cortical_id
693 )));
694 }
695
696 use feagi_structures::genomic::cortical_area::CoreCorticalType;
699
700 let death_id = CoreCorticalType::Death.to_cortical_id();
701 let power_id = CoreCorticalType::Power.to_cortical_id();
702 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
703
704 let is_death_area = area.cortical_id == death_id;
705 let is_power_area = area.cortical_id == power_id;
706 let is_fatigue_area = area.cortical_id == fatigue_id;
707
708 if is_death_area {
709 trace!(
710 target: "feagi-bdu",
711 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
712 area.cortical_id
713 );
714 area.cortical_idx = 0;
715 } else if is_power_area {
716 trace!(
717 target: "feagi-bdu",
718 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
719 area.cortical_id
720 );
721 area.cortical_idx = 1;
722 } else if is_fatigue_area {
723 trace!(
724 target: "feagi-bdu",
725 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
726 area.cortical_id
727 );
728 area.cortical_idx = 2;
729 } else {
730 if area.cortical_idx == 0 {
732 area.cortical_idx = self.next_cortical_idx;
733 self.next_cortical_idx += 1;
734 trace!(
735 target: "feagi-bdu",
736 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be ≥3)",
737 area.cortical_idx,
738 area.cortical_id.as_base_64()
739 );
740 } else {
741 if area.cortical_idx == 0 || area.cortical_idx == 1 || area.cortical_idx == 2 {
743 warn!(
744 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
745 area.cortical_id, area.cortical_idx);
746 area.cortical_idx = self.next_cortical_idx;
747 self.next_cortical_idx += 1;
748 info!(
749 " Reassigned '{}' to cortical_idx={}",
750 area.cortical_id, area.cortical_idx
751 );
752 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
753 return Err(BduError::InvalidArea(format!(
754 "Cortical index {} is already in use",
755 area.cortical_idx
756 )));
757 }
758
759 if area.cortical_idx >= self.next_cortical_idx {
761 self.next_cortical_idx = area.cortical_idx + 1;
762 }
763 }
764 }
765
766 let cortical_id = area.cortical_id;
767 let cortical_idx = area.cortical_idx;
768
769 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
771 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
772
773 area.properties
775 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
776
777 let parent_region_id = area
786 .properties
787 .get("parent_region_id")
788 .and_then(|v| v.as_str())
789 .map(|s| s.to_string());
790
791 self.cortical_areas.insert(cortical_id, area);
793
794 if let Some(region_id) = parent_region_id {
796 let region = self
797 .brain_regions
798 .get_region_mut(®ion_id)
799 .ok_or_else(|| {
800 BduError::InvalidArea(format!(
801 "Unknown parent_region_id '{}' for cortical area {}",
802 region_id,
803 cortical_id.as_base_64()
804 ))
805 })?;
806 region.add_area(cortical_id);
807 }
808
809 {
812 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
813 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
814 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
815 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
816 }
817 let state_manager = StateManager::instance();
819 let state_manager = state_manager.read();
820 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
821
822 if let Some(ref npu) = self.npu {
826 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
827 if let Ok(mut npu_lock) = npu.lock() {
828 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
829 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
830 trace!(
831 target: "feagi-bdu",
832 "Registered cortical area idx={} -> '{}' in NPU",
833 cortical_idx,
834 cortical_id.as_base_64()
835 );
836 }
837 }
838
839 self.sync_cortical_area_flags_to_npu()?;
841
842 self.initialized = true;
843
844 self.refresh_cortical_area_hashes(true, true);
845 self.refresh_brain_regions_hash();
846
847 Ok(cortical_idx)
848 }
849
850 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
865 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
866 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
867 })?;
868
869 self.cortical_id_to_idx.remove(cortical_id);
871 self.cortical_idx_to_id.remove(&area.cortical_idx);
872
873 self.refresh_cortical_area_hashes(true, true);
874 Ok(())
875 }
876
877 pub fn rename_cortical_area_id(
881 &mut self,
882 old_id: &CorticalID,
883 new_id: CorticalID,
884 new_cortical_type: CorticalAreaType,
885 ) -> BduResult<()> {
886 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
887 }
888
889 pub fn rename_cortical_area_id_with_options(
891 &mut self,
892 old_id: &CorticalID,
893 new_id: CorticalID,
894 new_cortical_type: CorticalAreaType,
895 update_npu_registry: bool,
896 ) -> BduResult<()> {
897 if !self.cortical_areas.contains_key(old_id) {
898 return Err(BduError::InvalidArea(format!(
899 "Cortical area {} does not exist",
900 old_id
901 )));
902 }
903 if self.cortical_areas.contains_key(&new_id) {
904 return Err(BduError::InvalidArea(format!(
905 "Cortical area {} already exists",
906 new_id
907 )));
908 }
909
910 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
911 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
912 })?;
913 let cortical_idx = area.cortical_idx;
914 area.cortical_id = new_id;
915 area.cortical_type = new_cortical_type;
916
917 self.cortical_areas.insert(new_id, area);
918 self.cortical_id_to_idx.remove(old_id);
919 self.cortical_id_to_idx.insert(new_id, cortical_idx);
920 self.cortical_idx_to_id.insert(cortical_idx, new_id);
921
922 {
924 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
925 if let Some(value) = neuron_cache.remove(old_id) {
926 neuron_cache.insert(new_id, value);
927 }
928 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
929 if let Some(value) = synapse_cache.remove(old_id) {
930 synapse_cache.insert(new_id, value);
931 }
932 }
933
934 self.brain_regions.rename_cortical_area_id(old_id, new_id);
936
937 let old_id_str = old_id.as_base_64();
939 let new_id_str = new_id.as_base_64();
940 for area in self.cortical_areas.values_mut() {
941 if let Some(mapping) = area
942 .properties
943 .get_mut("cortical_mapping_dst")
944 .and_then(|v| v.as_object_mut())
945 {
946 if let Some(value) = mapping.remove(&old_id_str) {
947 mapping.insert(new_id_str.clone(), value);
948 }
949 }
950 }
951
952 if update_npu_registry {
954 if let Some(ref npu) = self.npu {
955 if let Ok(mut npu_lock) = npu.lock() {
956 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
957 }
958 }
959 }
960
961 self.refresh_cortical_area_hashes(true, true);
962 self.refresh_brain_regions_hash();
963 self.refresh_cortical_mappings_hash();
964
965 Ok(())
966 }
967
968 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
970 self.cortical_areas.get(cortical_id)
971 }
972
973 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
975 self.cortical_areas.get_mut(cortical_id)
976 }
977
978 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
980 self.cortical_id_to_idx.get(cortical_id).copied()
981 }
982
983 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
995 self.brain_regions.find_region_containing_area(cortical_id)
996 }
997
998 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1009 use std::collections::HashSet;
1010
1011 let region_ids: Vec<String> = self
1012 .brain_regions
1013 .get_all_region_ids()
1014 .into_iter()
1015 .cloned()
1016 .collect();
1017
1018 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1019 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1020
1021 for rid in ®ion_ids {
1023 inputs_by_region.insert(rid.clone(), HashSet::new());
1024 outputs_by_region.insert(rid.clone(), HashSet::new());
1025 }
1026
1027 for (src_id, src_area) in &self.cortical_areas {
1028 let Some(dstmap) = src_area
1029 .properties
1030 .get("cortical_mapping_dst")
1031 .and_then(|v| v.as_object())
1032 else {
1033 continue;
1034 };
1035
1036 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1037 warn!(
1038 target: "feagi-bdu",
1039 "Skipping region IO for source area {} (not in any region)",
1040 src_id.as_base_64()
1041 );
1042 continue;
1043 };
1044
1045 for dst_id_str in dstmap.keys() {
1046 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1047 BduError::InvalidArea(format!(
1048 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1049 dst_id_str,
1050 src_id.as_base_64(),
1051 e
1052 ))
1053 })?;
1054
1055 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1056 else {
1057 warn!(
1058 target: "feagi-bdu",
1059 "Skipping region IO for destination area {} (not in any region)",
1060 dst_id.as_base_64()
1061 );
1062 continue;
1063 };
1064
1065 if src_region_id == dst_region_id {
1066 continue;
1067 }
1068
1069 outputs_by_region
1070 .entry(src_region_id.clone())
1071 .or_default()
1072 .insert(src_id.as_base_64());
1073 inputs_by_region
1074 .entry(dst_region_id.clone())
1075 .or_default()
1076 .insert(dst_id.as_base_64());
1077 }
1078 }
1079
1080 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1081 for rid in region_ids {
1082 let mut inputs: Vec<String> = inputs_by_region
1083 .remove(&rid)
1084 .unwrap_or_default()
1085 .into_iter()
1086 .collect();
1087 let mut outputs: Vec<String> = outputs_by_region
1088 .remove(&rid)
1089 .unwrap_or_default()
1090 .into_iter()
1091 .collect();
1092
1093 inputs.sort();
1094 outputs.sort();
1095
1096 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1097 BduError::InvalidArea(format!(
1098 "Unable to recompute region IO: region '{}' not found in hierarchy",
1099 rid
1100 ))
1101 })?;
1102
1103 if inputs.is_empty() {
1104 region.properties.remove("inputs");
1105 } else {
1106 region
1107 .properties
1108 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1109 }
1110
1111 if outputs.is_empty() {
1112 region.properties.remove("outputs");
1113 } else {
1114 region
1115 .properties
1116 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1117 }
1118
1119 computed.insert(rid, (inputs, outputs));
1120 }
1121
1122 self.refresh_brain_regions_hash();
1123
1124 Ok(computed)
1125 }
1126
1127 pub fn get_root_region_id(&self) -> Option<String> {
1133 self.brain_regions.get_root_region_id()
1134 }
1135
1136 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1138 self.cortical_idx_to_id.get(&cortical_idx)
1139 }
1140
1141 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1144 self.cortical_idx_to_id
1145 .iter()
1146 .map(|(idx, id)| (*idx, id.as_base_64()))
1147 .collect()
1148 }
1149
1150 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1155 let mut granularities = ahash::AHashMap::new();
1156 for (cortical_id, area) in &self.cortical_areas {
1157 let cortical_idx = self
1158 .cortical_id_to_idx
1159 .get(cortical_id)
1160 .copied()
1161 .unwrap_or(0);
1162
1163 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1166 if let Some(arr) = granularity_json.as_array() {
1167 if arr.len() == 3 {
1168 let x_opt = arr[0]
1169 .as_u64()
1170 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1171 let y_opt = arr[1]
1172 .as_u64()
1173 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1174 let z_opt = arr[2]
1175 .as_u64()
1176 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1177
1178 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1179 let granularity = (x as u32, y as u32, z as u32);
1180 if granularity != (1, 1, 1) {
1182 granularities.insert(cortical_idx, granularity);
1183 }
1184 }
1185 }
1186 }
1187 }
1188 }
1189 granularities
1190 }
1191
1192 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1194 self.cortical_areas.keys().collect()
1195 }
1196
1197 pub fn get_cortical_area_count(&self) -> usize {
1199 self.cortical_areas.len()
1200 }
1201
1202 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1216 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1217 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1218 if let Some(upstream_array) = upstream_prop.as_array() {
1219 return upstream_array
1220 .iter()
1221 .filter_map(|v| v.as_u64().map(|n| n as u32))
1222 .collect();
1223 }
1224 }
1225
1226 warn!(target: "feagi-bdu",
1228 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1229 target_cortical_id.as_base_64()
1230 );
1231 }
1232
1233 Vec::new()
1234 }
1235
1236 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1238 upstream
1239 .iter()
1240 .filter_map(|idx| {
1241 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1242 let area = self.cortical_areas.get(cortical_id)?;
1243 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1244 None
1245 } else {
1246 Some(*idx)
1247 }
1248 })
1249 .collect()
1250 }
1251
1252 pub fn refresh_upstream_cortical_areas_from_mappings(
1257 &mut self,
1258 target_cortical_id: &CorticalID,
1259 ) -> Vec<u32> {
1260 use std::collections::HashSet;
1261 let target_id_str = target_cortical_id.as_base_64();
1262 let mut upstream_idxs = HashSet::new();
1263 for (src_id, src_area) in &self.cortical_areas {
1264 if src_id == target_cortical_id {
1265 continue;
1266 }
1267 if let Some(mapping) = src_area
1268 .properties
1269 .get("cortical_mapping_dst")
1270 .and_then(|v| v.as_object())
1271 {
1272 if mapping.contains_key(&target_id_str) {
1273 upstream_idxs.insert(src_area.cortical_idx);
1274 }
1275 }
1276 }
1277
1278 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1279 upstream_list.sort_unstable();
1280
1281 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1282 target_area.properties.insert(
1283 "upstream_cortical_areas".to_string(),
1284 serde_json::json!(upstream_list),
1285 );
1286 }
1287
1288 self.get_upstream_cortical_areas(target_cortical_id)
1289 }
1290
1291 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1301 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1302 let upstream_array = area
1303 .properties
1304 .entry("upstream_cortical_areas".to_string())
1305 .or_insert_with(|| serde_json::json!([]));
1306
1307 if let Some(arr) = upstream_array.as_array_mut() {
1308 let src_value = serde_json::json!(src_cortical_idx);
1309 if !arr.contains(&src_value) {
1310 arr.push(src_value);
1311 info!(target: "feagi-bdu",
1312 "✓ Added upstream area idx={} to cortical area '{}'",
1313 src_cortical_idx, target_cortical_id.as_base_64()
1314 );
1315 }
1316 }
1317 }
1318 }
1319
1320 pub fn get_memory_twin_for_upstream_idx(
1322 &self,
1323 memory_area_idx: u32,
1324 upstream_idx: u32,
1325 ) -> Option<CorticalID> {
1326 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1327 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1328 let area = self.cortical_areas.get(memory_id)?;
1329 let mapping = area
1330 .properties
1331 .get("memory_twin_areas")
1332 .and_then(|v| v.as_object())?;
1333 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1334 CorticalID::try_from_base_64(twin_b64).ok()
1335 }
1336
1337 pub fn ensure_memory_twin_area(
1339 &mut self,
1340 memory_area_id: &CorticalID,
1341 upstream_area_id: &CorticalID,
1342 ) -> BduResult<CorticalID> {
1343 use crate::models::CorticalAreaExt;
1344
1345 let register_replay_mapping = |manager: &mut ConnectomeManager,
1346 twin_id: &CorticalID|
1347 -> BduResult<()> {
1348 let Some(npu) = manager.npu.as_ref() else {
1349 return Ok(());
1350 };
1351 let memory_area_idx =
1352 *manager
1353 .cortical_id_to_idx
1354 .get(memory_area_id)
1355 .ok_or_else(|| {
1356 BduError::InvalidArea(format!(
1357 "Memory area idx missing for {}",
1358 memory_area_id.as_base_64()
1359 ))
1360 })?;
1361 let upstream_area_idx = *manager
1362 .cortical_id_to_idx
1363 .get(upstream_area_id)
1364 .ok_or_else(|| {
1365 BduError::InvalidArea(format!(
1366 "Upstream area idx missing for {}",
1367 upstream_area_id.as_base_64()
1368 ))
1369 })?;
1370 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1371 BduError::InvalidArea(format!(
1372 "Twin area idx missing for {}",
1373 twin_id.as_base_64()
1374 ))
1375 })?;
1376 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1377 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1378 })?;
1379 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1380 if let Ok(mut npu_lock) = npu.lock() {
1381 npu_lock.register_memory_twin_mapping(
1382 memory_area_idx,
1383 upstream_area_idx,
1384 twin_area_idx,
1385 potential,
1386 );
1387 }
1388 Ok(())
1389 };
1390
1391 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1392 BduError::InvalidArea(format!(
1393 "Memory area {} not found",
1394 memory_area_id.as_base_64()
1395 ))
1396 })?;
1397 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1398 BduError::InvalidArea(format!(
1399 "Upstream area {} not found",
1400 upstream_area_id.as_base_64()
1401 ))
1402 })?;
1403
1404 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1405 return Err(BduError::InvalidArea(format!(
1406 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1407 upstream_area_id.as_base_64()
1408 )));
1409 }
1410
1411 if let Some(existing) = memory_area
1412 .properties
1413 .get("memory_twin_areas")
1414 .and_then(|v| v.as_object())
1415 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1416 .and_then(|v| v.as_str())
1417 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1418 {
1419 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1420 register_replay_mapping(self, &existing)?;
1421 self.refresh_cortical_mappings_hash();
1422 return Ok(existing);
1423 }
1424
1425 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1426 if self.cortical_areas.contains_key(&twin_id) {
1427 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1428 let expected_source = upstream_area_id.as_base_64();
1429 let expected_target = memory_area_id.as_base_64();
1430 let existing_source = existing
1431 .properties
1432 .get("memory_twin_of")
1433 .and_then(|v| v.as_str());
1434 let existing_target = existing
1435 .properties
1436 .get("memory_twin_for")
1437 .and_then(|v| v.as_str());
1438 if existing_source != Some(expected_source.as_str())
1439 || existing_target != Some(expected_target.as_str())
1440 {
1441 warn!(
1442 target: "feagi-bdu",
1443 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1444 upstream_area_id.as_base_64(),
1445 memory_area_id.as_base_64()
1446 );
1447 existing.properties.insert(
1448 "memory_twin_of".to_string(),
1449 serde_json::json!(expected_source),
1450 );
1451 existing.properties.insert(
1452 "memory_twin_for".to_string(),
1453 serde_json::json!(expected_target),
1454 );
1455 }
1456 }
1457 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1458 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1459 register_replay_mapping(self, &twin_id)?;
1460 self.refresh_cortical_mappings_hash();
1461 return Ok(twin_id);
1462 }
1463
1464 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1465 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1466 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1467 let mut twin_area = CorticalArea::new(
1468 twin_id,
1469 0,
1470 twin_name,
1471 upstream_area.dimensions,
1472 twin_position,
1473 twin_type,
1474 )?;
1475 twin_area.properties = self.build_memory_twin_properties(
1476 memory_area,
1477 upstream_area,
1478 memory_area_id,
1479 upstream_area_id,
1480 );
1481
1482 let _twin_idx = self.add_cortical_area(twin_area)?;
1483 let _ = self.create_neurons_for_area(&twin_id);
1484
1485 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1486 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1487 register_replay_mapping(self, &twin_id)?;
1488 self.refresh_cortical_mappings_hash();
1489 Ok(twin_id)
1490 }
1491
1492 fn build_memory_twin_position(
1493 &self,
1494 memory_area: &CorticalArea,
1495 upstream_area: &CorticalArea,
1496 ) -> GenomeCoordinate3D {
1497 let memory_parent = memory_area
1498 .properties
1499 .get("parent_region_id")
1500 .and_then(|v| v.as_str());
1501 let upstream_parent = upstream_area
1502 .properties
1503 .get("parent_region_id")
1504 .and_then(|v| v.as_str());
1505 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1506
1507 if !same_region {
1508 return GenomeCoordinate3D::new(
1509 memory_area.position.x + 20,
1510 memory_area.position.y,
1511 memory_area.position.z,
1512 );
1513 }
1514
1515 let width = upstream_area.dimensions.width as f32;
1516 let margin = (width * 0.25).ceil() as i32;
1517 let offset = upstream_area.dimensions.width as i32 + margin;
1518 GenomeCoordinate3D::new(
1519 upstream_area.position.x + offset,
1520 upstream_area.position.y,
1521 upstream_area.position.z,
1522 )
1523 }
1524
1525 fn build_memory_twin_id(
1526 &self,
1527 memory_area_id: &CorticalID,
1528 upstream_area_id: &CorticalID,
1529 ) -> BduResult<CorticalID> {
1530 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1531 hasher.write(memory_area_id.as_base_64().as_bytes());
1532 hasher.write(upstream_area_id.as_base_64().as_bytes());
1533 hasher.write(b"memory_twin");
1534 let hash = hasher.finish();
1535 let mut bytes = hash.to_be_bytes();
1536 bytes[0] = b'c';
1537 CorticalID::try_from_bytes(&bytes)
1538 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1539 }
1540
1541 fn build_memory_twin_properties(
1542 &self,
1543 memory_area: &CorticalArea,
1544 upstream_area: &CorticalArea,
1545 memory_area_id: &CorticalID,
1546 upstream_area_id: &CorticalID,
1547 ) -> HashMap<String, serde_json::Value> {
1548 let mut props = upstream_area.properties.clone();
1549 props.remove("cortical_mapping_dst");
1550 props.remove("upstream_cortical_areas");
1551 props.remove("parent_region_id");
1552 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1553 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1554 props.insert(
1555 "memory_twin_of".to_string(),
1556 serde_json::json!(upstream_area_id.as_base_64()),
1557 );
1558 props.insert(
1559 "memory_twin_for".to_string(),
1560 serde_json::json!(memory_area_id.as_base_64()),
1561 );
1562 if let Some(parent_region_id) = memory_area
1563 .properties
1564 .get("parent_region_id")
1565 .and_then(|v| v.as_str())
1566 {
1567 props.insert(
1568 "parent_region_id".to_string(),
1569 serde_json::json!(parent_region_id),
1570 );
1571 }
1572 props
1573 }
1574
1575 fn set_memory_twin_mapping(
1576 &mut self,
1577 memory_area_id: &CorticalID,
1578 upstream_area_id: &CorticalID,
1579 twin_id: &CorticalID,
1580 ) {
1581 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1582 let mapping = memory_area
1583 .properties
1584 .entry("memory_twin_areas".to_string())
1585 .or_insert_with(|| serde_json::json!({}));
1586 if let Some(map) = mapping.as_object_mut() {
1587 map.insert(
1588 upstream_area_id.as_base_64(),
1589 serde_json::json!(twin_id.as_base_64()),
1590 );
1591 }
1592 }
1593 }
1594
1595 fn ensure_memory_replay_mapping(
1596 &mut self,
1597 memory_area_id: &CorticalID,
1598 twin_id: &CorticalID,
1599 ) -> BduResult<()> {
1600 if !self.morphology_registry.contains("memory_replay") {
1601 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1602 }
1603 self.refresh_morphologies_hash();
1604 let mapping_data = vec![serde_json::json!({
1605 "morphology_id": "memory_replay",
1606 "morphology_scalar": [1, 1, 1],
1607 "postSynapticCurrent_multiplier": 1,
1608 "plasticity_flag": false,
1609 "plasticity_constant": 0,
1610 "ltp_multiplier": 0,
1611 "ltd_multiplier": 0,
1612 "plasticity_window": 0,
1613 })];
1614 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1615 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1616 self.refresh_cortical_area_hashes(true, false);
1618 Ok(())
1619 }
1620
1621 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1631 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1632 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1633 if let Some(arr) = upstream_prop.as_array_mut() {
1634 let src_value = serde_json::json!(src_cortical_idx);
1635 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1636 arr.remove(pos);
1637 debug!(target: "feagi-bdu",
1638 "Removed upstream area idx={} from cortical area '{}'",
1639 src_cortical_idx, target_cortical_id.as_base_64()
1640 );
1641 }
1642 }
1643 }
1644 }
1645 }
1646
1647 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1649 self.cortical_areas.contains_key(cortical_id)
1650 }
1651
1652 pub fn is_initialized(&self) -> bool {
1654 self.initialized && !self.cortical_areas.is_empty()
1655 }
1656
1657 pub fn add_brain_region(
1663 &mut self,
1664 region: BrainRegion,
1665 parent_id: Option<String>,
1666 ) -> BduResult<()> {
1667 self.brain_regions.add_region(region, parent_id)?;
1668 self.refresh_brain_regions_hash();
1669 Ok(())
1670 }
1671
1672 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1674 self.brain_regions.remove_region(region_id)?;
1675 self.refresh_brain_regions_hash();
1676 Ok(())
1677 }
1678
1679 pub fn change_brain_region_parent(
1681 &mut self,
1682 region_id: &str,
1683 new_parent_id: &str,
1684 ) -> BduResult<()> {
1685 self.brain_regions.change_parent(region_id, new_parent_id)?;
1686 self.refresh_brain_regions_hash();
1687 Ok(())
1688 }
1689
1690 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1692 self.brain_regions.get_region(region_id)
1693 }
1694
1695 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1697 self.brain_regions.get_region_mut(region_id)
1698 }
1699
1700 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1702 self.brain_regions.get_all_region_ids()
1703 }
1704
1705 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1707 &self.brain_regions
1708 }
1709
1710 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1716 &self.morphology_registry
1717 }
1718
1719 pub fn get_morphology_count(&self) -> usize {
1721 self.morphology_registry.count()
1722 }
1723
1724 pub fn upsert_morphology(
1729 &mut self,
1730 morphology_id: String,
1731 morphology: feagi_evolutionary::Morphology,
1732 ) {
1733 self.morphology_registry
1734 .add_morphology(morphology_id, morphology);
1735 self.refresh_morphologies_hash();
1736 }
1737
1738 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1744 let removed = self.morphology_registry.remove_morphology(morphology_id);
1745 if removed {
1746 self.refresh_morphologies_hash();
1747 }
1748 removed
1749 }
1750
1751 pub fn update_cortical_mapping(
1769 &mut self,
1770 src_area_id: &CorticalID,
1771 dst_area_id: &CorticalID,
1772 mapping_data: Vec<serde_json::Value>,
1773 ) -> BduResult<()> {
1774 use tracing::info;
1775
1776 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1777
1778 let mapping_has_bidirectional_stdp = |rules: &[serde_json::Value]| -> bool {
1780 for rule in rules {
1781 if let Some(obj) = rule.as_object() {
1782 if let Some(morphology_id) = obj.get("morphology_id").and_then(|v| v.as_str()) {
1783 if morphology_id == "associative_memory" {
1784 return true;
1785 }
1786 }
1787 }
1788 }
1789 false
1790 };
1791
1792 let requested_bidirectional = mapping_has_bidirectional_stdp(&mapping_data);
1793 let existing_bidirectional = self
1794 .cortical_areas
1795 .get(src_area_id)
1796 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1797 .and_then(|v| v.as_object())
1798 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1799 .and_then(|v| v.as_array())
1800 .map(|arr| mapping_has_bidirectional_stdp(arr))
1801 .unwrap_or(false);
1802 let should_apply_bidirectional = requested_bidirectional || existing_bidirectional;
1803
1804 {
1805 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1807 crate::types::BduError::InvalidArea(format!(
1808 "Source area not found: {}",
1809 src_area_id
1810 ))
1811 })?;
1812
1813 let cortical_mapping_dst =
1815 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1816 existing.as_object_mut().ok_or_else(|| {
1817 crate::types::BduError::InvalidMorphology(
1818 "cortical_mapping_dst is not an object".to_string(),
1819 )
1820 })?
1821 } else {
1822 src_area
1824 .properties
1825 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1826 src_area
1827 .properties
1828 .get_mut("cortical_mapping_dst")
1829 .unwrap()
1830 .as_object_mut()
1831 .unwrap()
1832 };
1833
1834 if mapping_data.is_empty() {
1836 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1838 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1839 } else {
1840 cortical_mapping_dst.insert(
1841 dst_area_id.as_base_64(),
1842 serde_json::Value::Array(mapping_data.clone()),
1843 );
1844 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1845 src_area_id, dst_area_id, mapping_data.len());
1846 }
1847 }
1848
1849 if should_apply_bidirectional {
1851 let dst_area = self.cortical_areas.get_mut(dst_area_id).ok_or_else(|| {
1852 crate::types::BduError::InvalidArea(format!(
1853 "Destination area not found: {}",
1854 dst_area_id
1855 ))
1856 })?;
1857 let dst_mapping_dst =
1858 if let Some(existing) = dst_area.properties.get_mut("cortical_mapping_dst") {
1859 existing.as_object_mut().ok_or_else(|| {
1860 crate::types::BduError::InvalidMorphology(
1861 "cortical_mapping_dst is not an object".to_string(),
1862 )
1863 })?
1864 } else {
1865 dst_area
1866 .properties
1867 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1868 dst_area
1869 .properties
1870 .get_mut("cortical_mapping_dst")
1871 .unwrap()
1872 .as_object_mut()
1873 .unwrap()
1874 };
1875
1876 if mapping_data.is_empty() {
1877 dst_mapping_dst.remove(&src_area_id.as_base_64());
1878 info!(
1879 target: "feagi-bdu",
1880 "Removed bi-directional STDP mirror mapping from {} to {}",
1881 dst_area_id,
1882 src_area_id
1883 );
1884 } else {
1885 dst_mapping_dst.insert(
1886 src_area_id.as_base_64(),
1887 serde_json::Value::Array(mapping_data.clone()),
1888 );
1889 info!(
1890 target: "feagi-bdu",
1891 "Updated bi-directional STDP mirror mapping from {} to {} with {} connections",
1892 dst_area_id,
1893 src_area_id,
1894 mapping_data.len()
1895 );
1896 }
1897 }
1898
1899 self.refresh_cortical_mappings_hash();
1900
1901 Ok(())
1902 }
1903
1904 pub fn regenerate_synapses_for_mapping(
1917 &mut self,
1918 src_area_id: &CorticalID,
1919 dst_area_id: &CorticalID,
1920 ) -> BduResult<usize> {
1921 use tracing::info;
1922
1923 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1924
1925 let mapping_rules_len = self
1926 .cortical_areas
1927 .get(src_area_id)
1928 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1929 .and_then(|v| v.as_object())
1930 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1931 .and_then(|v| v.as_array())
1932 .map(|arr| arr.len())
1933 .unwrap_or(0);
1934 tracing::debug!(
1935 target: "feagi-bdu",
1936 "Mapping rules for {} -> {}: {}",
1937 src_area_id,
1938 dst_area_id,
1939 mapping_rules_len
1940 );
1941
1942 let Some(npu_arc) = self.npu.clone() else {
1944 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
1945 return Ok(0);
1946 };
1947
1948 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
1958 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
1959 })?;
1960 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
1961 BduError::InvalidArea(format!(
1962 "No cortical idx for destination area {}",
1963 dst_area_id
1964 ))
1965 })?;
1966
1967 let mut pruned_synapse_count: usize = 0;
1970 use std::time::Instant;
1971 let start = Instant::now();
1972
1973 let (sources, targets) = {
1979 let lock_start = std::time::Instant::now();
1980 let npu = npu_arc.lock().unwrap();
1981 let lock_wait = lock_start.elapsed();
1982 tracing::debug!(
1983 target: "feagi-bdu",
1984 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
1985 lock_wait.as_secs_f64() * 1000.0,
1986 src_area_id,
1987 dst_area_id
1988 );
1989 let sources: Vec<NeuronId> = npu
1990 .get_neurons_in_cortical_area(src_idx)
1991 .into_iter()
1992 .map(NeuronId)
1993 .collect();
1994 let targets: Vec<NeuronId> = npu
1995 .get_neurons_in_cortical_area(dst_idx)
1996 .into_iter()
1997 .map(NeuronId)
1998 .collect();
1999 (sources, targets)
2000 };
2001
2002 tracing::debug!(
2003 target: "feagi-bdu",
2004 "Prune synapses: {} sources, {} targets",
2005 sources.len(),
2006 targets.len()
2007 );
2008
2009 if !sources.is_empty() && !targets.is_empty() {
2010 let remove_start = Instant::now();
2011 pruned_synapse_count = {
2012 let lock_start = std::time::Instant::now();
2013 let mut npu = npu_arc.lock().unwrap();
2014 let lock_wait = lock_start.elapsed();
2015 tracing::debug!(
2016 target: "feagi-bdu",
2017 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
2018 lock_wait.as_secs_f64() * 1000.0,
2019 src_area_id,
2020 dst_area_id
2021 );
2022 npu.remove_synapses_between(sources, targets)
2026 };
2027 let remove_time = remove_start.elapsed();
2028 let total_time = start.elapsed();
2029
2030 info!(
2031 target: "feagi-bdu",
2032 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2033 pruned_synapse_count,
2034 src_area_id,
2035 dst_area_id,
2036 total_time.as_millis(),
2037 remove_time.as_millis()
2038 );
2039
2040 if pruned_synapse_count > 0 {
2042 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2043 BduError::Internal(format!(
2044 "Pruned synapse count overflow (usize -> u32): {}",
2045 pruned_synapse_count
2046 ))
2047 })?;
2048 let state_manager = StateManager::instance();
2049 let state_manager = state_manager.read();
2050 let core_state = state_manager.get_core_state();
2051 core_state.subtract_synapse_count(pruned_u32);
2052 state_manager.subtract_cortical_area_outgoing_synapses(
2053 &src_area_id.as_base_64(),
2054 pruned_synapse_count,
2055 );
2056 state_manager.subtract_cortical_area_incoming_synapses(
2057 &dst_area_id.as_base_64(),
2058 pruned_synapse_count,
2059 );
2060
2061 {
2065 let mut cache = self.cached_synapse_counts_per_area.write();
2066 let entry = cache
2067 .entry(*src_area_id)
2068 .or_insert_with(|| AtomicUsize::new(0));
2069 let mut current = entry.load(Ordering::Relaxed);
2070 loop {
2071 let next = current.saturating_sub(pruned_synapse_count);
2072 match entry.compare_exchange(
2073 current,
2074 next,
2075 Ordering::Relaxed,
2076 Ordering::Relaxed,
2077 ) {
2078 Ok(_) => break,
2079 Err(v) => current = v,
2080 }
2081 }
2082 }
2083 }
2084 }
2085
2086 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2093 tracing::debug!(
2094 target: "feagi-bdu",
2095 "Synaptogenesis created {} synapses for {} -> {}",
2096 synapse_count,
2097 src_area_id,
2098 dst_area_id
2099 );
2100
2101 if synapse_count > 0 {
2104 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2105 BduError::Internal(format!(
2106 "Created synapse count overflow (usize -> u32): {}",
2107 synapse_count
2108 ))
2109 })?;
2110
2111 {
2113 let mut cache = self.cached_synapse_counts_per_area.write();
2114 cache
2115 .entry(*src_area_id)
2116 .or_insert_with(|| AtomicUsize::new(0))
2117 .fetch_add(synapse_count, Ordering::Relaxed);
2118 }
2119
2120 let state_manager = StateManager::instance();
2122 let state_manager = state_manager.read();
2123 let core_state = state_manager.get_core_state();
2124 core_state.add_synapse_count(created_u32);
2125 state_manager
2126 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2127 state_manager
2128 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2129 }
2130
2131 let src_idx_for_upstream = src_idx;
2134
2135 let has_mapping = self
2137 .cortical_areas
2138 .get(src_area_id)
2139 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2140 .and_then(|v| v.as_object())
2141 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2142 .is_some();
2143
2144 info!(target: "feagi-bdu",
2145 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2146 synapse_count,
2147 src_area_id.as_base_64(),
2148 dst_area_id.as_base_64(),
2149 has_mapping,
2150 if has_mapping { "" } else { "NOT " }
2151 );
2152
2153 if has_mapping {
2154 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2156
2157 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2158 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2159 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2160 warn!(
2161 target: "feagi-bdu",
2162 "Failed to ensure memory twin for {} -> {}: {}",
2163 src_area_id.as_base_64(),
2164 dst_area_id.as_base_64(),
2165 e
2166 );
2167 }
2168 }
2169 }
2170
2171 #[cfg(feature = "plasticity")]
2173 if let Some(ref executor) = self.plasticity_executor {
2174 use feagi_evolutionary::extract_memory_properties;
2175
2176 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2177 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2178 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2179 let upstream_non_memory =
2180 self.filter_non_memory_upstream_areas(&upstream_areas);
2181 debug!(
2182 target: "feagi-bdu",
2183 "Registering memory area idx={} id={} upstream={} depth={}",
2184 dst_area.cortical_idx,
2185 dst_area_id.as_base_64(),
2186 upstream_areas.len(),
2187 mem_props.temporal_depth
2188 );
2189
2190 if let Some(ref npu_arc) = self.npu {
2193 if let Ok(mut npu) = npu_arc.lock() {
2194 let existing_configs = npu.get_all_fire_ledger_configs();
2195 for &upstream_idx in &upstream_areas {
2196 let existing = existing_configs
2197 .iter()
2198 .find(|(idx, _)| *idx == upstream_idx)
2199 .map(|(_, w)| *w)
2200 .unwrap_or(0);
2201
2202 let desired = mem_props.temporal_depth as usize;
2203 let resolved = existing.max(desired);
2204 if resolved != existing {
2205 if let Err(e) =
2206 npu.configure_fire_ledger_window(upstream_idx, resolved)
2207 {
2208 warn!(
2209 target: "feagi-bdu",
2210 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2211 upstream_idx,
2212 resolved,
2213 e
2214 );
2215 }
2216 }
2217 }
2218 } else {
2219 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2220 }
2221 }
2222
2223 if let Ok(exec) = executor.lock() {
2224 use feagi_npu_plasticity::{
2225 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2226 };
2227
2228 let lifecycle_config = MemoryNeuronLifecycleConfig {
2229 initial_lifespan: mem_props.init_lifespan,
2230 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2231 longterm_threshold: mem_props.longterm_threshold,
2232 max_reactivations: 1000,
2233 };
2234
2235 exec.register_memory_area(
2236 dst_area.cortical_idx,
2237 dst_area_id.as_base_64(),
2238 mem_props.temporal_depth,
2239 upstream_non_memory,
2240 Some(lifecycle_config),
2241 );
2242 } else {
2243 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2244 }
2245 } else {
2246 debug!(
2247 target: "feagi-bdu",
2248 "Skipping plasticity registration: no memory properties for area {}",
2249 dst_area_id.as_base_64()
2250 );
2251 }
2252 } else {
2253 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2254 }
2255 } else {
2256 warn!(
2257 target: "feagi-bdu",
2258 "PlasticityExecutor not available; memory area {} not registered",
2259 dst_area_id.as_base_64()
2260 );
2261 }
2262
2263 #[cfg(not(feature = "plasticity"))]
2264 {
2265 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2266 }
2267 } else {
2268 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2270
2271 let mut npu = npu_arc.lock().unwrap();
2273 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2274 }
2275
2276 info!(
2277 target: "feagi-bdu",
2278 "Created {} new synapses: {} -> {}",
2279 synapse_count,
2280 src_area_id,
2281 dst_area_id
2282 );
2283
2284 if pruned_synapse_count > 0 || synapse_count == 0 {
2287 let mut npu = npu_arc.lock().unwrap();
2288 npu.rebuild_synapse_index();
2289 info!(
2290 target: "feagi-bdu",
2291 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2292 src_area_id,
2293 dst_area_id,
2294 pruned_synapse_count,
2295 synapse_count
2296 );
2297 } else {
2298 info!(
2299 target: "feagi-bdu",
2300 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2301 src_area_id,
2302 dst_area_id,
2303 synapse_count
2304 );
2305 }
2306
2307 {
2309 let npu = npu_arc.lock().unwrap();
2310 let fresh_count = npu.get_synapse_count();
2311 self.cached_synapse_count
2312 .store(fresh_count, Ordering::Relaxed);
2313 }
2314
2315 Ok(synapse_count)
2316 }
2317
2318 #[allow(clippy::too_many_arguments)]
2320 fn register_stdp_mapping_for_rule(
2321 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2322 src_area_id: &CorticalID,
2323 dst_area_id: &CorticalID,
2324 src_cortical_idx: u32,
2325 dst_cortical_idx: u32,
2326 rule_obj: &serde_json::Map<String, serde_json::Value>,
2327 bidirectional_stdp: bool,
2328 synapse_psp: u8,
2329 synapse_type: feagi_npu_neural::SynapseType,
2330 ) -> BduResult<()> {
2331 let plasticity_window = rule_obj
2332 .get("plasticity_window")
2333 .and_then(|v| v.as_u64())
2334 .ok_or_else(|| {
2335 BduError::Internal(format!(
2336 "Missing plasticity_window in plastic mapping rule {} -> {}",
2337 src_area_id, dst_area_id
2338 ))
2339 })? as usize;
2340 let plasticity_constant = rule_obj
2341 .get("plasticity_constant")
2342 .and_then(|v| v.as_i64())
2343 .ok_or_else(|| {
2344 BduError::Internal(format!(
2345 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2346 src_area_id, dst_area_id
2347 ))
2348 })?;
2349 let ltp_multiplier = rule_obj
2350 .get("ltp_multiplier")
2351 .and_then(|v| v.as_i64())
2352 .ok_or_else(|| {
2353 BduError::Internal(format!(
2354 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2355 src_area_id, dst_area_id
2356 ))
2357 })?;
2358 let ltd_multiplier = rule_obj
2359 .get("ltd_multiplier")
2360 .and_then(|v| v.as_i64())
2361 .ok_or_else(|| {
2362 BduError::Internal(format!(
2363 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2364 src_area_id, dst_area_id
2365 ))
2366 })?;
2367
2368 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2369 plasticity_window,
2370 plasticity_constant,
2371 ltp_multiplier,
2372 ltd_multiplier,
2373 bidirectional_stdp,
2374 synapse_psp,
2375 synapse_type,
2376 };
2377
2378 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2379 let mut npu_lock = npu
2380 .lock()
2381 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2382 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2383
2384 npu_lock
2385 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2386 .map_err(|e| {
2387 BduError::Internal(format!(
2388 "Failed to register STDP mapping {} -> {}: {}",
2389 src_area_id, dst_area_id, e
2390 ))
2391 })?;
2392
2393 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2395 for area_idx in [src_cortical_idx, dst_cortical_idx] {
2396 let existing = existing_configs
2397 .iter()
2398 .find(|(idx, _)| *idx == area_idx)
2399 .map(|(_, w)| *w)
2400 .unwrap_or(0);
2401 let resolved = existing.max(plasticity_window);
2402 if resolved != existing {
2403 npu_lock
2404 .configure_fire_ledger_window(area_idx, resolved)
2405 .map_err(|e| {
2406 BduError::Internal(format!(
2407 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2408 area_idx, resolved, e
2409 ))
2410 })?;
2411 }
2412 }
2413
2414 Ok(())
2415 }
2416
2417 fn resolve_synapse_params_for_rule(
2419 &self,
2420 src_area_id: &CorticalID,
2421 rule: &serde_json::Value,
2422 ) -> BduResult<(u8, u8, feagi_npu_neural::SynapseType)> {
2423 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2425 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2426 })?;
2427
2428 let (weight, synapse_type) = {
2434 let parse_i64 = |v: &serde_json::Value| -> Option<i64> {
2436 if let Some(i) = v.as_i64() {
2437 return Some(i);
2438 }
2439 let f = v.as_f64()?;
2440 if f.fract() == 0.0 {
2441 Some(f as i64)
2442 } else {
2443 None
2444 }
2445 };
2446
2447 let multiplier_i64: i64 = if let Some(obj) = rule.as_object() {
2448 obj.get("postSynapticCurrent_multiplier")
2449 .and_then(parse_i64)
2450 .unwrap_or(1) } else if let Some(arr) = rule.as_array() {
2452 arr.get(2).and_then(parse_i64).unwrap_or(1) } else {
2455 128 };
2457
2458 if multiplier_i64 < 0 {
2459 let abs = if multiplier_i64 == i64::MIN {
2460 i64::MAX
2461 } else {
2462 multiplier_i64.abs()
2463 };
2464 (
2465 abs.clamp(0, 255) as u8,
2466 feagi_npu_neural::SynapseType::Inhibitory,
2467 )
2468 } else {
2469 (
2470 multiplier_i64.clamp(0, 255) as u8,
2471 feagi_npu_neural::SynapseType::Excitatory,
2472 )
2473 }
2474 };
2475
2476 let (psp_f32, psp) = {
2483 use crate::models::cortical_area::CorticalAreaExt;
2484 let psp_f32 = src_area.postsynaptic_current();
2485 (psp_f32, psp_f32.clamp(0.0, 255.0) as u8)
2486 };
2487
2488 tracing::debug!(
2489 target: "feagi-bdu",
2490 "Resolved synapse params src={} weight={} psp={} psp_f32={} type={:?}",
2491 src_area_id.as_base_64(),
2492 weight,
2493 psp,
2494 psp_f32,
2495 synapse_type
2496 );
2497
2498 Ok((weight, psp, synapse_type))
2499 }
2500
2501 fn apply_cortical_mapping_for_pair(
2503 &mut self,
2504 src_area_id: &CorticalID,
2505 dst_area_id: &CorticalID,
2506 ) -> BduResult<usize> {
2507 let rules = {
2513 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2514 crate::types::BduError::InvalidArea(format!(
2515 "Source area not found: {}",
2516 src_area_id
2517 ))
2518 })?;
2519
2520 let Some(mapping_dst) = src_area
2521 .properties
2522 .get("cortical_mapping_dst")
2523 .and_then(|v| v.as_object())
2524 else {
2525 return Ok(0);
2526 };
2527
2528 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
2529 else {
2530 return Ok(0);
2531 };
2532
2533 rules.clone()
2534 }; if rules.is_empty() {
2537 return Ok(0);
2538 }
2539
2540 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2542 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2543 })?;
2544 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2545 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2546 })?;
2547
2548 let npu_arc = self
2550 .npu
2551 .as_ref()
2552 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2553 .clone();
2554
2555 tracing::debug!(
2556 target: "feagi-bdu",
2557 "Applying {} mapping rule(s) for {} -> {}",
2558 rules.len(),
2559 src_area_id,
2560 dst_area_id
2561 );
2562 let mut total_synapses = 0;
2564 for rule in &rules {
2565 let morphology_id = if let Some(rule_obj) = rule.as_object() {
2566 rule_obj
2567 .get("morphology_id")
2568 .and_then(|v| v.as_str())
2569 .unwrap_or("unknown")
2570 .to_string()
2571 } else if let Some(rule_arr) = rule.as_array() {
2572 rule_arr
2573 .first()
2574 .and_then(|v| v.as_str())
2575 .unwrap_or("unknown")
2576 .to_string()
2577 } else {
2578 "unknown".to_string()
2579 };
2580
2581 let rule_keys: Vec<String> = rule
2582 .as_object()
2583 .map(|obj| obj.keys().cloned().collect())
2584 .unwrap_or_default();
2585
2586 let mut plasticity_flag = rule
2588 .as_object()
2589 .and_then(|obj| obj.get("plasticity_flag"))
2590 .and_then(|v| v.as_bool())
2591 .unwrap_or(false);
2592 if morphology_id == "associative_memory" {
2593 plasticity_flag = true;
2594 }
2595 if plasticity_flag {
2596 let Some(rule_obj) = rule.as_object() else {
2597 return Err(crate::types::BduError::InvalidMorphology(
2598 "Plasticity mapping rule must be an object format".to_string(),
2599 ));
2600 };
2601 let (_weight, psp, synapse_type) =
2602 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2603 let bidirectional_stdp = morphology_id == "associative_memory";
2604 if let Err(e) = Self::register_stdp_mapping_for_rule(
2605 &npu_arc,
2606 src_area_id,
2607 dst_area_id,
2608 src_cortical_idx,
2609 dst_cortical_idx,
2610 rule_obj,
2611 bidirectional_stdp,
2612 psp,
2613 synapse_type,
2614 ) {
2615 tracing::error!(
2616 target: "feagi-bdu",
2617 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2618 src_area_id,
2619 dst_area_id,
2620 morphology_id,
2621 rule_keys,
2622 e
2623 );
2624 return Err(e);
2625 }
2626 }
2627
2628 let synapse_count = match self.apply_single_morphology_rule(
2630 src_area_id,
2631 dst_area_id,
2632 rule,
2633 ) {
2634 Ok(count) => count,
2635 Err(e) => {
2636 tracing::error!(
2637 target: "feagi-bdu",
2638 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2639 src_area_id,
2640 dst_area_id,
2641 morphology_id,
2642 rule_keys,
2643 e
2644 );
2645 return Err(e);
2646 }
2647 };
2648 total_synapses += synapse_count;
2649 tracing::debug!(
2650 target: "feagi-bdu",
2651 "Rule {} created {} synapses for {} -> {}",
2652 morphology_id,
2653 synapse_count,
2654 src_area_id,
2655 dst_area_id
2656 );
2657 }
2658
2659 Ok(total_synapses)
2660 }
2661
2662 #[allow(clippy::too_many_arguments)]
2676 fn apply_function_morphology(
2677 &self,
2678 morphology_id: &str,
2679 rule: &serde_json::Value,
2680 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2681 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2682 src_area_id: &CorticalID,
2683 dst_area_id: &CorticalID,
2684 src_idx: u32,
2685 dst_idx: u32,
2686 weight: u8,
2687 psp: u8,
2688 synapse_attractivity: u8,
2689 synapse_type: feagi_npu_neural::SynapseType,
2690 ) -> BduResult<usize> {
2691 match morphology_id {
2692 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
2693 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2695 crate::types::BduError::InvalidArea(format!(
2696 "Source area not found: {}",
2697 src_area_id
2698 ))
2699 })?;
2700 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2701 crate::types::BduError::InvalidArea(format!(
2702 "Destination area not found: {}",
2703 dst_area_id
2704 ))
2705 })?;
2706
2707 let src_dimensions = (
2708 src_area.dimensions.width as usize,
2709 src_area.dimensions.height as usize,
2710 src_area.dimensions.depth as usize,
2711 );
2712 let dst_dimensions = (
2713 dst_area.dimensions.width as usize,
2714 dst_area.dimensions.height as usize,
2715 dst_area.dimensions.depth as usize,
2716 );
2717
2718 let transpose = match morphology_id {
2721 "transpose_xy" => Some((1, 0, 2)),
2722 "transpose_yz" => Some((0, 2, 1)),
2723 "transpose_xz" => Some((2, 1, 0)),
2724 _ => None,
2725 };
2726
2727 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2728 let count = apply_projector_morphology_with_dimensions(
2729 npu,
2730 src_idx,
2731 dst_idx,
2732 src_dimensions,
2733 dst_dimensions,
2734 transpose,
2735 None, weight,
2737 psp,
2738 synapse_attractivity,
2739 synapse_type,
2740 )?;
2741 npu.rebuild_synapse_index();
2743 Ok(count as usize)
2744 }
2745 "episodic_memory" => {
2746 use tracing::trace;
2749 trace!(
2750 target: "feagi-bdu",
2751 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
2752 src_idx, dst_idx
2753 );
2754 Ok(0)
2755 }
2756 "memory_replay" => {
2757 use tracing::trace;
2759 trace!(
2760 target: "feagi-bdu",
2761 "Memory replay morphology: {} -> {} (no physical synapses)",
2762 src_idx, dst_idx
2763 );
2764 Ok(0)
2765 }
2766 "associative_memory" => {
2767 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2771 crate::types::BduError::InvalidArea(format!(
2772 "Source area not found: {}",
2773 src_area_id
2774 ))
2775 })?;
2776 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2777 crate::types::BduError::InvalidArea(format!(
2778 "Destination area not found: {}",
2779 dst_area_id
2780 ))
2781 })?;
2782
2783 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2784 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2785 {
2786 let src_dimensions = (
2787 src_area.dimensions.width as usize,
2788 src_area.dimensions.height as usize,
2789 src_area.dimensions.depth as usize,
2790 );
2791 let dst_dimensions = (
2792 dst_area.dimensions.width as usize,
2793 dst_area.dimensions.height as usize,
2794 dst_area.dimensions.depth as usize,
2795 );
2796 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2797 let count = apply_projector_morphology_with_dimensions(
2798 npu,
2799 src_idx,
2800 dst_idx,
2801 src_dimensions,
2802 dst_dimensions,
2803 None,
2804 None,
2805 weight,
2806 psp,
2807 synapse_attractivity,
2808 synapse_type,
2809 )?;
2810 npu.rebuild_synapse_index();
2811 Ok(count as usize)
2812 } else {
2813 Ok(0)
2814 }
2815 }
2816 "block_to_block" => {
2817 tracing::warn!(
2818 target: "feagi-bdu",
2819 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
2820 src_idx, dst_idx
2821 );
2822 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2824 crate::types::BduError::InvalidArea(format!(
2825 "Source area not found: {}",
2826 src_area_id
2827 ))
2828 })?;
2829 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2830 crate::types::BduError::InvalidArea(format!(
2831 "Destination area not found: {}",
2832 dst_area_id
2833 ))
2834 })?;
2835
2836 let src_dimensions = (
2837 src_area.dimensions.width as usize,
2838 src_area.dimensions.height as usize,
2839 src_area.dimensions.depth as usize,
2840 );
2841 let dst_dimensions = (
2842 dst_area.dimensions.width as usize,
2843 dst_area.dimensions.height as usize,
2844 dst_area.dimensions.depth as usize,
2845 );
2846
2847 let scalar = if let Some(obj) = rule.as_object() {
2849 if let Some(scalar_arr) =
2851 obj.get("morphology_scalar").and_then(|v| v.as_array())
2852 {
2853 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
2855 } else {
2856 1 }
2858 } else if let Some(arr) = rule.as_array() {
2859 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
2861 } else {
2862 1 };
2864
2865 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
2868 let count = if estimated_neurons > 100_000 {
2869 let _ = npu;
2871
2872 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
2873 npu_arc,
2874 src_idx,
2875 dst_idx,
2876 src_dimensions,
2877 dst_dimensions,
2878 scalar, weight,
2880 psp,
2881 synapse_attractivity,
2882 synapse_type,
2883 )? as usize
2884 } else {
2885 tracing::warn!(
2887 target: "feagi-bdu",
2888 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
2889 src_idx, dst_idx, src_dimensions, dst_dimensions
2890 );
2891 let count =
2892 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
2893 npu,
2894 src_idx,
2895 dst_idx,
2896 src_dimensions,
2897 dst_dimensions,
2898 scalar, weight,
2900 psp,
2901 synapse_attractivity,
2902 synapse_type,
2903 )? as usize;
2904 tracing::warn!(
2905 target: "feagi-bdu",
2906 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
2907 count
2908 );
2909 if count > 0 {
2911 npu.rebuild_synapse_index();
2912 }
2913 count
2914 };
2915
2916 if count > 0 && estimated_neurons > 100_000 {
2918 let mut npu_lock = npu_arc.lock().unwrap();
2919 npu_lock.rebuild_synapse_index();
2920 }
2921
2922 Ok(count)
2923 }
2924 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
2925 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
2926 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2927 crate::types::BduError::InvalidArea(format!(
2928 "Source area not found: {}",
2929 src_area_id
2930 ))
2931 })?;
2932 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2933 crate::types::BduError::InvalidArea(format!(
2934 "Destination area not found: {}",
2935 dst_area_id
2936 ))
2937 })?;
2938
2939 let src_dimensions = (
2940 src_area.dimensions.width as usize,
2941 src_area.dimensions.height as usize,
2942 src_area.dimensions.depth as usize,
2943 );
2944 let dst_dimensions = (
2945 dst_area.dimensions.width as usize,
2946 dst_area.dimensions.height as usize,
2947 dst_area.dimensions.depth as usize,
2948 );
2949
2950 let (axis, mode) = match morphology_id {
2951 "bitmask_encoder_x" => (
2952 crate::connectivity::core_morphologies::BitmaskAxis::X,
2953 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2954 ),
2955 "bitmask_encoder_y" => (
2956 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2957 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2958 ),
2959 "bitmask_encoder_z" => (
2960 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2961 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2962 ),
2963 "bitmask_decoder_x" => (
2964 crate::connectivity::core_morphologies::BitmaskAxis::X,
2965 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2966 ),
2967 "bitmask_decoder_y" => (
2968 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2969 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2970 ),
2971 "bitmask_decoder_z" => (
2972 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2973 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2974 ),
2975 _ => unreachable!("matched bitmask morphology above"),
2976 };
2977
2978 let count =
2979 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
2980 npu,
2981 src_idx,
2982 dst_idx,
2983 src_dimensions,
2984 dst_dimensions,
2985 axis,
2986 mode,
2987 weight,
2988 psp,
2989 synapse_attractivity,
2990 synapse_type,
2991 )?;
2992 if count > 0 {
2993 npu.rebuild_synapse_index();
2994 }
2995 Ok(count as usize)
2996 }
2997 "sweeper" => {
2998 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2999 crate::types::BduError::InvalidArea(format!(
3000 "Destination area not found: {}",
3001 dst_area_id
3002 ))
3003 })?;
3004 let dst_dimensions = (
3005 dst_area.dimensions.width as usize,
3006 dst_area.dimensions.height as usize,
3007 dst_area.dimensions.depth as usize,
3008 );
3009
3010 let count =
3011 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3012 npu,
3013 src_idx,
3014 dst_idx,
3015 dst_dimensions,
3016 weight,
3017 psp,
3018 synapse_attractivity,
3019 synapse_type,
3020 )?;
3021 if count > 0 {
3022 npu.rebuild_synapse_index();
3023 }
3024 Ok(count as usize)
3025 }
3026 "last_to_first" => {
3027 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3028 crate::types::BduError::InvalidArea(format!(
3029 "Source area not found: {}",
3030 src_area_id
3031 ))
3032 })?;
3033 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3034 crate::types::BduError::InvalidArea(format!(
3035 "Destination area not found: {}",
3036 dst_area_id
3037 ))
3038 })?;
3039 let src_dimensions = (
3040 src_area.dimensions.width as usize,
3041 src_area.dimensions.height as usize,
3042 src_area.dimensions.depth as usize,
3043 );
3044 let dst_dimensions = (
3045 dst_area.dimensions.width as usize,
3046 dst_area.dimensions.height as usize,
3047 dst_area.dimensions.depth as usize,
3048 );
3049
3050 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3051 npu,
3052 src_idx,
3053 dst_idx,
3054 src_dimensions,
3055 dst_dimensions,
3056 weight,
3057 psp,
3058 synapse_attractivity,
3059 synapse_type,
3060 )?;
3061 if count > 0 {
3062 npu.rebuild_synapse_index();
3063 }
3064 Ok(count as usize)
3065 }
3066 "rotator_z" => {
3067 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3068 crate::types::BduError::InvalidArea(format!(
3069 "Source area not found: {}",
3070 src_area_id
3071 ))
3072 })?;
3073 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3074 crate::types::BduError::InvalidArea(format!(
3075 "Destination area not found: {}",
3076 dst_area_id
3077 ))
3078 })?;
3079 let src_dimensions = (
3080 src_area.dimensions.width as usize,
3081 src_area.dimensions.height as usize,
3082 src_area.dimensions.depth as usize,
3083 );
3084 let dst_dimensions = (
3085 dst_area.dimensions.width as usize,
3086 dst_area.dimensions.height as usize,
3087 dst_area.dimensions.depth as usize,
3088 );
3089
3090 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3091 npu,
3092 src_idx,
3093 dst_idx,
3094 src_dimensions,
3095 dst_dimensions,
3096 weight,
3097 psp,
3098 synapse_attractivity,
3099 synapse_type,
3100 )?;
3101 if count > 0 {
3102 npu.rebuild_synapse_index();
3103 }
3104 Ok(count as usize)
3105 }
3106 _ => {
3107 use tracing::debug;
3110 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3111 Ok(0)
3112 }
3113 }
3114 }
3115
3116 fn apply_single_morphology_rule(
3118 &mut self,
3119 src_area_id: &CorticalID,
3120 dst_area_id: &CorticalID,
3121 rule: &serde_json::Value,
3122 ) -> BduResult<usize> {
3123 let morphology_id = if let Some(arr) = rule.as_array() {
3125 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3126 } else if let Some(obj) = rule.as_object() {
3127 obj.get("morphology_id")
3128 .and_then(|v| v.as_str())
3129 .unwrap_or("")
3130 } else {
3131 return Ok(0);
3132 };
3133
3134 if morphology_id.is_empty() {
3135 return Ok(0);
3136 }
3137
3138 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3140 crate::types::BduError::InvalidMorphology(format!(
3141 "Morphology not found: {}",
3142 morphology_id
3143 ))
3144 })?;
3145
3146 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3148 crate::types::BduError::InvalidArea(format!(
3149 "Source area ID not found: {}",
3150 src_area_id
3151 ))
3152 })?;
3153 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3154 crate::types::BduError::InvalidArea(format!(
3155 "Destination area ID not found: {}",
3156 dst_area_id
3157 ))
3158 })?;
3159
3160 if let Some(ref npu_arc) = self.npu {
3162 let lock_start = std::time::Instant::now();
3163 let mut npu = npu_arc.lock().unwrap();
3164 let lock_wait = lock_start.elapsed();
3165 tracing::debug!(
3166 target: "feagi-bdu",
3167 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3168 lock_wait.as_secs_f64() * 1000.0,
3169 src_area_id,
3170 dst_area_id,
3171 morphology_id
3172 );
3173
3174 let (weight, psp, synapse_type) =
3175 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3176
3177 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3179 obj.get("synapse_attractivity")
3180 .and_then(|v| v.as_u64())
3181 .unwrap_or(100) as u8
3182 } else {
3183 100 };
3185
3186 match morphology.morphology_type {
3187 feagi_evolutionary::MorphologyType::Functions => {
3188 tracing::warn!(
3189 target: "feagi-bdu",
3190 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3191 morphology_id
3192 );
3193 self.apply_function_morphology(
3196 morphology_id,
3197 rule,
3198 npu_arc,
3199 &mut npu,
3200 src_area_id,
3201 dst_area_id,
3202 *src_idx,
3203 *dst_idx,
3204 weight,
3205 psp,
3206 synapse_attractivity,
3207 synapse_type,
3208 )
3209 }
3210 feagi_evolutionary::MorphologyType::Vectors => {
3211 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3212
3213 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3215 crate::types::BduError::InvalidArea(format!(
3216 "Destination area not found: {}",
3217 dst_area_id
3218 ))
3219 })?;
3220
3221 let dst_dimensions = (
3222 dst_area.dimensions.width as usize,
3223 dst_area.dimensions.height as usize,
3224 dst_area.dimensions.depth as usize,
3225 );
3226
3227 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3228 morphology.parameters
3229 {
3230 let vectors_tuples: Vec<(i32, i32, i32)> =
3232 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3233
3234 let count = apply_vectors_morphology_with_dimensions(
3235 &mut npu,
3236 *src_idx,
3237 *dst_idx,
3238 vectors_tuples,
3239 dst_dimensions,
3240 weight, psp, synapse_attractivity, synapse_type,
3244 )?;
3245 npu.rebuild_synapse_index();
3248 Ok(count as usize)
3249 } else {
3250 Ok(0)
3251 }
3252 }
3253 feagi_evolutionary::MorphologyType::Patterns => {
3254 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3255 use crate::connectivity::rules::patterns::{
3256 Pattern3D, PatternElement as RulePatternElement,
3257 };
3258 use feagi_evolutionary::PatternElement as EvoPatternElement;
3259
3260 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3261 morphology.parameters
3262 else {
3263 return Ok(0);
3264 };
3265
3266 let convert_element =
3267 |element: &EvoPatternElement|
3268 -> crate::types::BduResult<RulePatternElement> {
3269 match element {
3270 EvoPatternElement::Value(value) => {
3271 if *value < 0 {
3272 return Err(crate::types::BduError::InvalidMorphology(
3273 format!(
3274 "Pattern morphology {} contains negative voxel coordinate {}",
3275 morphology_id, value
3276 ),
3277 ));
3278 }
3279 Ok(RulePatternElement::Exact(*value))
3280 }
3281 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3282 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3283 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3284 }
3285 };
3286
3287 let mut converted_patterns = Vec::with_capacity(patterns.len());
3288 for pattern_pair in patterns {
3289 if pattern_pair.len() != 2 {
3290 return Err(crate::types::BduError::InvalidMorphology(format!(
3291 "Pattern morphology {} must contain [src, dst] pairs",
3292 morphology_id
3293 )));
3294 }
3295
3296 let src_pattern = &pattern_pair[0];
3297 let dst_pattern = &pattern_pair[1];
3298
3299 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3300 return Err(crate::types::BduError::InvalidMorphology(format!(
3301 "Pattern morphology {} requires 3-axis patterns",
3302 morphology_id
3303 )));
3304 }
3305
3306 let src: Pattern3D = (
3307 convert_element(&src_pattern[0])?,
3308 convert_element(&src_pattern[1])?,
3309 convert_element(&src_pattern[2])?,
3310 );
3311 let dst: Pattern3D = (
3312 convert_element(&dst_pattern[0])?,
3313 convert_element(&dst_pattern[1])?,
3314 convert_element(&dst_pattern[2])?,
3315 );
3316
3317 converted_patterns.push((src, dst));
3318 }
3319
3320 let count = apply_patterns_morphology(
3321 &mut npu,
3322 *src_idx,
3323 *dst_idx,
3324 converted_patterns,
3325 weight,
3326 psp,
3327 synapse_attractivity,
3328 synapse_type,
3329 )?;
3330 if count > 0 {
3331 npu.rebuild_synapse_index();
3332 }
3333 Ok(count as usize)
3334 }
3335 feagi_evolutionary::MorphologyType::Composite => {
3336 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3337 morphology.parameters
3338 else {
3339 return Ok(0);
3340 };
3341
3342 if morphology_id != "tile" {
3343 use tracing::debug;
3344 debug!(
3345 target: "feagi-bdu",
3346 "Composite morphology {} not yet implemented",
3347 morphology_id
3348 );
3349 return Ok(0);
3350 }
3351
3352 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3353 crate::types::BduError::InvalidArea(format!(
3354 "Source area not found: {}",
3355 src_area_id
3356 ))
3357 })?;
3358 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3359 crate::types::BduError::InvalidArea(format!(
3360 "Destination area not found: {}",
3361 dst_area_id
3362 ))
3363 })?;
3364 let src_dimensions = (
3365 src_area.dimensions.width as usize,
3366 src_area.dimensions.height as usize,
3367 src_area.dimensions.depth as usize,
3368 );
3369 let dst_dimensions = (
3370 dst_area.dimensions.width as usize,
3371 dst_area.dimensions.height as usize,
3372 dst_area.dimensions.depth as usize,
3373 );
3374
3375 let count =
3376 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3377 &mut npu,
3378 *src_idx,
3379 *dst_idx,
3380 src_dimensions,
3381 dst_dimensions,
3382 weight,
3383 psp,
3384 synapse_attractivity,
3385 synapse_type,
3386 )?;
3387 if count > 0 {
3388 npu.rebuild_synapse_index();
3389 }
3390 Ok(count as usize)
3391 }
3392 }
3393 } else {
3394 Ok(0) }
3396 }
3397
3398 pub fn set_npu(
3411 &mut self,
3412 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3413 ) {
3414 self.npu = Some(Arc::clone(&npu));
3415 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3416
3417 #[cfg(not(feature = "wasm"))]
3420 {
3421 use feagi_state_manager::StateManager;
3422 let state_manager = StateManager::instance();
3423 let state_manager = state_manager.read();
3424 let core_state = state_manager.get_core_state();
3425 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3427 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3428 info!(
3429 target: "feagi-bdu",
3430 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3431 self.config.max_neurons, self.config.max_synapses
3432 );
3433 }
3434
3435 let existing_area_count = self.cortical_id_to_idx.len();
3442 if existing_area_count > 0 {
3443 match npu.lock() {
3444 Ok(mut npu_lock) => {
3445 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3446 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3447 }
3448 info!(
3449 target: "feagi-bdu",
3450 "🔁 Backfilled {} cortical area registrations into NPU",
3451 existing_area_count
3452 );
3453 }
3454 Err(e) => {
3455 warn!(
3456 target: "feagi-bdu",
3457 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3458 e
3459 );
3460 }
3461 }
3462 }
3463
3464 self.update_all_cached_stats();
3466 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3467 self.get_neuron_count(), self.get_synapse_count());
3468 }
3469
3470 pub fn has_npu(&self) -> bool {
3472 self.npu.is_some()
3473 }
3474
3475 pub fn get_npu(
3482 &self,
3483 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3484 {
3485 self.npu.as_ref()
3486 }
3487
3488 #[cfg(feature = "plasticity")]
3491 pub fn set_plasticity_executor(
3492 &mut self,
3493 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3494 ) {
3495 self.plasticity_executor = Some(executor);
3496 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3497 }
3498
3499 #[cfg(feature = "plasticity")]
3501 pub fn get_plasticity_executor(
3502 &self,
3503 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3504 self.plasticity_executor.as_ref()
3505 }
3506
3507 pub fn get_neuron_capacity(&self) -> usize {
3519 self.config.max_neurons
3521 }
3522
3523 pub fn get_synapse_capacity(&self) -> usize {
3535 self.config.max_synapses
3537 }
3538
3539 pub fn update_fatigue_index(&self) -> Option<u8> {
3554 let mut last_calc = match self.last_fatigue_calculation.lock() {
3556 Ok(guard) => guard,
3557 Err(_) => return None, };
3559
3560 let now = std::time::Instant::now();
3561 if now.duration_since(*last_calc).as_secs() < 2 {
3562 return None; }
3564 *last_calc = now;
3565 drop(last_calc);
3566
3567 let regular_neuron_count = self.get_neuron_count();
3569 let regular_neuron_capacity = self.get_neuron_capacity();
3570 let regular_neuron_util = if regular_neuron_capacity > 0 {
3571 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3572 } else {
3573 0
3574 };
3575
3576 let memory_neuron_util = match StateManager::instance().try_read() {
3580 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3581 None => {
3582 return None;
3584 }
3585 };
3586
3587 let synapse_count = self.get_synapse_count();
3589 let synapse_capacity = self.get_synapse_capacity();
3590 let synapse_util = if synapse_capacity > 0 {
3591 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3592 } else {
3593 0
3594 };
3595
3596 let fatigue_index = regular_neuron_util
3598 .max(memory_neuron_util)
3599 .max(synapse_util);
3600
3601 let current_fatigue_active = {
3603 StateManager::instance()
3605 .try_read()
3606 .map(|m| m.get_core_state().is_fatigue_active())
3607 .unwrap_or(false)
3608 };
3609
3610 let new_fatigue_active = if fatigue_index >= 85 {
3611 true
3612 } else if fatigue_index < 80 {
3613 false
3614 } else {
3615 current_fatigue_active };
3617
3618 if let Some(state_manager) = StateManager::instance().try_write() {
3622 let core_state = state_manager.get_core_state();
3623 core_state.set_fatigue_index(fatigue_index);
3624 core_state.set_fatigue_active(new_fatigue_active);
3625 core_state.set_regular_neuron_util(regular_neuron_util);
3626 core_state.set_memory_neuron_util(memory_neuron_util);
3627 core_state.set_synapse_util(synapse_util);
3628 } else {
3629 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3631 }
3632
3633 if let Some(ref npu) = self.npu {
3635 if let Ok(mut npu_lock) = npu.lock() {
3636 npu_lock.set_fatigue_active(new_fatigue_active);
3637 }
3638 }
3639
3640 trace!(
3641 target: "feagi-bdu",
3642 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3643 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3644 );
3645
3646 Some(fatigue_index)
3647 }
3648
3649 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3666 let area = self
3668 .cortical_areas
3669 .get(cortical_id)
3670 .ok_or_else(|| {
3671 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3672 })?
3673 .clone();
3674
3675 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3677 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3678 })?;
3679
3680 let npu = self
3682 .npu
3683 .as_ref()
3684 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3685
3686 use crate::models::CorticalAreaExt;
3689 let per_voxel_cnt = area.neurons_per_voxel();
3690 let firing_threshold = area.firing_threshold();
3691 let firing_threshold_increment_x = area.firing_threshold_increment_x();
3692 let firing_threshold_increment_y = area.firing_threshold_increment_y();
3693 let firing_threshold_increment_z = area.firing_threshold_increment_z();
3694 let firing_threshold_limit_raw = area.firing_threshold_limit();
3696 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
3697 f32::MAX } else {
3699 firing_threshold_limit_raw
3700 };
3701
3702 if firing_threshold_increment_x != 0.0
3704 || firing_threshold_increment_y != 0.0
3705 || firing_threshold_increment_z != 0.0
3706 {
3707 info!(
3708 target: "feagi-bdu",
3709 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
3710 cortical_id.as_base_64(),
3711 firing_threshold_increment_x,
3712 firing_threshold_increment_y,
3713 firing_threshold_increment_z
3714 );
3715 } else {
3716 if area.properties.contains_key("firing_threshold_increment_x")
3718 || area.properties.contains_key("firing_threshold_increment_y")
3719 || area.properties.contains_key("firing_threshold_increment_z")
3720 {
3721 info!(
3722 target: "feagi-bdu",
3723 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
3724 cortical_id.as_base_64(),
3725 area.properties.get("firing_threshold_increment_x"),
3726 area.properties.get("firing_threshold_increment_y"),
3727 area.properties.get("firing_threshold_increment_z")
3728 );
3729 }
3730 }
3731
3732 let leak_coefficient = area.leak_coefficient();
3733 let excitability = area.neuron_excitability();
3734 let refractory_period = area.refractory_period();
3735 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
3737 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
3738 u16::MAX } else {
3740 consecutive_fire_limit_raw
3741 };
3742 let snooze_length = area.snooze_period();
3743 let mp_charge_accumulation = area.mp_charge_accumulation();
3744
3745 let voxels = area.dimensions.width as usize
3747 * area.dimensions.height as usize
3748 * area.dimensions.depth as usize;
3749 let expected_neurons = voxels * per_voxel_cnt as usize;
3750
3751 trace!(
3752 target: "feagi-bdu",
3753 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
3754 cortical_id.as_base_64(),
3755 area.dimensions.width,
3756 area.dimensions.height,
3757 area.dimensions.depth,
3758 per_voxel_cnt,
3759 expected_neurons
3760 );
3761
3762 let mut npu_lock = npu
3765 .lock()
3766 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3767
3768 let neuron_count = npu_lock
3769 .create_cortical_area_neurons(
3770 *cortical_idx,
3771 area.dimensions.width,
3772 area.dimensions.height,
3773 area.dimensions.depth,
3774 per_voxel_cnt,
3775 firing_threshold,
3776 firing_threshold_increment_x,
3777 firing_threshold_increment_y,
3778 firing_threshold_increment_z,
3779 firing_threshold_limit,
3780 leak_coefficient,
3781 0.0, 0, refractory_period,
3784 excitability,
3785 consecutive_fire_limit,
3786 snooze_length,
3787 mp_charge_accumulation,
3788 )
3789 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?;
3790
3791 trace!(
3792 target: "feagi-bdu",
3793 "Created {} neurons for area {} via NPU",
3794 neuron_count,
3795 cortical_id.as_base_64()
3796 );
3797
3798 {
3801 let mut cache = self.cached_neuron_counts_per_area.write();
3802 cache
3803 .entry(*cortical_id)
3804 .or_insert_with(|| AtomicUsize::new(0))
3805 .store(neuron_count as usize, Ordering::Relaxed);
3806 }
3807
3808 let state_manager = StateManager::instance();
3810 let state_manager = state_manager.read();
3811 state_manager
3812 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
3813
3814 self.cached_neuron_count
3816 .fetch_add(neuron_count as usize, Ordering::Relaxed);
3817
3818 let state_manager = StateManager::instance();
3820 let state_manager = state_manager.read();
3821 let core_state = state_manager.get_core_state();
3822 core_state.add_neuron_count(neuron_count);
3823 core_state.add_regular_neuron_count(neuron_count);
3824
3825 Ok(neuron_count)
3833 }
3834
3835 #[allow(clippy::too_many_arguments)]
3859 pub fn add_neuron(
3860 &mut self,
3861 cortical_id: &CorticalID,
3862 x: u32,
3863 y: u32,
3864 z: u32,
3865 firing_threshold: f32,
3866 firing_threshold_limit: f32,
3867 leak_coefficient: f32,
3868 resting_potential: f32,
3869 neuron_type: u8,
3870 refractory_period: u16,
3871 excitability: f32,
3872 consecutive_fire_limit: u16,
3873 snooze_length: u16,
3874 mp_charge_accumulation: bool,
3875 ) -> BduResult<u64> {
3876 if !self.cortical_areas.contains_key(cortical_id) {
3878 return Err(BduError::InvalidArea(format!(
3879 "Cortical area {} not found",
3880 cortical_id
3881 )));
3882 }
3883
3884 let cortical_idx = *self
3885 .cortical_id_to_idx
3886 .get(cortical_id)
3887 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
3888
3889 let npu = self
3891 .npu
3892 .as_ref()
3893 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3894
3895 let mut npu_lock = npu
3896 .lock()
3897 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3898
3899 let neuron_id = npu_lock
3901 .add_neuron(
3902 firing_threshold,
3903 firing_threshold_limit,
3904 leak_coefficient,
3905 resting_potential,
3906 neuron_type as i32,
3907 refractory_period,
3908 excitability,
3909 consecutive_fire_limit,
3910 snooze_length,
3911 mp_charge_accumulation,
3912 cortical_idx,
3913 x,
3914 y,
3915 z,
3916 )
3917 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
3918
3919 trace!(
3920 target: "feagi-bdu",
3921 "Created neuron {} in area {} at ({}, {}, {})",
3922 neuron_id.0,
3923 cortical_id,
3924 x,
3925 y,
3926 z
3927 );
3928
3929 let state_manager = StateManager::instance();
3931 let state_manager = state_manager.read();
3932 let core_state = state_manager.get_core_state();
3933 core_state.add_neuron_count(1);
3934 core_state.add_regular_neuron_count(1);
3935 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3936
3937 Ok(neuron_id.0 as u64)
3938 }
3939
3940 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
3951 let npu = self
3953 .npu
3954 .as_ref()
3955 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3956
3957 let mut npu_lock = npu
3958 .lock()
3959 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3960
3961 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
3962 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
3963
3964 let deleted = npu_lock.delete_neuron(neuron_id as u32);
3965
3966 if deleted {
3967 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
3968
3969 let state_manager = StateManager::instance();
3971 let state_manager = state_manager.read();
3972 let core_state = state_manager.get_core_state();
3973 core_state.subtract_neuron_count(1);
3974 core_state.subtract_regular_neuron_count(1);
3975 if let Some(cortical_id) = cortical_id {
3976 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3977 }
3978
3979 }
3983
3984 Ok(deleted)
3985 }
3986
3987 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
4001 let src_area = self
4003 .cortical_areas
4004 .get(src_cortical_id)
4005 .ok_or_else(|| {
4006 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
4007 })?
4008 .clone();
4009
4010 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
4012 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
4013 _ => return Ok(0), };
4015
4016 let src_cortical_idx = *self
4017 .cortical_id_to_idx
4018 .get(src_cortical_id)
4019 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
4020
4021 let mut total_synapses = 0u32;
4022 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
4026 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
4028 Ok(id) => id,
4029 Err(_) => {
4030 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
4031 continue;
4032 }
4033 };
4034
4035 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
4037 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
4038 continue;
4039 }
4040
4041 let synapse_count =
4043 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
4044 total_synapses += synapse_count as u32;
4045
4046 upstream_updates.push((dst_cortical_id, src_cortical_idx));
4049 }
4050
4051 for (dst_id, src_idx) in upstream_updates {
4053 self.add_upstream_area(&dst_id, src_idx);
4054 }
4055
4056 trace!(
4057 target: "feagi-bdu",
4058 "Created {} synapses for area {} via NPU",
4059 total_synapses,
4060 src_cortical_id
4061 );
4062
4063 if total_synapses > 0 {
4066 let mut cache = self.cached_synapse_counts_per_area.write();
4067 cache
4068 .entry(*src_cortical_id)
4069 .or_insert_with(|| AtomicUsize::new(0))
4070 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4071 }
4072
4073 self.cached_synapse_count
4075 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4076
4077 if total_synapses > 0 {
4079 let state_manager = StateManager::instance();
4080 let state_manager = state_manager.read();
4081 let core_state = state_manager.get_core_state();
4082 core_state.add_synapse_count(total_synapses);
4083 }
4084
4085 Ok(total_synapses)
4086 }
4087
4088 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4107 if let Some(ref npu) = self.npu {
4108 if let Ok(npu_lock) = npu.lock() {
4109 npu_lock.is_neuron_valid(neuron_id as u32)
4111 } else {
4112 false
4113 }
4114 } else {
4115 false
4116 }
4117 }
4118
4119 pub fn get_neuron_count(&self) -> usize {
4131 if let Some(ref npu) = self.npu {
4133 if let Ok(npu_lock) = npu.try_lock() {
4134 let fresh_count = npu_lock.get_neuron_count();
4135 self.cached_neuron_count
4136 .store(fresh_count, Ordering::Relaxed);
4137 }
4138 }
4140
4141 self.cached_neuron_count.load(Ordering::Relaxed)
4143 }
4144
4145 pub fn update_cached_neuron_count(&self) {
4151 if let Some(ref npu) = self.npu {
4152 if let Ok(npu_lock) = npu.try_lock() {
4153 let count = npu_lock.get_neuron_count();
4154 self.cached_neuron_count.store(count, Ordering::Relaxed);
4155 }
4156 }
4157 }
4158
4159 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4163 let npu = self.npu.as_ref()?;
4164 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4165 let npu_lock = npu.lock().ok()?;
4166 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4167 drop(npu_lock);
4168
4169 let mut cache = self.cached_neuron_counts_per_area.write();
4170 cache
4171 .entry(*cortical_id)
4172 .or_insert_with(|| AtomicUsize::new(0))
4173 .store(count, Ordering::Relaxed);
4174
4175 let state_manager = StateManager::instance();
4177 let state_manager = state_manager.read();
4178 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4179
4180 self.update_cached_neuron_count();
4181
4182 Some(count)
4183 }
4184
4185 pub fn get_synapse_count(&self) -> usize {
4197 if let Some(ref npu) = self.npu {
4199 if let Ok(npu_lock) = npu.try_lock() {
4200 let fresh_count = npu_lock.get_synapse_count();
4201 self.cached_synapse_count
4202 .store(fresh_count, Ordering::Relaxed);
4203 }
4204 }
4206
4207 self.cached_synapse_count.load(Ordering::Relaxed)
4209 }
4210
4211 pub fn update_cached_synapse_count(&self) {
4217 if let Some(ref npu) = self.npu {
4218 if let Ok(npu_lock) = npu.try_lock() {
4219 let count = npu_lock.get_synapse_count();
4220 self.cached_synapse_count.store(count, Ordering::Relaxed);
4221 }
4222 }
4223 }
4224
4225 pub fn update_all_cached_stats(&self) {
4231 self.update_cached_neuron_count();
4232 self.update_cached_synapse_count();
4233 }
4234
4235 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4246 if let Some(ref npu) = self.npu {
4247 if let Ok(npu_lock) = npu.lock() {
4248 npu_lock
4249 .get_neuron_coordinates(neuron_id as u32)
4250 .unwrap_or((0, 0, 0))
4251 } else {
4252 (0, 0, 0)
4253 }
4254 } else {
4255 (0, 0, 0)
4256 }
4257 }
4258
4259 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
4270 if let Some(ref npu) = self.npu {
4271 if let Ok(npu_lock) = npu.lock() {
4272 npu_lock.get_neuron_cortical_area(neuron_id as u32)
4273 } else {
4274 0
4275 }
4276 } else {
4277 0
4278 }
4279 }
4280
4281 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4292 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4294 Some(idx) => *idx,
4295 None => return Vec::new(),
4296 };
4297
4298 if let Some(ref npu) = self.npu {
4299 if let Ok(npu_lock) = npu.lock() {
4300 npu_lock
4302 .get_neurons_in_cortical_area(cortical_idx)
4303 .into_iter()
4304 .map(|id| id as u64)
4305 .collect()
4306 } else {
4307 Vec::new()
4308 }
4309 } else {
4310 Vec::new()
4311 }
4312 }
4313
4314 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4325 if let Some(ref npu) = self.npu {
4326 if let Ok(npu_lock) = npu.lock() {
4327 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4328 } else {
4329 Vec::new()
4330 }
4331 } else {
4332 Vec::new()
4333 }
4334 }
4335
4336 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4347 if let Some(ref npu) = self.npu {
4348 if let Ok(npu_lock) = npu.lock() {
4349 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4350 } else {
4351 Vec::new()
4352 }
4353 } else {
4354 Vec::new()
4355 }
4356 }
4357
4358 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4384 let cache = self.cached_neuron_counts_per_area.read();
4386 let base_count = cache
4387 .get(cortical_id)
4388 .map(|count| count.load(Ordering::Relaxed))
4389 .unwrap_or(0);
4390
4391 let memory_count = self
4393 .cortical_areas
4394 .get(cortical_id)
4395 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4396 .and_then(|_| {
4397 StateManager::instance()
4398 .try_read()
4399 .and_then(|state_manager| {
4400 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4401 })
4402 })
4403 .map(|stats| stats.neuron_count)
4404 .unwrap_or(0);
4405
4406 base_count.saturating_add(memory_count)
4407 }
4408
4409 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4416 let mut result = Vec::new();
4417
4418 for cortical_id in self.cortical_areas.keys() {
4419 let count = self.get_neuron_count_in_area(cortical_id);
4420 if count > 0 {
4421 result.push((cortical_id.to_string(), count));
4422 }
4423 }
4424
4425 result
4426 }
4427
4428 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4439 self.get_neuron_count_in_area(cortical_id) > 0
4440 }
4441
4442 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4458 let cache = self.cached_synapse_counts_per_area.read();
4460 cache
4461 .get(cortical_id)
4462 .map(|count| count.load(Ordering::Relaxed))
4463 .unwrap_or(0)
4464 }
4465
4466 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4476 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4477 return 0;
4478 }
4479
4480 if let Some(state_manager) = StateManager::instance().try_read() {
4481 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4482 return stats.incoming_synapse_count;
4483 }
4484 }
4485
4486 0
4487 }
4488
4489 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4499 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4500 return 0;
4501 }
4502
4503 if let Some(state_manager) = StateManager::instance().try_read() {
4504 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4505 return stats.outgoing_synapse_count;
4506 }
4507 }
4508
4509 0
4510 }
4511
4512 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4524 let synapses = self.get_outgoing_synapses(source_neuron_id);
4525 synapses
4526 .iter()
4527 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4528 }
4529
4530 pub fn get_connection_weight(
4542 &self,
4543 source_neuron_id: u64,
4544 target_neuron_id: u64,
4545 ) -> Option<u8> {
4546 let synapses = self.get_outgoing_synapses(source_neuron_id);
4547 synapses
4548 .iter()
4549 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4550 .map(|(_, weight, _, _)| *weight)
4551 }
4552
4553 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4564 let neurons = self.get_neurons_in_area(cortical_id);
4565 let neuron_count = neurons.len();
4566
4567 if neuron_count == 0 {
4568 return (0, 0, 0.0);
4569 }
4570
4571 let mut total_synapses = 0;
4572 for neuron_id in neurons {
4573 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4574 }
4575
4576 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4577
4578 (neuron_count, total_synapses, avg_synapses)
4579 }
4580
4581 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4592 let cortical_idx = self.get_neuron_cortical_idx(neuron_id);
4593 self.cortical_idx_to_id.get(&cortical_idx).copied()
4594 }
4595
4596 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4607 let area = match self.cortical_areas.get(cortical_id) {
4608 Some(a) => a,
4609 None => return 0.0,
4610 };
4611
4612 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4613 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4614
4615 if volume == 0 {
4616 return 0.0;
4617 }
4618
4619 neuron_count as f32 / volume as f32
4620 }
4621
4622 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
4629 let mut stats = Vec::new();
4630
4631 for cortical_id in self.cortical_areas.keys() {
4632 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4633 let synapse_count = self.get_synapse_count_in_area(cortical_id);
4634 let density = self.get_neuron_density(cortical_id);
4635
4636 stats.push((
4637 cortical_id.to_string(),
4638 neuron_count,
4639 synapse_count,
4640 density,
4641 ));
4642 }
4643
4644 stats
4645 }
4646
4647 pub fn get_config(&self) -> &ConnectomeConfig {
4653 &self.config
4654 }
4655
4656 pub fn set_config(&mut self, config: ConnectomeConfig) {
4658 self.config = config;
4659 }
4660
4661 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
4680 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
4681
4682 use feagi_structures::genomic::cortical_area::{
4683 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
4684 };
4685
4686 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
4688 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
4689 })?;
4690
4691 let core_position = (0, 0, 0).into();
4693
4694 let death_id = CoreCorticalType::Death.to_cortical_id();
4696 if !self.cortical_areas.contains_key(&death_id) {
4697 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
4698 let death_area = CorticalArea::new(
4699 death_id,
4700 0, "_death".to_string(),
4702 core_dimensions,
4703 core_position,
4704 CorticalAreaType::Core(CoreCorticalType::Death),
4705 )
4706 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
4707 match self.add_cortical_area(death_area) {
4708 Ok(idx) => {
4709 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
4710 }
4711 Err(e) => {
4712 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
4713 return Err(e);
4714 }
4715 }
4716 } else {
4717 info!(target: "feagi-bdu", " ✓ _death area already exists");
4718 }
4719
4720 let power_id = CoreCorticalType::Power.to_cortical_id();
4722 if !self.cortical_areas.contains_key(&power_id) {
4723 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
4724 let power_area = CorticalArea::new(
4725 power_id,
4726 1, "_power".to_string(),
4728 core_dimensions,
4729 core_position,
4730 CorticalAreaType::Core(CoreCorticalType::Power),
4731 )
4732 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
4733 match self.add_cortical_area(power_area) {
4734 Ok(idx) => {
4735 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
4736 }
4737 Err(e) => {
4738 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
4739 return Err(e);
4740 }
4741 }
4742 } else {
4743 info!(target: "feagi-bdu", " ✓ _power area already exists");
4744 }
4745
4746 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
4748 if !self.cortical_areas.contains_key(&fatigue_id) {
4749 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
4750 let fatigue_area = CorticalArea::new(
4751 fatigue_id,
4752 2, "_fatigue".to_string(),
4754 core_dimensions,
4755 core_position,
4756 CorticalAreaType::Core(CoreCorticalType::Fatigue),
4757 )
4758 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
4759 match self.add_cortical_area(fatigue_area) {
4760 Ok(idx) => {
4761 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
4762 }
4763 Err(e) => {
4764 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
4765 return Err(e);
4766 }
4767 }
4768 } else {
4769 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
4770 }
4771
4772 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
4773 Ok(())
4774 }
4775
4776 #[deprecated(
4793 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
4794 )]
4795 #[allow(deprecated)]
4796 pub fn save_genome_to_json(
4797 &self,
4798 genome_id: Option<String>,
4799 genome_title: Option<String>,
4800 ) -> BduResult<String> {
4801 let mut brain_regions_with_parents = std::collections::HashMap::new();
4803
4804 for region_id in self.brain_regions.get_all_region_ids() {
4805 if let Some(region) = self.brain_regions.get_region(region_id) {
4806 let parent_id = self
4807 .brain_regions
4808 .get_parent(region_id)
4809 .map(|s| s.to_string());
4810 brain_regions_with_parents
4811 .insert(region_id.to_string(), (region.clone(), parent_id));
4812 }
4813 }
4814
4815 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
4817 &self.cortical_areas,
4818 &brain_regions_with_parents,
4819 genome_id,
4820 genome_title,
4821 )?)
4822 }
4823
4824 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
4855 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
4856
4857 self.cortical_areas.clear();
4859 self.cortical_id_to_idx.clear();
4860 self.cortical_idx_to_id.clear();
4861 self.next_cortical_idx = 3;
4863 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4864
4865 self.brain_regions = BrainRegionHierarchy::new();
4867
4868 if let Some(ref npu) = self.npu {
4870 let mut npu_lock = npu
4871 .lock()
4872 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4873 npu_lock
4874 .reset_for_new_genome()
4875 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
4876 }
4877
4878 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
4879 Ok(())
4880 }
4881
4882 pub fn resize_for_genome(
4892 &mut self,
4893 genome: &feagi_evolutionary::RuntimeGenome,
4894 ) -> BduResult<()> {
4895 self.morphology_registry = genome.morphologies.clone();
4897 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
4898
4899 let required_neurons = genome.stats.innate_neuron_count;
4901 let required_synapses = genome.stats.innate_synapse_count;
4902
4903 info!(target: "feagi-bdu",
4904 "Genome requires: {} neurons, {} synapses",
4905 required_neurons,
4906 required_synapses
4907 );
4908
4909 let mut total_voxels = 0;
4911 for area in genome.cortical_areas.values() {
4912 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4913 }
4914
4915 info!(target: "feagi-bdu",
4916 "Genome has {} cortical areas with {} total voxels",
4917 genome.cortical_areas.len(),
4918 total_voxels
4919 );
4920
4921 Ok(())
4926 }
4927
4928 pub fn create_synapse(
4947 &mut self,
4948 source_neuron_id: u64,
4949 target_neuron_id: u64,
4950 weight: u8,
4951 psp: u8,
4952 synapse_type: u8,
4953 ) -> BduResult<()> {
4954 let npu = self
4956 .npu
4957 .as_ref()
4958 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4959
4960 let mut npu_lock = npu
4961 .lock()
4962 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4963
4964 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4966 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4967
4968 if !source_exists {
4969 return Err(BduError::InvalidNeuron(format!(
4970 "Source neuron {} not found",
4971 source_neuron_id
4972 )));
4973 }
4974 if !target_exists {
4975 return Err(BduError::InvalidNeuron(format!(
4976 "Target neuron {} not found",
4977 target_neuron_id
4978 )));
4979 }
4980
4981 let syn_type = if synapse_type == 0 {
4983 feagi_npu_neural::synapse::SynapseType::Excitatory
4984 } else {
4985 feagi_npu_neural::synapse::SynapseType::Inhibitory
4986 };
4987
4988 let synapse_idx = npu_lock
4989 .add_synapse(
4990 NeuronId(source_neuron_id as u32),
4991 NeuronId(target_neuron_id as u32),
4992 feagi_npu_neural::types::SynapticWeight(weight),
4993 feagi_npu_neural::types::SynapticPsp(psp),
4994 syn_type,
4995 )
4996 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
4997
4998 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
4999 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
5000
5001 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5002 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5003 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
5004 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
5005
5006 let state_manager = StateManager::instance();
5007 let state_manager = state_manager.read();
5008 let core_state = state_manager.get_core_state();
5009 core_state.add_synapse_count(1);
5010 if let Some(cortical_id) = source_cortical_id {
5011 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5012 }
5013 if let Some(cortical_id) = target_cortical_id {
5014 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5015 }
5016
5017 Ok(())
5022 }
5023
5024 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
5027 if let Some(ref npu) = self.npu {
5028 if let Ok(mut npu_lock) = npu.lock() {
5029 let mut psp_uniform_flags = ahash::AHashMap::new();
5031 let mut mp_driven_psp_flags = ahash::AHashMap::new();
5032
5033 for (cortical_id, area) in &self.cortical_areas {
5034 let default_psp_uniform =
5036 *cortical_id == CoreCorticalType::Power.to_cortical_id();
5037 let psp_uniform = area
5038 .get_property("psp_uniform_distribution")
5039 .and_then(|v| v.as_bool())
5040 .unwrap_or(default_psp_uniform);
5041 psp_uniform_flags.insert(*cortical_id, psp_uniform);
5042
5043 let mp_driven_psp = area
5045 .get_property("mp_driven_psp")
5046 .and_then(|v| v.as_bool())
5047 .unwrap_or(false);
5048 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
5049 }
5050
5051 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
5053 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5054
5055 trace!(
5056 target: "feagi-bdu",
5057 "Synchronized cortical area flags to NPU ({} areas)",
5058 self.cortical_areas.len()
5059 );
5060 }
5061 }
5062
5063 Ok(())
5064 }
5065
5066 pub fn get_synapse(
5078 &self,
5079 source_neuron_id: u64,
5080 target_neuron_id: u64,
5081 ) -> Option<(u8, u8, u8)> {
5082 let npu = self.npu.as_ref()?;
5084 let npu_lock = npu.lock().ok()?;
5085
5086 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
5089
5090 for (source_id, weight, psp, synapse_type) in incoming {
5092 if source_id == source_neuron_id as u32 {
5093 return Some((weight, psp, synapse_type));
5094 }
5095 }
5096
5097 None
5098 }
5099
5100 pub fn update_synapse_weight(
5113 &mut self,
5114 source_neuron_id: u64,
5115 target_neuron_id: u64,
5116 new_weight: u8,
5117 ) -> BduResult<()> {
5118 let npu = self
5120 .npu
5121 .as_ref()
5122 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5123
5124 let mut npu_lock = npu
5125 .lock()
5126 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5127
5128 let updated = npu_lock.update_synapse_weight(
5130 NeuronId(source_neuron_id as u32),
5131 NeuronId(target_neuron_id as u32),
5132 feagi_npu_neural::types::SynapticWeight(new_weight),
5133 );
5134
5135 if updated {
5136 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
5137 Ok(())
5138 } else {
5139 Err(BduError::InvalidSynapse(format!(
5140 "Synapse {} -> {} not found",
5141 source_neuron_id, target_neuron_id
5142 )))
5143 }
5144 }
5145
5146 pub fn remove_synapse(
5158 &mut self,
5159 source_neuron_id: u64,
5160 target_neuron_id: u64,
5161 ) -> BduResult<bool> {
5162 let npu = self
5164 .npu
5165 .as_ref()
5166 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5167
5168 let mut npu_lock = npu
5169 .lock()
5170 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5171
5172 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5173 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5174 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
5175 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
5176
5177 let removed = npu_lock.remove_synapse(
5179 NeuronId(source_neuron_id as u32),
5180 NeuronId(target_neuron_id as u32),
5181 );
5182
5183 if removed {
5184 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
5185
5186 let state_manager = StateManager::instance();
5188 let state_manager = state_manager.read();
5189 let core_state = state_manager.get_core_state();
5190 core_state.subtract_synapse_count(1);
5191 if let Some(cortical_id) = source_cortical_id {
5192 state_manager
5193 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5194 }
5195 if let Some(cortical_id) = target_cortical_id {
5196 state_manager
5197 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5198 }
5199 }
5200
5201 Ok(removed)
5202 }
5203
5204 pub fn batch_create_neurons(
5222 &mut self,
5223 cortical_id: &CorticalID,
5224 neurons: Vec<NeuronData>,
5225 ) -> BduResult<Vec<u64>> {
5226 let npu = self
5228 .npu
5229 .as_ref()
5230 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5231
5232 let mut npu_lock = npu
5233 .lock()
5234 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5235
5236 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5238 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5239 })?;
5240 let cortical_idx = area.cortical_idx;
5241
5242 let count = neurons.len();
5243
5244 let mut x_coords = Vec::with_capacity(count);
5246 let mut y_coords = Vec::with_capacity(count);
5247 let mut z_coords = Vec::with_capacity(count);
5248 let mut firing_thresholds = Vec::with_capacity(count);
5249 let mut threshold_limits = Vec::with_capacity(count);
5250 let mut leak_coeffs = Vec::with_capacity(count);
5251 let mut resting_potentials = Vec::with_capacity(count);
5252 let mut neuron_types = Vec::with_capacity(count);
5253 let mut refractory_periods = Vec::with_capacity(count);
5254 let mut excitabilities = Vec::with_capacity(count);
5255 let mut consec_fire_limits = Vec::with_capacity(count);
5256 let mut snooze_lengths = Vec::with_capacity(count);
5257 let mut mp_accums = Vec::with_capacity(count);
5258 let mut cortical_areas = Vec::with_capacity(count);
5259
5260 for (
5261 x,
5262 y,
5263 z,
5264 threshold,
5265 threshold_limit,
5266 leak,
5267 resting,
5268 ntype,
5269 refract,
5270 excit,
5271 consec_limit,
5272 snooze,
5273 mp_accum,
5274 ) in neurons
5275 {
5276 x_coords.push(x);
5277 y_coords.push(y);
5278 z_coords.push(z);
5279 firing_thresholds.push(threshold);
5280 threshold_limits.push(threshold_limit);
5281 leak_coeffs.push(leak);
5282 resting_potentials.push(resting);
5283 neuron_types.push(ntype);
5284 refractory_periods.push(refract);
5285 excitabilities.push(excit);
5286 consec_fire_limits.push(consec_limit);
5287 snooze_lengths.push(snooze);
5288 mp_accums.push(mp_accum);
5289 cortical_areas.push(cortical_idx);
5290 }
5291
5292 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5294
5295 let firing_thresholds_t = firing_thresholds;
5300 let threshold_limits_t = threshold_limits;
5301 let resting_potentials_t = resting_potentials;
5302 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5303 firing_thresholds_t,
5304 threshold_limits_t,
5305 leak_coeffs,
5306 resting_potentials_t,
5307 neuron_types,
5308 refractory_periods,
5309 excitabilities,
5310 consec_fire_limits,
5311 snooze_lengths,
5312 mp_accums,
5313 cortical_areas,
5314 x_coords,
5315 y_coords,
5316 z_coords,
5317 );
5318
5319 let mut neuron_ids = Vec::with_capacity(count);
5321 for i in 0..neurons_created {
5322 neuron_ids.push((first_neuron_id + i) as u64);
5323 }
5324
5325 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5326
5327 let state_manager = StateManager::instance();
5329 let state_manager = state_manager.read();
5330 let core_state = state_manager.get_core_state();
5331 core_state.add_neuron_count(neurons_created);
5332 core_state.add_regular_neuron_count(neurons_created);
5333 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5334
5335 {
5337 let mut cache = self.cached_neuron_counts_per_area.write();
5338 cache
5339 .entry(*cortical_id)
5340 .or_insert_with(|| AtomicUsize::new(0))
5341 .fetch_add(count, Ordering::Relaxed);
5342 }
5343
5344 Ok(neuron_ids)
5345 }
5346
5347 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5358 let npu = self
5360 .npu
5361 .as_ref()
5362 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5363
5364 let mut npu_lock = npu
5365 .lock()
5366 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5367
5368 let mut deleted_count = 0;
5369 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5370 std::collections::HashMap::new();
5371
5372 for neuron_id in neuron_ids {
5375 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5376 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
5377
5378 if npu_lock.delete_neuron(neuron_id as u32) {
5379 deleted_count += 1;
5380 if let Some(cortical_id) = cortical_id {
5381 let key = cortical_id.as_base_64();
5382 *per_area_deleted.entry(key).or_insert(0) += 1;
5383 }
5384 }
5385 }
5386
5387 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5388
5389 if deleted_count > 0 {
5391 let state_manager = StateManager::instance();
5392 let state_manager = state_manager.read();
5393 let core_state = state_manager.get_core_state();
5394 core_state.subtract_neuron_count(deleted_count as u32);
5395 core_state.subtract_regular_neuron_count(deleted_count as u32);
5396 for (cortical_id, count) in per_area_deleted {
5397 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5398 }
5399 }
5400
5401 Ok(deleted_count)
5408 }
5409
5410 pub fn update_neuron_properties(
5429 &mut self,
5430 neuron_id: u64,
5431 firing_threshold: Option<f32>,
5432 leak_coefficient: Option<f32>,
5433 resting_potential: Option<f32>,
5434 excitability: Option<f32>,
5435 ) -> BduResult<()> {
5436 let npu = self
5438 .npu
5439 .as_ref()
5440 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5441
5442 let mut npu_lock = npu
5443 .lock()
5444 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5445
5446 let neuron_id_u32 = neuron_id as u32;
5447
5448 let mut updated = false;
5450
5451 if let Some(threshold) = firing_threshold {
5453 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5454 updated = true;
5455 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5456 } else if !updated {
5457 return Err(BduError::InvalidNeuron(format!(
5458 "Neuron {} not found",
5459 neuron_id
5460 )));
5461 }
5462 }
5463
5464 if let Some(leak) = leak_coefficient {
5465 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5466 updated = true;
5467 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5468 } else if !updated {
5469 return Err(BduError::InvalidNeuron(format!(
5470 "Neuron {} not found",
5471 neuron_id
5472 )));
5473 }
5474 }
5475
5476 if let Some(resting) = resting_potential {
5477 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5478 updated = true;
5479 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5480 } else if !updated {
5481 return Err(BduError::InvalidNeuron(format!(
5482 "Neuron {} not found",
5483 neuron_id
5484 )));
5485 }
5486 }
5487
5488 if let Some(excit) = excitability {
5489 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
5490 updated = true;
5491 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
5492 } else if !updated {
5493 return Err(BduError::InvalidNeuron(format!(
5494 "Neuron {} not found",
5495 neuron_id
5496 )));
5497 }
5498 }
5499
5500 if !updated {
5501 return Err(BduError::Internal(
5502 "No properties provided for update".to_string(),
5503 ));
5504 }
5505
5506 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
5507
5508 Ok(())
5509 }
5510
5511 pub fn set_neuron_firing_threshold(
5523 &mut self,
5524 neuron_id: u64,
5525 new_threshold: f32,
5526 ) -> BduResult<()> {
5527 let npu = self
5529 .npu
5530 .as_ref()
5531 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5532
5533 let mut npu_lock = npu
5534 .lock()
5535 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5536
5537 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
5539 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
5540 Ok(())
5541 } else {
5542 Err(BduError::InvalidNeuron(format!(
5543 "Neuron {} not found",
5544 neuron_id
5545 )))
5546 }
5547 }
5548
5549 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
5564 self.cortical_areas
5565 .values()
5566 .find(|area| area.name == name)
5567 .cloned()
5568 }
5569
5570 pub fn resize_cortical_area(
5587 &mut self,
5588 cortical_id: &CorticalID,
5589 new_dimensions: CorticalAreaDimensions,
5590 ) -> BduResult<()> {
5591 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
5593 return Err(BduError::InvalidArea(format!(
5594 "Invalid dimensions: {:?} (all must be > 0)",
5595 new_dimensions
5596 )));
5597 }
5598
5599 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
5601 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5602 })?;
5603
5604 let old_dimensions = area.dimensions;
5605 area.dimensions = new_dimensions;
5606
5607 info!(target: "feagi-bdu",
5611 "Resized cortical area {} from {:?} to {:?}",
5612 cortical_id,
5613 old_dimensions,
5614 new_dimensions
5615 );
5616
5617 self.refresh_cortical_area_hashes(false, true);
5618
5619 Ok(())
5620 }
5621
5622 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
5633 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
5634 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5635 })?;
5636
5637 Ok(region
5639 .cortical_areas
5640 .iter()
5641 .map(|id| id.as_base_64())
5642 .collect())
5643 }
5644
5645 pub fn update_brain_region(
5658 &mut self,
5659 region_id: &str,
5660 new_name: Option<String>,
5661 new_description: Option<String>,
5662 ) -> BduResult<()> {
5663 let region = self
5664 .brain_regions
5665 .get_region_mut(region_id)
5666 .ok_or_else(|| {
5667 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5668 })?;
5669
5670 if let Some(name) = new_name {
5671 region.name = name;
5672 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
5673 }
5674
5675 if let Some(desc) = new_description {
5676 region
5678 .properties
5679 .insert("description".to_string(), serde_json::json!(desc));
5680 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
5681 }
5682
5683 info!(target: "feagi-bdu","Updated brain region {}", region_id);
5684
5685 self.refresh_brain_regions_hash();
5686
5687 Ok(())
5688 }
5689
5690 pub fn update_brain_region_properties(
5704 &mut self,
5705 region_id: &str,
5706 properties: std::collections::HashMap<String, serde_json::Value>,
5707 ) -> BduResult<()> {
5708 use tracing::{debug, info};
5709
5710 let region = self
5711 .brain_regions
5712 .get_region_mut(region_id)
5713 .ok_or_else(|| {
5714 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5715 })?;
5716
5717 for (key, value) in properties {
5718 match key.as_str() {
5719 "title" | "name" => {
5720 if let Some(name) = value.as_str() {
5721 region.name = name.to_string();
5722 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
5723 }
5724 }
5725 "coordinate_3d" | "coordinates_3d" => {
5726 region
5727 .properties
5728 .insert("coordinate_3d".to_string(), value.clone());
5729 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
5730 }
5731 "coordinate_2d" | "coordinates_2d" => {
5732 region
5733 .properties
5734 .insert("coordinate_2d".to_string(), value.clone());
5735 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
5736 }
5737 "description" => {
5738 region
5739 .properties
5740 .insert("description".to_string(), value.clone());
5741 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
5742 }
5743 "region_type" => {
5744 if let Some(type_str) = value.as_str() {
5745 region.region_type = feagi_structures::genomic::RegionType::Undefined;
5748 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
5749 }
5750 }
5751 _ => {
5753 region.properties.insert(key.clone(), value.clone());
5754 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
5755 }
5756 }
5757 }
5758
5759 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
5760
5761 Ok(())
5762 }
5763
5764 pub fn get_neuron_by_coordinates(
5782 &self,
5783 cortical_id: &CorticalID,
5784 x: u32,
5785 y: u32,
5786 z: u32,
5787 ) -> Option<u64> {
5788 let area = self.get_cortical_area(cortical_id)?;
5790 let cortical_idx = area.cortical_idx;
5791
5792 let npu = self.npu.as_ref()?;
5794 let npu_lock = npu.lock().ok()?;
5795
5796 npu_lock
5797 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
5798 .map(|id| id as u64)
5799 }
5800
5801 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
5812 let npu = self.npu.as_ref()?;
5813 let npu_lock = npu.lock().ok()?;
5814
5815 let neuron_count = npu_lock.get_neuron_count();
5817 if (neuron_id as usize) >= neuron_count {
5818 return None;
5819 }
5820
5821 Some(
5822 npu_lock
5823 .get_neuron_coordinates(neuron_id as u32)
5824 .unwrap_or((0, 0, 0)),
5825 )
5826 }
5827
5828 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
5839 let npu = self.npu.as_ref()?;
5840 let npu_lock = npu.lock().ok()?;
5841
5842 let neuron_count = npu_lock.get_neuron_count();
5844 if (neuron_id as usize) >= neuron_count {
5845 return None;
5846 }
5847
5848 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5849
5850 self.cortical_areas
5852 .values()
5853 .find(|area| area.cortical_idx == cortical_idx)
5854 .map(|area| area.cortical_id)
5855 }
5856
5857 pub fn get_neuron_properties(
5868 &self,
5869 neuron_id: u64,
5870 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5871 let npu = self.npu.as_ref()?;
5872 let npu_lock = npu.lock().ok()?;
5873
5874 let neuron_id_u32 = neuron_id as u32;
5875 let idx = neuron_id as usize;
5876
5877 let neuron_count = npu_lock.get_neuron_count();
5879 if idx >= neuron_count {
5880 return None;
5881 }
5882
5883 let mut properties = std::collections::HashMap::new();
5884
5885 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
5887
5888 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
5890 properties.insert("x".to_string(), serde_json::json!(x));
5891 properties.insert("y".to_string(), serde_json::json!(y));
5892 properties.insert("z".to_string(), serde_json::json!(z));
5893
5894 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32);
5896 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
5897
5898 if let Some((consec_count, consec_limit, snooze, mp, threshold, refract_countdown)) =
5900 npu_lock.get_neuron_state(NeuronId(neuron_id_u32))
5901 {
5902 properties.insert(
5903 "consecutive_fire_count".to_string(),
5904 serde_json::json!(consec_count),
5905 );
5906 properties.insert(
5907 "consecutive_fire_limit".to_string(),
5908 serde_json::json!(consec_limit),
5909 );
5910 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
5911 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
5912 properties.insert("threshold".to_string(), serde_json::json!(threshold));
5913 properties.insert(
5914 "refractory_countdown".to_string(),
5915 serde_json::json!(refract_countdown),
5916 );
5917 }
5918
5919 if let Some(leak) = npu_lock.get_neuron_property_by_index(idx, "leak_coefficient") {
5921 properties.insert("leak_coefficient".to_string(), serde_json::json!(leak));
5922 }
5923 if let Some(resting) = npu_lock.get_neuron_property_by_index(idx, "resting_potential") {
5924 properties.insert("resting_potential".to_string(), serde_json::json!(resting));
5925 }
5926 if let Some(excit) = npu_lock.get_neuron_property_by_index(idx, "excitability") {
5927 properties.insert("excitability".to_string(), serde_json::json!(excit));
5928 }
5929 if let Some(threshold_limit) = npu_lock.get_neuron_property_by_index(idx, "threshold_limit")
5930 {
5931 properties.insert(
5932 "threshold_limit".to_string(),
5933 serde_json::json!(threshold_limit),
5934 );
5935 }
5936
5937 if let Some(refract_period) =
5939 npu_lock.get_neuron_property_u16_by_index(idx, "refractory_period")
5940 {
5941 properties.insert(
5942 "refractory_period".to_string(),
5943 serde_json::json!(refract_period),
5944 );
5945 }
5946
5947 Some(properties)
5948 }
5949
5950 pub fn get_neuron_property(
5962 &self,
5963 neuron_id: u64,
5964 property_name: &str,
5965 ) -> Option<serde_json::Value> {
5966 self.get_neuron_properties(neuron_id)?
5967 .get(property_name)
5968 .cloned()
5969 }
5970
5971 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
5982 self.cortical_areas.keys().copied().collect()
5983 }
5984
5985 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
5992 self.cortical_areas
5993 .values()
5994 .map(|area| area.cortical_idx)
5995 .collect()
5996 }
5997
5998 pub fn get_cortical_area_names(&self) -> Vec<String> {
6005 self.cortical_areas
6006 .values()
6007 .map(|area| area.name.clone())
6008 .collect()
6009 }
6010
6011 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
6018 use crate::models::CorticalAreaExt;
6019 self.cortical_areas
6020 .values()
6021 .filter(|area| area.is_input_area())
6022 .map(|area| area.cortical_id)
6023 .collect()
6024 }
6025
6026 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
6033 use crate::models::CorticalAreaExt;
6034 self.cortical_areas
6035 .values()
6036 .filter(|area| area.is_output_area())
6037 .map(|area| area.cortical_id)
6038 .collect()
6039 }
6040
6041 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
6048 self.cortical_areas
6049 .values()
6050 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
6051 (
6052 max_w.max(area.dimensions.width as usize),
6053 max_h.max(area.dimensions.height as usize),
6054 max_d.max(area.dimensions.depth as usize),
6055 )
6056 })
6057 }
6058
6059 pub fn get_cortical_area_properties(
6070 &self,
6071 cortical_id: &CorticalID,
6072 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6073 let area = self.get_cortical_area(cortical_id)?;
6074
6075 let mut properties = std::collections::HashMap::new();
6076 properties.insert(
6077 "cortical_id".to_string(),
6078 serde_json::json!(area.cortical_id),
6079 );
6080 properties.insert(
6081 "cortical_id_s".to_string(),
6082 serde_json::json!(area.cortical_id.to_string()),
6083 );
6084 properties.insert(
6085 "cortical_idx".to_string(),
6086 serde_json::json!(area.cortical_idx),
6087 );
6088 properties.insert("name".to_string(), serde_json::json!(area.name));
6089 use crate::models::CorticalAreaExt;
6090 properties.insert(
6091 "area_type".to_string(),
6092 serde_json::json!(area.get_cortical_group()),
6093 );
6094 properties.insert(
6095 "dimensions".to_string(),
6096 serde_json::json!({
6097 "width": area.dimensions.width,
6098 "height": area.dimensions.height,
6099 "depth": area.dimensions.depth,
6100 }),
6101 );
6102 properties.insert("position".to_string(), serde_json::json!(area.position));
6103
6104 for (key, value) in &area.properties {
6106 properties.insert(key.clone(), value.clone());
6107 }
6108
6109 properties.extend(area.properties.clone());
6111
6112 Some(properties)
6113 }
6114
6115 pub fn get_all_cortical_area_properties(
6122 &self,
6123 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
6124 self.cortical_areas
6125 .keys()
6126 .filter_map(|id| self.get_cortical_area_properties(id))
6127 .collect()
6128 }
6129
6130 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
6141 self.brain_regions
6142 .get_all_region_ids()
6143 .into_iter()
6144 .cloned()
6145 .collect()
6146 }
6147
6148 pub fn get_brain_region_names(&self) -> Vec<String> {
6155 self.brain_regions
6156 .get_all_region_ids()
6157 .iter()
6158 .filter_map(|id| {
6159 self.brain_regions
6160 .get_region(id)
6161 .map(|region| region.name.clone())
6162 })
6163 .collect()
6164 }
6165
6166 pub fn get_brain_region_properties(
6177 &self,
6178 region_id: &str,
6179 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6180 let region = self.brain_regions.get_region(region_id)?;
6181
6182 let mut properties = std::collections::HashMap::new();
6183 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
6184 properties.insert("name".to_string(), serde_json::json!(region.name));
6185 properties.insert(
6186 "region_type".to_string(),
6187 serde_json::json!(format!("{:?}", region.region_type)),
6188 );
6189 properties.insert(
6190 "cortical_areas".to_string(),
6191 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
6192 );
6193
6194 properties.extend(region.properties.clone());
6196
6197 Some(properties)
6198 }
6199
6200 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
6211 self.cortical_areas.contains_key(cortical_id)
6212 }
6213
6214 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6225 self.brain_regions.get_region(region_id).is_some()
6226 }
6227
6228 pub fn get_brain_region_count(&self) -> usize {
6235 self.brain_regions.region_count()
6236 }
6237
6238 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6249 self.get_neurons_in_area(cortical_id)
6253 }
6254}
6255
6256impl std::fmt::Debug for ConnectomeManager {
6258 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6259 f.debug_struct("ConnectomeManager")
6260 .field("cortical_areas", &self.cortical_areas.len())
6261 .field("next_cortical_idx", &self.next_cortical_idx)
6262 .field("brain_regions", &self.brain_regions)
6263 .field(
6264 "npu",
6265 &if self.npu.is_some() {
6266 "Connected"
6267 } else {
6268 "Not connected"
6269 },
6270 )
6271 .field("initialized", &self.initialized)
6272 .finish()
6273 }
6274}
6275
6276#[cfg(test)]
6277mod tests {
6278 use super::*;
6279 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6280
6281 #[test]
6282 fn test_singleton_instance() {
6283 let instance1 = ConnectomeManager::instance();
6284 let instance2 = ConnectomeManager::instance();
6285
6286 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6288 }
6289
6290 #[test]
6291 fn test_add_cortical_area() {
6292 ConnectomeManager::reset_for_testing();
6293
6294 let instance = ConnectomeManager::instance();
6295 let mut manager = instance.write();
6296
6297 use feagi_structures::genomic::cortical_area::{
6298 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6299 };
6300 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6302 let area = CorticalArea::new(
6303 cortical_id,
6304 0,
6305 "Visual Input".to_string(),
6306 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6307 (0, 0, 0).into(),
6308 cortical_type,
6309 )
6310 .unwrap();
6311
6312 let initial_count = manager.get_cortical_area_count();
6313 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6314
6315 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6316 assert!(manager.has_cortical_area(&cortical_id));
6317 assert!(manager.is_initialized());
6318 }
6319
6320 #[test]
6321 fn test_cortical_area_lookups() {
6322 ConnectomeManager::reset_for_testing();
6323
6324 let instance = ConnectomeManager::instance();
6325 let mut manager = instance.write();
6326
6327 use feagi_structures::genomic::cortical_area::{
6328 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6329 };
6330 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6332 let area = CorticalArea::new(
6333 cortical_id,
6334 0,
6335 "Test Area".to_string(),
6336 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6337 (0, 0, 0).into(),
6338 cortical_type,
6339 )
6340 .unwrap();
6341
6342 let cortical_idx = manager.add_cortical_area(area).unwrap();
6343
6344 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6346
6347 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6349
6350 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6352 assert_eq!(retrieved_area.name, "Test Area");
6353 }
6354
6355 #[test]
6356 fn test_remove_cortical_area() {
6357 ConnectomeManager::reset_for_testing();
6358
6359 let instance = ConnectomeManager::instance();
6360 let mut manager = instance.write();
6361
6362 use feagi_structures::genomic::cortical_area::{
6363 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6364 };
6365 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6366
6367 if manager.has_cortical_area(&cortical_id) {
6369 manager.remove_cortical_area(&cortical_id).unwrap();
6370 }
6371
6372 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6373 let area = CorticalArea::new(
6374 cortical_id,
6375 0,
6376 "Test".to_string(),
6377 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6378 (0, 0, 0).into(),
6379 cortical_type,
6380 )
6381 .unwrap();
6382
6383 let initial_count = manager.get_cortical_area_count();
6384 manager.add_cortical_area(area).unwrap();
6385 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6386
6387 manager.remove_cortical_area(&cortical_id).unwrap();
6388 assert_eq!(manager.get_cortical_area_count(), initial_count);
6389 assert!(!manager.has_cortical_area(&cortical_id));
6390 }
6391
6392 #[test]
6393 fn test_duplicate_area_error() {
6394 ConnectomeManager::reset_for_testing();
6395
6396 let instance = ConnectomeManager::instance();
6397 let mut manager = instance.write();
6398
6399 use feagi_structures::genomic::cortical_area::{
6400 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6401 };
6402 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
6405 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6406 let area1 = CorticalArea::new(
6407 cortical_id,
6408 0,
6409 "First".to_string(),
6410 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6411 (0, 0, 0).into(),
6412 cortical_type,
6413 )
6414 .unwrap();
6415
6416 let area2 = CorticalArea::new(
6417 cortical_id, 1,
6419 "Second".to_string(),
6420 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6421 (0, 0, 0).into(),
6422 cortical_type,
6423 )
6424 .unwrap();
6425
6426 manager.add_cortical_area(area1).unwrap();
6427 let result = manager.add_cortical_area(area2);
6428
6429 assert!(result.is_err());
6430 }
6431
6432 #[test]
6433 fn test_brain_region_management() {
6434 ConnectomeManager::reset_for_testing();
6435
6436 let instance = ConnectomeManager::instance();
6437 let mut manager = instance.write();
6438
6439 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
6440 let region_id_str = region_id.to_string();
6441 let root = BrainRegion::new(
6442 region_id,
6443 "Root".to_string(),
6444 feagi_structures::genomic::brain_regions::RegionType::Undefined,
6445 )
6446 .unwrap();
6447
6448 let initial_count = manager.get_brain_region_ids().len();
6449 manager.add_brain_region(root, None).unwrap();
6450
6451 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
6452 assert!(manager.get_brain_region(®ion_id_str).is_some());
6453 }
6454
6455 #[test]
6456 fn test_synapse_operations() {
6457 use feagi_npu_burst_engine::npu::RustNPU;
6458 use feagi_npu_burst_engine::TracingMutex;
6459 use std::sync::Arc;
6460
6461 use feagi_npu_burst_engine::backend::CPUBackend;
6463 use feagi_npu_burst_engine::DynamicNPU;
6464 use feagi_npu_runtime::StdRuntime;
6465
6466 let runtime = StdRuntime;
6467 let backend = CPUBackend::new();
6468 let npu_result =
6469 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
6470 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
6471 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
6472
6473 use feagi_structures::genomic::cortical_area::{
6475 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6476 };
6477 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6479 let area = CorticalArea::new(
6480 cortical_id,
6481 0, "Test Area".to_string(),
6483 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
6484 (0, 0, 0).into(), cortical_type,
6486 )
6487 .unwrap();
6488 let cortical_idx = manager.add_cortical_area(area).unwrap();
6489
6490 if let Some(npu_arc) = manager.get_npu() {
6492 if let Ok(mut npu_guard) = npu_arc.try_lock() {
6493 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
6494 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
6495 }
6496 }
6497 }
6498
6499 let neuron1_id = manager
6501 .add_neuron(
6502 &cortical_id,
6503 0,
6504 0,
6505 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
6517 .unwrap();
6518
6519 let neuron2_id = manager
6520 .add_neuron(
6521 &cortical_id,
6522 1,
6523 0,
6524 0, 100.0,
6526 f32::MAX, 0.1,
6528 -60.0,
6529 0,
6530 2,
6531 1.0,
6532 5,
6533 10,
6534 false,
6535 )
6536 .unwrap();
6537
6538 manager
6540 .create_synapse(
6541 neuron1_id, neuron2_id, 128, 64, 0, )
6545 .unwrap();
6546
6547 println!("✅ Synapse creation test passed");
6550 }
6551
6552 #[test]
6553 fn test_apply_cortical_mapping_missing_rules_is_ok() {
6554 let mut manager = ConnectomeManager::new_for_testing();
6557
6558 use feagi_structures::genomic::cortical_area::{
6559 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6560 };
6561
6562 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
6563 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
6564
6565 let src_area = CorticalArea::new(
6566 src_id,
6567 0,
6568 "src".to_string(),
6569 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6570 (0, 0, 0).into(),
6571 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6572 )
6573 .unwrap();
6574
6575 let dst_area = CorticalArea::new(
6576 dst_id,
6577 1,
6578 "dst".to_string(),
6579 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6580 (0, 0, 0).into(),
6581 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6582 )
6583 .unwrap();
6584
6585 manager.add_cortical_area(src_area).unwrap();
6586 manager.add_cortical_area(dst_area).unwrap();
6587
6588 let count = manager
6590 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6591 .unwrap();
6592 assert_eq!(count, 0);
6593
6594 manager
6596 .update_cortical_mapping(
6597 &src_id,
6598 &dst_id,
6599 vec![serde_json::json!({"morphology_id":"m1"})],
6600 )
6601 .unwrap();
6602 manager
6603 .update_cortical_mapping(&src_id, &dst_id, vec![])
6604 .unwrap();
6605
6606 let count2 = manager
6607 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6608 .unwrap();
6609 assert_eq!(count2, 0);
6610 }
6611
6612 #[test]
6613 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
6614 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6615 let mapping_dst = serde_json::json!({
6616 "csrc0002": [
6617 {"morphology_id": "m1"}
6618 ]
6619 });
6620 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
6621
6622 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
6623 .expect("legacy destination key should resolve");
6624 assert_eq!(rules.len(), 1);
6625 assert_eq!(
6626 rules[0].get("morphology_id").and_then(|v| v.as_str()),
6627 Some("m1")
6628 );
6629 }
6630
6631 #[test]
6632 fn test_mapping_deletion_prunes_synapses_between_areas() {
6633 use feagi_npu_burst_engine::backend::CPUBackend;
6634 use feagi_npu_burst_engine::RustNPU;
6635 use feagi_npu_burst_engine::TracingMutex;
6636 use feagi_npu_runtime::StdRuntime;
6637 use feagi_structures::genomic::cortical_area::{
6638 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6639 };
6640 use std::sync::Arc;
6641
6642 let runtime = StdRuntime;
6644 let backend = CPUBackend::new();
6645 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6646 let dyn_npu = Arc::new(TracingMutex::new(
6647 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6648 "TestNPU",
6649 ));
6650 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6651
6652 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
6654 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
6655
6656 let src_area = CorticalArea::new(
6657 src_id,
6658 0,
6659 "src".to_string(),
6660 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6661 (0, 0, 0).into(),
6662 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6663 )
6664 .unwrap();
6665 let dst_area = CorticalArea::new(
6666 dst_id,
6667 1,
6668 "dst".to_string(),
6669 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6670 (0, 0, 0).into(),
6671 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6672 )
6673 .unwrap();
6674
6675 manager.add_cortical_area(src_area).unwrap();
6676 manager.add_cortical_area(dst_area).unwrap();
6677
6678 let s0 = manager
6680 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6681 .unwrap();
6682 let s1 = manager
6683 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6684 .unwrap();
6685 let t0 = manager
6686 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6687 .unwrap();
6688 let t1 = manager
6689 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6690 .unwrap();
6691
6692 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6694 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6695
6696 {
6698 let mut npu = dyn_npu.lock().unwrap();
6699 npu.rebuild_synapse_index();
6700 assert_eq!(npu.get_synapse_count(), 2);
6701 }
6702
6703 manager
6705 .update_cortical_mapping(&src_id, &dst_id, vec![])
6706 .unwrap();
6707 let created = manager
6708 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6709 .unwrap();
6710 assert_eq!(created, 0);
6711
6712 {
6714 let mut npu = dyn_npu.lock().unwrap();
6715 npu.rebuild_synapse_index();
6717 assert_eq!(npu.get_synapse_count(), 0);
6718 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6719 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6720 }
6721 }
6722
6723 #[test]
6724 fn test_mapping_update_prunes_synapses_between_areas() {
6725 use feagi_npu_burst_engine::backend::CPUBackend;
6726 use feagi_npu_burst_engine::RustNPU;
6727 use feagi_npu_burst_engine::TracingMutex;
6728 use feagi_npu_runtime::StdRuntime;
6729 use feagi_structures::genomic::cortical_area::{
6730 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6731 };
6732 use std::sync::Arc;
6733
6734 let runtime = StdRuntime;
6736 let backend = CPUBackend::new();
6737 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6738 let dyn_npu = Arc::new(TracingMutex::new(
6739 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6740 "TestNPU",
6741 ));
6742 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6743
6744 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6746
6747 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
6750 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
6751
6752 let src_area = CorticalArea::new(
6753 src_id,
6754 0,
6755 "src".to_string(),
6756 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6757 (0, 0, 0).into(),
6758 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6759 )
6760 .unwrap();
6761 let dst_area = CorticalArea::new(
6762 dst_id,
6763 0,
6764 "dst".to_string(),
6765 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6766 (0, 0, 0).into(),
6767 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6768 )
6769 .unwrap();
6770
6771 manager.add_cortical_area(src_area).unwrap();
6772 manager.add_cortical_area(dst_area).unwrap();
6773
6774 let s0 = manager
6776 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6777 .unwrap();
6778 let s1 = manager
6779 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6780 .unwrap();
6781 let t0 = manager
6782 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6783 .unwrap();
6784 let t1 = manager
6785 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6786 .unwrap();
6787
6788 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6790 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6791
6792 {
6794 let mut npu = dyn_npu.lock().unwrap();
6795 npu.rebuild_synapse_index();
6796 assert_eq!(npu.get_synapse_count(), 2);
6797 }
6798
6799 manager
6805 .update_cortical_mapping(
6806 &src_id,
6807 &dst_id,
6808 vec![serde_json::json!({
6809 "morphology_id": "episodic_memory",
6810 "morphology_scalar": [1],
6811 "postSynapticCurrent_multiplier": 1,
6812 "plasticity_flag": false,
6813 "plasticity_constant": 0,
6814 "ltp_multiplier": 0,
6815 "ltd_multiplier": 0,
6816 "plasticity_window": 0,
6817 })],
6818 )
6819 .unwrap();
6820 let created = manager
6821 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6822 .unwrap();
6823 assert_eq!(created, 0);
6824
6825 {
6827 let mut npu = dyn_npu.lock().unwrap();
6828 npu.rebuild_synapse_index();
6830 assert_eq!(npu.get_synapse_count(), 0);
6831 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6832 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6833 }
6834 }
6835
6836 #[test]
6837 fn test_upstream_area_tracking() {
6838 use crate::models::cortical_area::CorticalArea;
6840 use feagi_npu_burst_engine::backend::CPUBackend;
6841 use feagi_npu_burst_engine::TracingMutex;
6842 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6843 use feagi_npu_runtime::StdRuntime;
6844 use feagi_structures::genomic::cortical_area::{
6845 CorticalAreaDimensions, CorticalAreaType, CorticalID,
6846 };
6847
6848 let runtime = StdRuntime;
6850 let backend = CPUBackend::new();
6851 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6852 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6853 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6854
6855 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6858
6859 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
6861 let src_area = CorticalArea::new(
6862 src_id,
6863 0,
6864 "Source Area".to_string(),
6865 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6866 (0, 0, 0).into(),
6867 CorticalAreaType::Custom(
6868 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6869 ),
6870 )
6871 .unwrap();
6872 let src_idx = manager.add_cortical_area(src_area).unwrap();
6873
6874 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
6876 let dst_area = CorticalArea::new(
6877 dst_id,
6878 0,
6879 "Dest Area".to_string(),
6880 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6881 (0, 0, 0).into(),
6882 CorticalAreaType::Custom(
6883 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6884 ),
6885 )
6886 .unwrap();
6887 manager.add_cortical_area(dst_area).unwrap();
6888
6889 {
6891 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
6892 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
6893 assert!(
6894 upstream.as_array().unwrap().is_empty(),
6895 "Upstream areas should be empty initially"
6896 );
6897 }
6898
6899 let mapping_data = vec![serde_json::json!({
6901 "morphology_id": "episodic_memory",
6902 "morphology_scalar": 1,
6903 "postSynapticCurrent_multiplier": 1.0,
6904 })];
6905 manager
6906 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
6907 .unwrap();
6908 manager
6909 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6910 .unwrap();
6911
6912 {
6914 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6915 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
6916 assert_eq!(
6917 upstream_areas[0], src_idx,
6918 "Upstream area should be src_idx"
6919 );
6920 }
6921
6922 manager
6924 .update_cortical_mapping(&src_id, &dst_id, vec![])
6925 .unwrap();
6926 manager
6927 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6928 .unwrap();
6929
6930 {
6932 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6933 assert_eq!(
6934 upstream_areas.len(),
6935 0,
6936 "Should have 0 upstream areas after deletion"
6937 );
6938 }
6939 }
6940
6941 #[test]
6942 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
6943 use crate::models::cortical_area::CorticalArea;
6944 use feagi_npu_burst_engine::backend::CPUBackend;
6945 use feagi_npu_burst_engine::TracingMutex;
6946 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6947 use feagi_npu_runtime::StdRuntime;
6948 use feagi_structures::genomic::cortical_area::{
6949 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
6950 };
6951 use std::sync::Arc;
6952
6953 let runtime = StdRuntime;
6954 let backend = CPUBackend::new();
6955 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6956 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6957 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6958 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6959
6960 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6961 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
6962 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
6963 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
6964
6965 let a1_area = CorticalArea::new(
6966 a1_id,
6967 0,
6968 "A1".to_string(),
6969 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6970 (0, 0, 0).into(),
6971 CorticalAreaType::Custom(
6972 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6973 ),
6974 )
6975 .unwrap();
6976 let a2_area = CorticalArea::new(
6977 a2_id,
6978 0,
6979 "A2".to_string(),
6980 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6981 (0, 0, 0).into(),
6982 CorticalAreaType::Custom(
6983 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6984 ),
6985 )
6986 .unwrap();
6987
6988 let mut m1_area = CorticalArea::new(
6989 m1_id,
6990 0,
6991 "M1".to_string(),
6992 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6993 (0, 0, 0).into(),
6994 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6995 )
6996 .unwrap();
6997 m1_area
6998 .properties
6999 .insert("is_mem_type".to_string(), serde_json::json!(true));
7000 m1_area
7001 .properties
7002 .insert("temporal_depth".to_string(), serde_json::json!(1));
7003
7004 let mut m2_area = CorticalArea::new(
7005 m2_id,
7006 0,
7007 "M2".to_string(),
7008 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7009 (0, 0, 0).into(),
7010 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7011 )
7012 .unwrap();
7013 m2_area
7014 .properties
7015 .insert("is_mem_type".to_string(), serde_json::json!(true));
7016 m2_area
7017 .properties
7018 .insert("temporal_depth".to_string(), serde_json::json!(1));
7019
7020 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
7021 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
7022 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
7023 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
7024
7025 manager
7026 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7027 .unwrap();
7028 manager
7029 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7030 .unwrap();
7031
7032 let episodic_mapping = vec![serde_json::json!({
7033 "morphology_id": "episodic_memory",
7034 "morphology_scalar": 1,
7035 "postSynapticCurrent_multiplier": 1.0,
7036 })];
7037 manager
7038 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
7039 .unwrap();
7040 manager
7041 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
7042 .unwrap();
7043 manager
7044 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
7045 .unwrap();
7046 manager
7047 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
7048 .unwrap();
7049
7050 let assoc_mapping = vec![serde_json::json!({
7051 "morphology_id": "associative_memory",
7052 "morphology_scalar": 1,
7053 "postSynapticCurrent_multiplier": 1.0,
7054 "plasticity_flag": true,
7055 "plasticity_constant": 1,
7056 "ltp_multiplier": 1,
7057 "ltd_multiplier": 1,
7058 "plasticity_window": 5,
7059 })];
7060 manager
7061 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping)
7062 .unwrap();
7063 manager
7064 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7065 .unwrap();
7066
7067 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7068 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7069 assert_eq!(
7070 upstream_m1.len(),
7071 1,
7072 "M1 should have only 1 upstream before refresh"
7073 );
7074 assert_eq!(
7075 upstream_m2.len(),
7076 2,
7077 "M2 should have 2 upstreams before refresh"
7078 );
7079
7080 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
7081 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
7082
7083 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7084 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7085 assert_eq!(
7086 upstream_m1.len(),
7087 2,
7088 "M1 should have 2 upstreams after refresh"
7089 );
7090 assert_eq!(
7091 upstream_m2.len(),
7092 2,
7093 "M2 should have 2 upstreams after refresh"
7094 );
7095 assert!(upstream_m1.contains(&a1_idx));
7096 assert!(upstream_m1.contains(&m2_idx));
7097 assert!(upstream_m2.contains(&a2_idx));
7098 assert!(upstream_m2.contains(&m1_idx));
7099
7100 {
7102 let mut npu_lock = dyn_npu.lock().unwrap();
7103 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
7104 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
7105 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
7106 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
7107 npu_lock.process_burst().expect("Burst processing failed");
7108 }
7109
7110 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7111 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7112 assert_eq!(
7113 upstream_m1.len(),
7114 2,
7115 "M1 should keep 2 upstreams after firing"
7116 );
7117 assert_eq!(
7118 upstream_m2.len(),
7119 2,
7120 "M2 should keep 2 upstreams after firing"
7121 );
7122 }
7123
7124 #[test]
7125 fn test_memory_twin_created_for_memory_mapping() {
7126 use crate::models::cortical_area::CorticalArea;
7127 use feagi_npu_burst_engine::backend::CPUBackend;
7128 use feagi_npu_burst_engine::TracingMutex;
7129 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7130 use feagi_npu_runtime::StdRuntime;
7131 use feagi_structures::genomic::cortical_area::{
7132 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7133 MemoryCorticalType,
7134 };
7135 use std::sync::Arc;
7136
7137 let runtime = StdRuntime;
7138 let backend = CPUBackend::new();
7139 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7140 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7141 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7142 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7143
7144 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
7145 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
7146
7147 let src_area = CorticalArea::new(
7148 src_id,
7149 0,
7150 "Source Area".to_string(),
7151 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7152 (0, 0, 0).into(),
7153 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7154 )
7155 .unwrap();
7156 let mut dst_area = CorticalArea::new(
7157 dst_id,
7158 0,
7159 "Memory Area".to_string(),
7160 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7161 (0, 0, 0).into(),
7162 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7163 )
7164 .unwrap();
7165 dst_area
7166 .properties
7167 .insert("is_mem_type".to_string(), serde_json::json!(true));
7168 dst_area
7169 .properties
7170 .insert("temporal_depth".to_string(), serde_json::json!(1));
7171
7172 manager.add_cortical_area(src_area).unwrap();
7173 manager.add_cortical_area(dst_area).unwrap();
7174
7175 let mapping_data = vec![serde_json::json!({
7176 "morphology_id": "episodic_memory",
7177 "morphology_scalar": 1,
7178 "postSynapticCurrent_multiplier": 1.0,
7179 })];
7180 manager
7181 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7182 .unwrap();
7183 manager
7184 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7185 .unwrap();
7186
7187 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7188 let twin_map = memory_area
7189 .properties
7190 .get("memory_twin_areas")
7191 .and_then(|v| v.as_object())
7192 .expect("memory_twin_areas should be set");
7193 let twin_id_str = twin_map
7194 .get(&src_id.as_base_64())
7195 .and_then(|v| v.as_str())
7196 .expect("Missing twin entry for upstream area");
7197 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7198 let mapping = memory_area
7199 .properties
7200 .get("cortical_mapping_dst")
7201 .and_then(|v| v.as_object())
7202 .and_then(|map| map.get(&twin_id.as_base_64()))
7203 .and_then(|v| v.as_array())
7204 .expect("Missing memory replay mapping for twin area");
7205 let uses_replay = mapping.iter().any(|rule| {
7206 rule.get("morphology_id")
7207 .and_then(|v| v.as_str())
7208 .is_some_and(|id| id == "memory_replay")
7209 });
7210 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7211
7212 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7213 assert!(matches!(
7214 twin_area.cortical_type,
7215 CorticalAreaType::Custom(_)
7216 ));
7217 assert_eq!(
7218 twin_area
7219 .properties
7220 .get("memory_twin_of")
7221 .and_then(|v| v.as_str()),
7222 Some(src_id.as_base_64().as_str())
7223 );
7224 assert_eq!(
7225 twin_area
7226 .properties
7227 .get("memory_twin_for")
7228 .and_then(|v| v.as_str()),
7229 Some(dst_id.as_base_64().as_str())
7230 );
7231 }
7232
7233 #[test]
7234 fn test_associative_memory_between_memory_areas_creates_synapses() {
7235 use crate::models::cortical_area::CorticalArea;
7236 use feagi_npu_burst_engine::backend::CPUBackend;
7237 use feagi_npu_burst_engine::TracingMutex;
7238 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7239 use feagi_npu_runtime::StdRuntime;
7240 use feagi_structures::genomic::cortical_area::{
7241 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7242 };
7243 use std::sync::Arc;
7244
7245 let runtime = StdRuntime;
7246 let backend = CPUBackend::new();
7247 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7248 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7249 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7250 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7251
7252 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
7253 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
7254
7255 let mut m1_area = CorticalArea::new(
7256 m1_id,
7257 0,
7258 "Memory M1".to_string(),
7259 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7260 (0, 0, 0).into(),
7261 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7262 )
7263 .unwrap();
7264 m1_area
7265 .properties
7266 .insert("is_mem_type".to_string(), serde_json::json!(true));
7267 m1_area
7268 .properties
7269 .insert("temporal_depth".to_string(), serde_json::json!(1));
7270
7271 let mut m2_area = CorticalArea::new(
7272 m2_id,
7273 0,
7274 "Memory M2".to_string(),
7275 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7276 (0, 0, 0).into(),
7277 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7278 )
7279 .unwrap();
7280 m2_area
7281 .properties
7282 .insert("is_mem_type".to_string(), serde_json::json!(true));
7283 m2_area
7284 .properties
7285 .insert("temporal_depth".to_string(), serde_json::json!(1));
7286
7287 manager.add_cortical_area(m1_area).unwrap();
7288 manager.add_cortical_area(m2_area).unwrap();
7289
7290 manager
7291 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7292 .unwrap();
7293 manager
7294 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7295 .unwrap();
7296
7297 let mapping_data = vec![serde_json::json!({
7298 "morphology_id": "associative_memory",
7299 "morphology_scalar": 1,
7300 "postSynapticCurrent_multiplier": 1.0,
7301 "plasticity_flag": true,
7302 "plasticity_constant": 1,
7303 "ltp_multiplier": 1,
7304 "ltd_multiplier": 1,
7305 "plasticity_window": 5,
7306 })];
7307 manager
7308 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
7309 .unwrap();
7310 let created = manager
7311 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7312 .unwrap();
7313 assert!(
7314 created > 0,
7315 "Expected associative memory mapping between memory areas to create synapses"
7316 );
7317 }
7318
7319 #[test]
7320 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7321 use crate::models::cortical_area::CorticalArea;
7322 use feagi_npu_burst_engine::backend::CPUBackend;
7323 use feagi_npu_burst_engine::TracingMutex;
7324 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7325 use feagi_npu_runtime::StdRuntime;
7326 use feagi_structures::genomic::cortical_area::{
7327 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7328 MemoryCorticalType,
7329 };
7330 use std::sync::Arc;
7331
7332 let runtime = StdRuntime;
7333 let backend = CPUBackend::new();
7334 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7335 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7336 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7337 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7338
7339 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7340 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7341
7342 let src_area = CorticalArea::new(
7343 src_id,
7344 0,
7345 "Source Area".to_string(),
7346 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7347 (0, 0, 0).into(),
7348 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7349 )
7350 .unwrap();
7351 let mut mem_area = CorticalArea::new(
7352 mem_id,
7353 0,
7354 "Memory Area".to_string(),
7355 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7356 (0, 0, 0).into(),
7357 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7358 )
7359 .unwrap();
7360 mem_area
7361 .properties
7362 .insert("is_mem_type".to_string(), serde_json::json!(true));
7363 mem_area
7364 .properties
7365 .insert("temporal_depth".to_string(), serde_json::json!(1));
7366
7367 manager.add_cortical_area(src_area).unwrap();
7368 manager.add_cortical_area(mem_area).unwrap();
7369
7370 let twin_id = manager
7371 .build_memory_twin_id(&mem_id, &src_id)
7372 .expect("Failed to build twin id");
7373 let twin_area = CorticalArea::new(
7374 twin_id,
7375 0,
7376 "Source Area_twin".to_string(),
7377 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7378 (0, 0, 0).into(),
7379 CorticalAreaType::Custom(
7380 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7381 ),
7382 )
7383 .unwrap();
7384 manager.add_cortical_area(twin_area).unwrap();
7385
7386 let repaired = manager
7387 .ensure_memory_twin_area(&mem_id, &src_id)
7388 .expect("Failed to repair twin");
7389 assert_eq!(repaired, twin_id);
7390
7391 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
7392 let twin_map = mem_area
7393 .properties
7394 .get("memory_twin_areas")
7395 .and_then(|v| v.as_object())
7396 .expect("memory_twin_areas should be set");
7397 let twin_id_str = twin_map
7398 .get(&src_id.as_base_64())
7399 .and_then(|v| v.as_str())
7400 .expect("Missing twin entry for upstream area");
7401 assert_eq!(twin_id_str, twin_id.as_base_64());
7402
7403 let replay_map = mem_area
7404 .properties
7405 .get("cortical_mapping_dst")
7406 .and_then(|v| v.as_object())
7407 .and_then(|map| map.get(&twin_id.as_base_64()))
7408 .and_then(|v| v.as_array())
7409 .expect("Missing memory replay mapping for twin area");
7410 let uses_replay = replay_map.iter().any(|rule| {
7411 rule.get("morphology_id")
7412 .and_then(|v| v.as_str())
7413 .is_some_and(|id| id == "memory_replay")
7414 });
7415 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7416
7417 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7418 assert_eq!(
7419 twin_area
7420 .properties
7421 .get("memory_twin_of")
7422 .and_then(|v| v.as_str()),
7423 Some(src_id.as_base_64().as_str())
7424 );
7425 assert_eq!(
7426 twin_area
7427 .properties
7428 .get("memory_twin_for")
7429 .and_then(|v| v.as_str()),
7430 Some(mem_id.as_base_64().as_str())
7431 );
7432 }
7433}