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 new() -> Self {
187 Self {
188 cortical_areas: HashMap::new(),
189 cortical_id_to_idx: HashMap::new(),
190 cortical_idx_to_id: HashMap::new(),
191 next_cortical_idx: 3, brain_regions: BrainRegionHierarchy::new(),
194 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
195 config: ConnectomeConfig::default(),
196 npu: None,
197 #[cfg(feature = "plasticity")]
198 plasticity_executor: None,
199 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
200 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
201 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
202 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
203 initialized: false,
204 last_fatigue_calculation: Arc::new(Mutex::new(
205 std::time::Instant::now() - std::time::Duration::from_secs(10),
206 )), }
208 }
209
210 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
227 Arc::clone(&*INSTANCE)
230 }
231
232 pub fn new_for_testing() -> Self {
260 Self {
261 cortical_areas: HashMap::new(),
262 cortical_id_to_idx: HashMap::new(),
263 cortical_idx_to_id: HashMap::new(),
264 next_cortical_idx: 0,
265 brain_regions: BrainRegionHierarchy::new(),
266 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
267 config: ConnectomeConfig::default(),
268 npu: None,
269 #[cfg(feature = "plasticity")]
270 plasticity_executor: None,
271 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
272 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
273 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
274 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
275 initialized: false,
276 last_fatigue_calculation: Arc::new(Mutex::new(
277 std::time::Instant::now() - std::time::Duration::from_secs(10),
278 )),
279 }
280 }
281
282 pub fn new_for_testing_with_npu(
298 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
299 ) -> Self {
300 Self {
301 cortical_areas: HashMap::new(),
302 cortical_id_to_idx: HashMap::new(),
303 cortical_idx_to_id: HashMap::new(),
304 next_cortical_idx: 3,
305 brain_regions: BrainRegionHierarchy::new(),
306 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
307 config: ConnectomeConfig::default(),
308 npu: Some(npu),
309 #[cfg(feature = "plasticity")]
310 plasticity_executor: None,
311 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
312 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
313 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
314 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
315 initialized: false,
316 last_fatigue_calculation: Arc::new(Mutex::new(
317 std::time::Instant::now() - std::time::Duration::from_secs(10),
318 )),
319 }
320 }
321
322 pub fn setup_core_morphologies_for_testing(&mut self) {
332 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
333 }
334
335 #[cfg(test)]
344 pub fn reset_for_testing() {
345 let mut instance = INSTANCE.write();
346 *instance = Self::new();
347 }
348
349 fn update_state_hashes(
355 &self,
356 brain_regions: Option<u64>,
357 cortical_areas: Option<u64>,
358 brain_geometry: Option<u64>,
359 morphologies: Option<u64>,
360 cortical_mappings: Option<u64>,
361 ) {
362 let state_manager = StateManager::instance();
363 let state_manager = state_manager.read();
364 if let Some(value) = brain_regions {
365 state_manager.set_brain_regions_hash(value);
366 }
367 if let Some(value) = cortical_areas {
368 state_manager.set_cortical_areas_hash(value);
369 }
370 if let Some(value) = brain_geometry {
371 state_manager.set_brain_geometry_hash(value);
372 }
373 if let Some(value) = morphologies {
374 state_manager.set_morphologies_hash(value);
375 }
376 if let Some(value) = cortical_mappings {
377 state_manager.set_cortical_mappings_hash(value);
378 }
379 }
380
381 fn refresh_brain_regions_hash(&self) {
383 let hash = self.compute_brain_regions_hash();
384 self.update_state_hashes(Some(hash), None, None, None, None);
385 }
386
387 #[allow(dead_code)]
388 fn refresh_cortical_areas_hash(&self) {
390 let hash = self.compute_cortical_areas_hash();
391 self.update_state_hashes(None, Some(hash), None, None, None);
392 }
393
394 #[allow(dead_code)]
395 fn refresh_brain_geometry_hash(&self) {
397 let hash = self.compute_brain_geometry_hash();
398 self.update_state_hashes(None, None, Some(hash), None, None);
399 }
400
401 fn refresh_morphologies_hash(&self) {
403 let hash = self.compute_morphologies_hash();
404 self.update_state_hashes(None, None, None, Some(hash), None);
405 }
406
407 fn refresh_cortical_mappings_hash(&self) {
409 let hash = self.compute_cortical_mappings_hash();
410 self.update_state_hashes(None, None, None, None, Some(hash));
411 }
412
413 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
415 let cortical_hash = if properties_changed {
416 Some(self.compute_cortical_areas_hash())
417 } else {
418 None
419 };
420 let geometry_hash = if geometry_changed {
421 Some(self.compute_brain_geometry_hash())
422 } else {
423 None
424 };
425 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
426 }
427
428 fn compute_brain_regions_hash(&self) -> u64 {
430 let mut hasher = Xxh64::new(DATA_HASH_SEED);
431 let mut region_ids: Vec<String> = self
432 .brain_regions
433 .get_all_region_ids()
434 .into_iter()
435 .cloned()
436 .collect();
437 region_ids.sort();
438
439 for region_id in region_ids {
440 let Some(region) = self.brain_regions.get_region(®ion_id) else {
441 continue;
442 };
443 Self::hash_str(&mut hasher, ®ion_id);
444 Self::hash_str(&mut hasher, ®ion.name);
445 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
446 let parent_id = self.brain_regions.get_parent(®ion_id);
447 match parent_id {
448 Some(parent) => Self::hash_str(&mut hasher, parent),
449 None => Self::hash_str(&mut hasher, "null"),
450 }
451
452 let mut cortical_ids: Vec<String> = region
453 .cortical_areas
454 .iter()
455 .map(|id| id.as_base_64())
456 .collect();
457 cortical_ids.sort();
458 for cortical_id in cortical_ids {
459 Self::hash_str(&mut hasher, &cortical_id);
460 }
461
462 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
463 }
464
465 hasher.finish() & HASH_SAFE_MASK
466 }
467
468 fn compute_cortical_areas_hash(&self) -> u64 {
470 let mut hasher = Xxh64::new(DATA_HASH_SEED);
471 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
472 areas.sort_by_key(|area| area.cortical_id.as_base_64());
473
474 for area in areas {
475 let cortical_id = area.cortical_id.as_base_64();
476 Self::hash_str(&mut hasher, &cortical_id);
477 hasher.write_u32(area.cortical_idx);
478 Self::hash_str(&mut hasher, &area.name);
479 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
480
481 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
482 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
483 }
484
485 hasher.finish() & HASH_SAFE_MASK
486 }
487
488 fn compute_brain_geometry_hash(&self) -> u64 {
490 let mut hasher = Xxh64::new(DATA_HASH_SEED);
491 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
492 areas.sort_by_key(|area| area.cortical_id.as_base_64());
493
494 for area in areas {
495 let cortical_id = area.cortical_id.as_base_64();
496 Self::hash_str(&mut hasher, &cortical_id);
497
498 Self::hash_i32(&mut hasher, area.position.x);
499 Self::hash_i32(&mut hasher, area.position.y);
500 Self::hash_i32(&mut hasher, area.position.z);
501
502 Self::hash_u32(&mut hasher, area.dimensions.width);
503 Self::hash_u32(&mut hasher, area.dimensions.height);
504 Self::hash_u32(&mut hasher, area.dimensions.depth);
505
506 let coord_2d = area
507 .properties
508 .get("coordinate_2d")
509 .or_else(|| area.properties.get("coordinates_2d"));
510 match coord_2d {
511 Some(value) => Self::hash_json_value(&mut hasher, value),
512 None => Self::hash_str(&mut hasher, "null"),
513 }
514 }
515
516 hasher.finish() & HASH_SAFE_MASK
517 }
518
519 fn compute_morphologies_hash(&self) -> u64 {
521 let mut hasher = Xxh64::new(DATA_HASH_SEED);
522 let mut morphology_ids = self.morphology_registry.morphology_ids();
523 morphology_ids.sort();
524
525 for morphology_id in morphology_ids {
526 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
527 Self::hash_str(&mut hasher, &morphology_id);
528 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
529 Self::hash_str(&mut hasher, &morphology.class);
530 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
531 Self::hash_json_value(&mut hasher, &value);
532 }
533 }
534 }
535
536 hasher.finish() & HASH_SAFE_MASK
537 }
538
539 fn compute_cortical_mappings_hash(&self) -> u64 {
541 let mut hasher = Xxh64::new(DATA_HASH_SEED);
542 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
543 areas.sort_by_key(|area| area.cortical_id.as_base_64());
544
545 for area in areas {
546 let cortical_id = area.cortical_id.as_base_64();
547 Self::hash_str(&mut hasher, &cortical_id);
548 if let Some(serde_json::Value::Object(map)) =
549 area.properties.get("cortical_mapping_dst")
550 {
551 let mut dest_ids: Vec<&String> = map.keys().collect();
552 dest_ids.sort();
553 for dest_id in dest_ids {
554 Self::hash_str(&mut hasher, dest_id);
555 if let Some(value) = map.get(dest_id) {
556 Self::hash_json_value(&mut hasher, value);
557 }
558 }
559 } else {
560 Self::hash_str(&mut hasher, "null");
561 }
562 }
563
564 hasher.finish() & HASH_SAFE_MASK
565 }
566
567 fn hash_str(hasher: &mut Xxh64, value: &str) {
569 hasher.write(value.as_bytes());
570 hasher.write_u8(0);
571 }
572
573 fn hash_i32(hasher: &mut Xxh64, value: i32) {
575 hasher.write(&value.to_le_bytes());
576 }
577
578 fn hash_u32(hasher: &mut Xxh64, value: u32) {
580 hasher.write(&value.to_le_bytes());
581 }
582
583 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
585 match value {
586 serde_json::Value::Null => {
587 hasher.write_u8(0);
588 }
589 serde_json::Value::Bool(val) => {
590 hasher.write_u8(1);
591 hasher.write_u8(*val as u8);
592 }
593 serde_json::Value::Number(num) => {
594 hasher.write_u8(2);
595 Self::hash_str(hasher, &num.to_string());
596 }
597 serde_json::Value::String(val) => {
598 hasher.write_u8(3);
599 Self::hash_str(hasher, val);
600 }
601 serde_json::Value::Array(items) => {
602 hasher.write_u8(4);
603 for item in items {
604 Self::hash_json_value(hasher, item);
605 }
606 }
607 serde_json::Value::Object(map) => {
608 hasher.write_u8(5);
609 let mut keys: Vec<&String> = map.keys().collect();
610 keys.sort();
611 for key in keys {
612 Self::hash_str(hasher, key);
613 if let Some(val) = map.get(key) {
614 Self::hash_json_value(hasher, val);
615 }
616 }
617 }
618 }
619 }
620
621 fn hash_properties_filtered(
623 hasher: &mut Xxh64,
624 properties: &HashMap<String, serde_json::Value>,
625 excluded_keys: &[&str],
626 ) {
627 let mut keys: Vec<&String> = properties.keys().collect();
628 keys.sort();
629 for key in keys {
630 if excluded_keys.contains(&key.as_str()) {
631 continue;
632 }
633 Self::hash_str(hasher, key);
634 if let Some(value) = properties.get(key) {
635 Self::hash_json_value(hasher, value);
636 }
637 }
638 }
639
640 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
661 if self.cortical_areas.contains_key(&area.cortical_id) {
663 return Err(BduError::InvalidArea(format!(
664 "Cortical area {} already exists",
665 area.cortical_id
666 )));
667 }
668
669 use feagi_structures::genomic::cortical_area::CoreCorticalType;
672
673 let death_id = CoreCorticalType::Death.to_cortical_id();
674 let power_id = CoreCorticalType::Power.to_cortical_id();
675 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
676
677 let is_death_area = area.cortical_id == death_id;
678 let is_power_area = area.cortical_id == power_id;
679 let is_fatigue_area = area.cortical_id == fatigue_id;
680
681 if is_death_area {
682 trace!(
683 target: "feagi-bdu",
684 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
685 area.cortical_id
686 );
687 area.cortical_idx = 0;
688 } else if is_power_area {
689 trace!(
690 target: "feagi-bdu",
691 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
692 area.cortical_id
693 );
694 area.cortical_idx = 1;
695 } else if is_fatigue_area {
696 trace!(
697 target: "feagi-bdu",
698 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
699 area.cortical_id
700 );
701 area.cortical_idx = 2;
702 } else {
703 if area.cortical_idx == 0 {
705 area.cortical_idx = self.next_cortical_idx;
706 self.next_cortical_idx += 1;
707 trace!(
708 target: "feagi-bdu",
709 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be ≥3)",
710 area.cortical_idx,
711 area.cortical_id.as_base_64()
712 );
713 } else {
714 if area.cortical_idx == 0 || area.cortical_idx == 1 || area.cortical_idx == 2 {
716 warn!(
717 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
718 area.cortical_id, area.cortical_idx);
719 area.cortical_idx = self.next_cortical_idx;
720 self.next_cortical_idx += 1;
721 info!(
722 " Reassigned '{}' to cortical_idx={}",
723 area.cortical_id, area.cortical_idx
724 );
725 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
726 return Err(BduError::InvalidArea(format!(
727 "Cortical index {} is already in use",
728 area.cortical_idx
729 )));
730 }
731
732 if area.cortical_idx >= self.next_cortical_idx {
734 self.next_cortical_idx = area.cortical_idx + 1;
735 }
736 }
737 }
738
739 let cortical_id = area.cortical_id;
740 let cortical_idx = area.cortical_idx;
741
742 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
744 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
745
746 area.properties
748 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
749
750 let parent_region_id = area
759 .properties
760 .get("parent_region_id")
761 .and_then(|v| v.as_str())
762 .map(|s| s.to_string());
763
764 self.cortical_areas.insert(cortical_id, area);
766
767 if let Some(region_id) = parent_region_id {
769 let region = self
770 .brain_regions
771 .get_region_mut(®ion_id)
772 .ok_or_else(|| {
773 BduError::InvalidArea(format!(
774 "Unknown parent_region_id '{}' for cortical area {}",
775 region_id,
776 cortical_id.as_base_64()
777 ))
778 })?;
779 region.add_area(cortical_id);
780 }
781
782 {
785 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
786 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
787 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
788 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
789 }
790 if let Some(state_manager) = StateManager::instance().try_read() {
792 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
793 }
794
795 if let Some(ref npu) = self.npu {
799 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
800 if let Ok(mut npu_lock) = npu.lock() {
801 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
802 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
803 trace!(
804 target: "feagi-bdu",
805 "Registered cortical area idx={} -> '{}' in NPU",
806 cortical_idx,
807 cortical_id.as_base_64()
808 );
809 }
810 }
811
812 self.sync_cortical_area_flags_to_npu()?;
814
815 self.initialized = true;
816
817 self.refresh_cortical_area_hashes(true, true);
818 self.refresh_brain_regions_hash();
819
820 Ok(cortical_idx)
821 }
822
823 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
838 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
839 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
840 })?;
841
842 self.cortical_id_to_idx.remove(cortical_id);
844 self.cortical_idx_to_id.remove(&area.cortical_idx);
845
846 self.refresh_cortical_area_hashes(true, true);
847 Ok(())
848 }
849
850 pub fn rename_cortical_area_id(
854 &mut self,
855 old_id: &CorticalID,
856 new_id: CorticalID,
857 new_cortical_type: CorticalAreaType,
858 ) -> BduResult<()> {
859 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
860 }
861
862 pub fn rename_cortical_area_id_with_options(
864 &mut self,
865 old_id: &CorticalID,
866 new_id: CorticalID,
867 new_cortical_type: CorticalAreaType,
868 update_npu_registry: bool,
869 ) -> BduResult<()> {
870 if !self.cortical_areas.contains_key(old_id) {
871 return Err(BduError::InvalidArea(format!(
872 "Cortical area {} does not exist",
873 old_id
874 )));
875 }
876 if self.cortical_areas.contains_key(&new_id) {
877 return Err(BduError::InvalidArea(format!(
878 "Cortical area {} already exists",
879 new_id
880 )));
881 }
882
883 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
884 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
885 })?;
886 let cortical_idx = area.cortical_idx;
887 area.cortical_id = new_id;
888 area.cortical_type = new_cortical_type;
889
890 self.cortical_areas.insert(new_id, area);
891 self.cortical_id_to_idx.remove(old_id);
892 self.cortical_id_to_idx.insert(new_id, cortical_idx);
893 self.cortical_idx_to_id.insert(cortical_idx, new_id);
894
895 {
897 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
898 if let Some(value) = neuron_cache.remove(old_id) {
899 neuron_cache.insert(new_id, value);
900 }
901 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
902 if let Some(value) = synapse_cache.remove(old_id) {
903 synapse_cache.insert(new_id, value);
904 }
905 }
906
907 self.brain_regions.rename_cortical_area_id(old_id, new_id);
909
910 let old_id_str = old_id.as_base_64();
912 let new_id_str = new_id.as_base_64();
913 for area in self.cortical_areas.values_mut() {
914 if let Some(mapping) = area
915 .properties
916 .get_mut("cortical_mapping_dst")
917 .and_then(|v| v.as_object_mut())
918 {
919 if let Some(value) = mapping.remove(&old_id_str) {
920 mapping.insert(new_id_str.clone(), value);
921 }
922 }
923 }
924
925 if update_npu_registry {
927 if let Some(ref npu) = self.npu {
928 if let Ok(mut npu_lock) = npu.lock() {
929 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
930 }
931 }
932 }
933
934 self.refresh_cortical_area_hashes(true, true);
935 self.refresh_brain_regions_hash();
936 self.refresh_cortical_mappings_hash();
937
938 Ok(())
939 }
940
941 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
943 self.cortical_areas.get(cortical_id)
944 }
945
946 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
948 self.cortical_areas.get_mut(cortical_id)
949 }
950
951 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
953 self.cortical_id_to_idx.get(cortical_id).copied()
954 }
955
956 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
968 self.brain_regions.find_region_containing_area(cortical_id)
969 }
970
971 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
982 use std::collections::HashSet;
983
984 let region_ids: Vec<String> = self
985 .brain_regions
986 .get_all_region_ids()
987 .into_iter()
988 .cloned()
989 .collect();
990
991 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
992 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
993
994 for rid in ®ion_ids {
996 inputs_by_region.insert(rid.clone(), HashSet::new());
997 outputs_by_region.insert(rid.clone(), HashSet::new());
998 }
999
1000 for (src_id, src_area) in &self.cortical_areas {
1001 let Some(dstmap) = src_area
1002 .properties
1003 .get("cortical_mapping_dst")
1004 .and_then(|v| v.as_object())
1005 else {
1006 continue;
1007 };
1008
1009 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1010 warn!(
1011 target: "feagi-bdu",
1012 "Skipping region IO for source area {} (not in any region)",
1013 src_id.as_base_64()
1014 );
1015 continue;
1016 };
1017
1018 for dst_id_str in dstmap.keys() {
1019 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1020 BduError::InvalidArea(format!(
1021 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1022 dst_id_str,
1023 src_id.as_base_64(),
1024 e
1025 ))
1026 })?;
1027
1028 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1029 else {
1030 warn!(
1031 target: "feagi-bdu",
1032 "Skipping region IO for destination area {} (not in any region)",
1033 dst_id.as_base_64()
1034 );
1035 continue;
1036 };
1037
1038 if src_region_id == dst_region_id {
1039 continue;
1040 }
1041
1042 outputs_by_region
1043 .entry(src_region_id.clone())
1044 .or_default()
1045 .insert(src_id.as_base_64());
1046 inputs_by_region
1047 .entry(dst_region_id.clone())
1048 .or_default()
1049 .insert(dst_id.as_base_64());
1050 }
1051 }
1052
1053 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1054 for rid in region_ids {
1055 let mut inputs: Vec<String> = inputs_by_region
1056 .remove(&rid)
1057 .unwrap_or_default()
1058 .into_iter()
1059 .collect();
1060 let mut outputs: Vec<String> = outputs_by_region
1061 .remove(&rid)
1062 .unwrap_or_default()
1063 .into_iter()
1064 .collect();
1065
1066 inputs.sort();
1067 outputs.sort();
1068
1069 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1070 BduError::InvalidArea(format!(
1071 "Unable to recompute region IO: region '{}' not found in hierarchy",
1072 rid
1073 ))
1074 })?;
1075
1076 if inputs.is_empty() {
1077 region.properties.remove("inputs");
1078 } else {
1079 region
1080 .properties
1081 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1082 }
1083
1084 if outputs.is_empty() {
1085 region.properties.remove("outputs");
1086 } else {
1087 region
1088 .properties
1089 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1090 }
1091
1092 computed.insert(rid, (inputs, outputs));
1093 }
1094
1095 self.refresh_brain_regions_hash();
1096
1097 Ok(computed)
1098 }
1099
1100 pub fn get_root_region_id(&self) -> Option<String> {
1106 self.brain_regions.get_root_region_id()
1107 }
1108
1109 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1111 self.cortical_idx_to_id.get(&cortical_idx)
1112 }
1113
1114 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1117 self.cortical_idx_to_id
1118 .iter()
1119 .map(|(idx, id)| (*idx, id.as_base_64()))
1120 .collect()
1121 }
1122
1123 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1128 let mut granularities = ahash::AHashMap::new();
1129 for (cortical_id, area) in &self.cortical_areas {
1130 let cortical_idx = self
1131 .cortical_id_to_idx
1132 .get(cortical_id)
1133 .copied()
1134 .unwrap_or(0);
1135
1136 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1139 if let Some(arr) = granularity_json.as_array() {
1140 if arr.len() == 3 {
1141 let x_opt = arr[0]
1142 .as_u64()
1143 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1144 let y_opt = arr[1]
1145 .as_u64()
1146 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1147 let z_opt = arr[2]
1148 .as_u64()
1149 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1150
1151 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1152 let granularity = (x as u32, y as u32, z as u32);
1153 if granularity != (1, 1, 1) {
1155 granularities.insert(cortical_idx, granularity);
1156 }
1157 }
1158 }
1159 }
1160 }
1161 }
1162 granularities
1163 }
1164
1165 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1167 self.cortical_areas.keys().collect()
1168 }
1169
1170 pub fn get_cortical_area_count(&self) -> usize {
1172 self.cortical_areas.len()
1173 }
1174
1175 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1189 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1190 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1191 if let Some(upstream_array) = upstream_prop.as_array() {
1192 return upstream_array
1193 .iter()
1194 .filter_map(|v| v.as_u64().map(|n| n as u32))
1195 .collect();
1196 }
1197 }
1198
1199 warn!(target: "feagi-bdu",
1201 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1202 target_cortical_id.as_base_64()
1203 );
1204 }
1205
1206 Vec::new()
1207 }
1208
1209 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1211 upstream
1212 .iter()
1213 .filter_map(|idx| {
1214 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1215 let area = self.cortical_areas.get(cortical_id)?;
1216 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1217 None
1218 } else {
1219 Some(*idx)
1220 }
1221 })
1222 .collect()
1223 }
1224
1225 pub fn refresh_upstream_cortical_areas_from_mappings(
1230 &mut self,
1231 target_cortical_id: &CorticalID,
1232 ) -> Vec<u32> {
1233 use std::collections::HashSet;
1234 let target_id_str = target_cortical_id.as_base_64();
1235 let mut upstream_idxs = HashSet::new();
1236 for (src_id, src_area) in &self.cortical_areas {
1237 if src_id == target_cortical_id {
1238 continue;
1239 }
1240 if let Some(mapping) = src_area
1241 .properties
1242 .get("cortical_mapping_dst")
1243 .and_then(|v| v.as_object())
1244 {
1245 if mapping.contains_key(&target_id_str) {
1246 upstream_idxs.insert(src_area.cortical_idx);
1247 }
1248 }
1249 }
1250
1251 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1252 upstream_list.sort_unstable();
1253
1254 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1255 target_area.properties.insert(
1256 "upstream_cortical_areas".to_string(),
1257 serde_json::json!(upstream_list),
1258 );
1259 }
1260
1261 self.get_upstream_cortical_areas(target_cortical_id)
1262 }
1263
1264 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1274 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1275 let upstream_array = area
1276 .properties
1277 .entry("upstream_cortical_areas".to_string())
1278 .or_insert_with(|| serde_json::json!([]));
1279
1280 if let Some(arr) = upstream_array.as_array_mut() {
1281 let src_value = serde_json::json!(src_cortical_idx);
1282 if !arr.contains(&src_value) {
1283 arr.push(src_value);
1284 info!(target: "feagi-bdu",
1285 "✓ Added upstream area idx={} to cortical area '{}'",
1286 src_cortical_idx, target_cortical_id.as_base_64()
1287 );
1288 }
1289 }
1290 }
1291 }
1292
1293 pub fn get_memory_twin_for_upstream_idx(
1295 &self,
1296 memory_area_idx: u32,
1297 upstream_idx: u32,
1298 ) -> Option<CorticalID> {
1299 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1300 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1301 let area = self.cortical_areas.get(memory_id)?;
1302 let mapping = area
1303 .properties
1304 .get("memory_twin_areas")
1305 .and_then(|v| v.as_object())?;
1306 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1307 CorticalID::try_from_base_64(twin_b64).ok()
1308 }
1309
1310 pub fn ensure_memory_twin_area(
1312 &mut self,
1313 memory_area_id: &CorticalID,
1314 upstream_area_id: &CorticalID,
1315 ) -> BduResult<CorticalID> {
1316 use crate::models::CorticalAreaExt;
1317
1318 let register_replay_mapping = |manager: &mut ConnectomeManager,
1319 twin_id: &CorticalID|
1320 -> BduResult<()> {
1321 let Some(npu) = manager.npu.as_ref() else {
1322 return Ok(());
1323 };
1324 let memory_area_idx =
1325 *manager
1326 .cortical_id_to_idx
1327 .get(memory_area_id)
1328 .ok_or_else(|| {
1329 BduError::InvalidArea(format!(
1330 "Memory area idx missing for {}",
1331 memory_area_id.as_base_64()
1332 ))
1333 })?;
1334 let upstream_area_idx = *manager
1335 .cortical_id_to_idx
1336 .get(upstream_area_id)
1337 .ok_or_else(|| {
1338 BduError::InvalidArea(format!(
1339 "Upstream area idx missing for {}",
1340 upstream_area_id.as_base_64()
1341 ))
1342 })?;
1343 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1344 BduError::InvalidArea(format!(
1345 "Twin area idx missing for {}",
1346 twin_id.as_base_64()
1347 ))
1348 })?;
1349 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1350 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1351 })?;
1352 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1353 if let Ok(mut npu_lock) = npu.lock() {
1354 npu_lock.register_memory_twin_mapping(
1355 memory_area_idx,
1356 upstream_area_idx,
1357 twin_area_idx,
1358 potential,
1359 );
1360 }
1361 Ok(())
1362 };
1363
1364 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1365 BduError::InvalidArea(format!(
1366 "Memory area {} not found",
1367 memory_area_id.as_base_64()
1368 ))
1369 })?;
1370 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1371 BduError::InvalidArea(format!(
1372 "Upstream area {} not found",
1373 upstream_area_id.as_base_64()
1374 ))
1375 })?;
1376
1377 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1378 return Err(BduError::InvalidArea(format!(
1379 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1380 upstream_area_id.as_base_64()
1381 )));
1382 }
1383
1384 if let Some(existing) = memory_area
1385 .properties
1386 .get("memory_twin_areas")
1387 .and_then(|v| v.as_object())
1388 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1389 .and_then(|v| v.as_str())
1390 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1391 {
1392 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1393 register_replay_mapping(self, &existing)?;
1394 self.refresh_cortical_mappings_hash();
1395 return Ok(existing);
1396 }
1397
1398 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1399 if self.cortical_areas.contains_key(&twin_id) {
1400 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1401 let expected_source = upstream_area_id.as_base_64();
1402 let expected_target = memory_area_id.as_base_64();
1403 let existing_source = existing
1404 .properties
1405 .get("memory_twin_of")
1406 .and_then(|v| v.as_str());
1407 let existing_target = existing
1408 .properties
1409 .get("memory_twin_for")
1410 .and_then(|v| v.as_str());
1411 if existing_source != Some(expected_source.as_str())
1412 || existing_target != Some(expected_target.as_str())
1413 {
1414 warn!(
1415 target: "feagi-bdu",
1416 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1417 upstream_area_id.as_base_64(),
1418 memory_area_id.as_base_64()
1419 );
1420 existing.properties.insert(
1421 "memory_twin_of".to_string(),
1422 serde_json::json!(expected_source),
1423 );
1424 existing.properties.insert(
1425 "memory_twin_for".to_string(),
1426 serde_json::json!(expected_target),
1427 );
1428 }
1429 }
1430 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1431 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1432 register_replay_mapping(self, &twin_id)?;
1433 self.refresh_cortical_mappings_hash();
1434 return Ok(twin_id);
1435 }
1436
1437 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1438 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1439 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1440 let mut twin_area = CorticalArea::new(
1441 twin_id,
1442 0,
1443 twin_name,
1444 upstream_area.dimensions,
1445 twin_position,
1446 twin_type,
1447 )?;
1448 twin_area.properties = self.build_memory_twin_properties(
1449 memory_area,
1450 upstream_area,
1451 memory_area_id,
1452 upstream_area_id,
1453 );
1454
1455 let _twin_idx = self.add_cortical_area(twin_area)?;
1456 let _ = self.create_neurons_for_area(&twin_id);
1457
1458 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1459 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1460 register_replay_mapping(self, &twin_id)?;
1461 self.refresh_cortical_mappings_hash();
1462 Ok(twin_id)
1463 }
1464
1465 fn build_memory_twin_position(
1466 &self,
1467 memory_area: &CorticalArea,
1468 upstream_area: &CorticalArea,
1469 ) -> GenomeCoordinate3D {
1470 let memory_parent = memory_area
1471 .properties
1472 .get("parent_region_id")
1473 .and_then(|v| v.as_str());
1474 let upstream_parent = upstream_area
1475 .properties
1476 .get("parent_region_id")
1477 .and_then(|v| v.as_str());
1478 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1479
1480 if !same_region {
1481 return GenomeCoordinate3D::new(
1482 memory_area.position.x + 20,
1483 memory_area.position.y,
1484 memory_area.position.z,
1485 );
1486 }
1487
1488 let width = upstream_area.dimensions.width as f32;
1489 let margin = (width * 0.25).ceil() as i32;
1490 let offset = upstream_area.dimensions.width as i32 + margin;
1491 GenomeCoordinate3D::new(
1492 upstream_area.position.x + offset,
1493 upstream_area.position.y,
1494 upstream_area.position.z,
1495 )
1496 }
1497
1498 fn build_memory_twin_id(
1499 &self,
1500 memory_area_id: &CorticalID,
1501 upstream_area_id: &CorticalID,
1502 ) -> BduResult<CorticalID> {
1503 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1504 hasher.write(memory_area_id.as_base_64().as_bytes());
1505 hasher.write(upstream_area_id.as_base_64().as_bytes());
1506 hasher.write(b"memory_twin");
1507 let hash = hasher.finish();
1508 let mut bytes = hash.to_be_bytes();
1509 bytes[0] = b'c';
1510 CorticalID::try_from_bytes(&bytes)
1511 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1512 }
1513
1514 fn build_memory_twin_properties(
1515 &self,
1516 memory_area: &CorticalArea,
1517 upstream_area: &CorticalArea,
1518 memory_area_id: &CorticalID,
1519 upstream_area_id: &CorticalID,
1520 ) -> HashMap<String, serde_json::Value> {
1521 let mut props = upstream_area.properties.clone();
1522 props.remove("cortical_mapping_dst");
1523 props.remove("upstream_cortical_areas");
1524 props.remove("parent_region_id");
1525 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1526 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1527 props.insert(
1528 "memory_twin_of".to_string(),
1529 serde_json::json!(upstream_area_id.as_base_64()),
1530 );
1531 props.insert(
1532 "memory_twin_for".to_string(),
1533 serde_json::json!(memory_area_id.as_base_64()),
1534 );
1535 if let Some(parent_region_id) = memory_area
1536 .properties
1537 .get("parent_region_id")
1538 .and_then(|v| v.as_str())
1539 {
1540 props.insert(
1541 "parent_region_id".to_string(),
1542 serde_json::json!(parent_region_id),
1543 );
1544 }
1545 props
1546 }
1547
1548 fn set_memory_twin_mapping(
1549 &mut self,
1550 memory_area_id: &CorticalID,
1551 upstream_area_id: &CorticalID,
1552 twin_id: &CorticalID,
1553 ) {
1554 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1555 let mapping = memory_area
1556 .properties
1557 .entry("memory_twin_areas".to_string())
1558 .or_insert_with(|| serde_json::json!({}));
1559 if let Some(map) = mapping.as_object_mut() {
1560 map.insert(
1561 upstream_area_id.as_base_64(),
1562 serde_json::json!(twin_id.as_base_64()),
1563 );
1564 }
1565 }
1566 }
1567
1568 fn ensure_memory_replay_mapping(
1569 &mut self,
1570 memory_area_id: &CorticalID,
1571 twin_id: &CorticalID,
1572 ) -> BduResult<()> {
1573 if !self.morphology_registry.contains("memory_replay") {
1574 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1575 }
1576 self.refresh_morphologies_hash();
1577 let mapping_data = vec![serde_json::json!({
1578 "morphology_id": "memory_replay",
1579 "morphology_scalar": [1, 1, 1],
1580 "postSynapticCurrent_multiplier": 1,
1581 "plasticity_flag": false,
1582 "plasticity_constant": 0,
1583 "ltp_multiplier": 0,
1584 "ltd_multiplier": 0,
1585 "plasticity_window": 0,
1586 })];
1587 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1588 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1589 self.refresh_cortical_area_hashes(true, false);
1591 Ok(())
1592 }
1593
1594 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1604 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1605 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1606 if let Some(arr) = upstream_prop.as_array_mut() {
1607 let src_value = serde_json::json!(src_cortical_idx);
1608 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1609 arr.remove(pos);
1610 debug!(target: "feagi-bdu",
1611 "Removed upstream area idx={} from cortical area '{}'",
1612 src_cortical_idx, target_cortical_id.as_base_64()
1613 );
1614 }
1615 }
1616 }
1617 }
1618 }
1619
1620 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1622 self.cortical_areas.contains_key(cortical_id)
1623 }
1624
1625 pub fn is_initialized(&self) -> bool {
1627 self.initialized && !self.cortical_areas.is_empty()
1628 }
1629
1630 pub fn add_brain_region(
1636 &mut self,
1637 region: BrainRegion,
1638 parent_id: Option<String>,
1639 ) -> BduResult<()> {
1640 self.brain_regions.add_region(region, parent_id)?;
1641 self.refresh_brain_regions_hash();
1642 Ok(())
1643 }
1644
1645 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1647 self.brain_regions.remove_region(region_id)?;
1648 self.refresh_brain_regions_hash();
1649 Ok(())
1650 }
1651
1652 pub fn change_brain_region_parent(
1654 &mut self,
1655 region_id: &str,
1656 new_parent_id: &str,
1657 ) -> BduResult<()> {
1658 self.brain_regions.change_parent(region_id, new_parent_id)?;
1659 self.refresh_brain_regions_hash();
1660 Ok(())
1661 }
1662
1663 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1665 self.brain_regions.get_region(region_id)
1666 }
1667
1668 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1670 self.brain_regions.get_region_mut(region_id)
1671 }
1672
1673 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1675 self.brain_regions.get_all_region_ids()
1676 }
1677
1678 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1680 &self.brain_regions
1681 }
1682
1683 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1689 &self.morphology_registry
1690 }
1691
1692 pub fn get_morphology_count(&self) -> usize {
1694 self.morphology_registry.count()
1695 }
1696
1697 pub fn upsert_morphology(
1702 &mut self,
1703 morphology_id: String,
1704 morphology: feagi_evolutionary::Morphology,
1705 ) {
1706 self.morphology_registry
1707 .add_morphology(morphology_id, morphology);
1708 self.refresh_morphologies_hash();
1709 }
1710
1711 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1717 let removed = self.morphology_registry.remove_morphology(morphology_id);
1718 if removed {
1719 self.refresh_morphologies_hash();
1720 }
1721 removed
1722 }
1723
1724 pub fn update_cortical_mapping(
1742 &mut self,
1743 src_area_id: &CorticalID,
1744 dst_area_id: &CorticalID,
1745 mapping_data: Vec<serde_json::Value>,
1746 ) -> BduResult<()> {
1747 use tracing::info;
1748
1749 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1750
1751 let mapping_has_bidirectional_stdp = |rules: &[serde_json::Value]| -> bool {
1753 for rule in rules {
1754 if let Some(obj) = rule.as_object() {
1755 if let Some(morphology_id) = obj.get("morphology_id").and_then(|v| v.as_str()) {
1756 if morphology_id == "associative_memory" {
1757 return true;
1758 }
1759 }
1760 }
1761 }
1762 false
1763 };
1764
1765 let requested_bidirectional = mapping_has_bidirectional_stdp(&mapping_data);
1766 let existing_bidirectional = self
1767 .cortical_areas
1768 .get(src_area_id)
1769 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1770 .and_then(|v| v.as_object())
1771 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1772 .and_then(|v| v.as_array())
1773 .map(|arr| mapping_has_bidirectional_stdp(arr))
1774 .unwrap_or(false);
1775 let should_apply_bidirectional = requested_bidirectional || existing_bidirectional;
1776
1777 {
1778 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1780 crate::types::BduError::InvalidArea(format!(
1781 "Source area not found: {}",
1782 src_area_id
1783 ))
1784 })?;
1785
1786 let cortical_mapping_dst =
1788 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1789 existing.as_object_mut().ok_or_else(|| {
1790 crate::types::BduError::InvalidMorphology(
1791 "cortical_mapping_dst is not an object".to_string(),
1792 )
1793 })?
1794 } else {
1795 src_area
1797 .properties
1798 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1799 src_area
1800 .properties
1801 .get_mut("cortical_mapping_dst")
1802 .unwrap()
1803 .as_object_mut()
1804 .unwrap()
1805 };
1806
1807 if mapping_data.is_empty() {
1809 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1811 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1812 } else {
1813 cortical_mapping_dst.insert(
1814 dst_area_id.as_base_64(),
1815 serde_json::Value::Array(mapping_data.clone()),
1816 );
1817 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1818 src_area_id, dst_area_id, mapping_data.len());
1819 }
1820 }
1821
1822 if should_apply_bidirectional {
1824 let dst_area = self.cortical_areas.get_mut(dst_area_id).ok_or_else(|| {
1825 crate::types::BduError::InvalidArea(format!(
1826 "Destination area not found: {}",
1827 dst_area_id
1828 ))
1829 })?;
1830 let dst_mapping_dst =
1831 if let Some(existing) = dst_area.properties.get_mut("cortical_mapping_dst") {
1832 existing.as_object_mut().ok_or_else(|| {
1833 crate::types::BduError::InvalidMorphology(
1834 "cortical_mapping_dst is not an object".to_string(),
1835 )
1836 })?
1837 } else {
1838 dst_area
1839 .properties
1840 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1841 dst_area
1842 .properties
1843 .get_mut("cortical_mapping_dst")
1844 .unwrap()
1845 .as_object_mut()
1846 .unwrap()
1847 };
1848
1849 if mapping_data.is_empty() {
1850 dst_mapping_dst.remove(&src_area_id.as_base_64());
1851 info!(
1852 target: "feagi-bdu",
1853 "Removed bi-directional STDP mirror mapping from {} to {}",
1854 dst_area_id,
1855 src_area_id
1856 );
1857 } else {
1858 dst_mapping_dst.insert(
1859 src_area_id.as_base_64(),
1860 serde_json::Value::Array(mapping_data.clone()),
1861 );
1862 info!(
1863 target: "feagi-bdu",
1864 "Updated bi-directional STDP mirror mapping from {} to {} with {} connections",
1865 dst_area_id,
1866 src_area_id,
1867 mapping_data.len()
1868 );
1869 }
1870 }
1871
1872 self.refresh_cortical_mappings_hash();
1873
1874 Ok(())
1875 }
1876
1877 pub fn regenerate_synapses_for_mapping(
1890 &mut self,
1891 src_area_id: &CorticalID,
1892 dst_area_id: &CorticalID,
1893 ) -> BduResult<usize> {
1894 use tracing::info;
1895
1896 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1897
1898 let mapping_rules_len = self
1899 .cortical_areas
1900 .get(src_area_id)
1901 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1902 .and_then(|v| v.as_object())
1903 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1904 .and_then(|v| v.as_array())
1905 .map(|arr| arr.len())
1906 .unwrap_or(0);
1907 tracing::debug!(
1908 target: "feagi-bdu",
1909 "Mapping rules for {} -> {}: {}",
1910 src_area_id,
1911 dst_area_id,
1912 mapping_rules_len
1913 );
1914
1915 let Some(npu_arc) = self.npu.clone() else {
1917 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
1918 return Ok(0);
1919 };
1920
1921 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
1931 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
1932 })?;
1933 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
1934 BduError::InvalidArea(format!(
1935 "No cortical idx for destination area {}",
1936 dst_area_id
1937 ))
1938 })?;
1939
1940 let mut pruned_synapse_count: usize = 0;
1943 use std::time::Instant;
1944 let start = Instant::now();
1945
1946 let (sources, targets) = {
1952 let lock_start = std::time::Instant::now();
1953 let npu = npu_arc.lock().unwrap();
1954 let lock_wait = lock_start.elapsed();
1955 tracing::debug!(
1956 target: "feagi-bdu",
1957 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
1958 lock_wait.as_secs_f64() * 1000.0,
1959 src_area_id,
1960 dst_area_id
1961 );
1962 let sources: Vec<NeuronId> = npu
1963 .get_neurons_in_cortical_area(src_idx)
1964 .into_iter()
1965 .map(NeuronId)
1966 .collect();
1967 let targets: Vec<NeuronId> = npu
1968 .get_neurons_in_cortical_area(dst_idx)
1969 .into_iter()
1970 .map(NeuronId)
1971 .collect();
1972 (sources, targets)
1973 };
1974
1975 tracing::debug!(
1976 target: "feagi-bdu",
1977 "Prune synapses: {} sources, {} targets",
1978 sources.len(),
1979 targets.len()
1980 );
1981
1982 if !sources.is_empty() && !targets.is_empty() {
1983 let remove_start = Instant::now();
1984 pruned_synapse_count = {
1985 let lock_start = std::time::Instant::now();
1986 let mut npu = npu_arc.lock().unwrap();
1987 let lock_wait = lock_start.elapsed();
1988 tracing::debug!(
1989 target: "feagi-bdu",
1990 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
1991 lock_wait.as_secs_f64() * 1000.0,
1992 src_area_id,
1993 dst_area_id
1994 );
1995 npu.remove_synapses_from_sources_to_targets(sources, targets)
1996 };
1997 let remove_time = remove_start.elapsed();
1998 let total_time = start.elapsed();
1999
2000 info!(
2001 target: "feagi-bdu",
2002 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2003 pruned_synapse_count,
2004 src_area_id,
2005 dst_area_id,
2006 total_time.as_millis(),
2007 remove_time.as_millis()
2008 );
2009
2010 if pruned_synapse_count > 0 {
2012 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2013 BduError::Internal(format!(
2014 "Pruned synapse count overflow (usize -> u32): {}",
2015 pruned_synapse_count
2016 ))
2017 })?;
2018 if let Some(state_manager) = StateManager::instance().try_read() {
2019 let core_state = state_manager.get_core_state();
2020 core_state.subtract_synapse_count(pruned_u32);
2021 state_manager.subtract_cortical_area_outgoing_synapses(
2022 &src_area_id.as_base_64(),
2023 pruned_synapse_count,
2024 );
2025 state_manager.subtract_cortical_area_incoming_synapses(
2026 &dst_area_id.as_base_64(),
2027 pruned_synapse_count,
2028 );
2029 }
2030
2031 {
2035 let mut cache = self.cached_synapse_counts_per_area.write();
2036 let entry = cache
2037 .entry(*src_area_id)
2038 .or_insert_with(|| AtomicUsize::new(0));
2039 let mut current = entry.load(Ordering::Relaxed);
2040 loop {
2041 let next = current.saturating_sub(pruned_synapse_count);
2042 match entry.compare_exchange(
2043 current,
2044 next,
2045 Ordering::Relaxed,
2046 Ordering::Relaxed,
2047 ) {
2048 Ok(_) => break,
2049 Err(v) => current = v,
2050 }
2051 }
2052 }
2053 }
2054 }
2055
2056 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2063 tracing::debug!(
2064 target: "feagi-bdu",
2065 "Synaptogenesis created {} synapses for {} -> {}",
2066 synapse_count,
2067 src_area_id,
2068 dst_area_id
2069 );
2070
2071 if synapse_count > 0 {
2074 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2075 BduError::Internal(format!(
2076 "Created synapse count overflow (usize -> u32): {}",
2077 synapse_count
2078 ))
2079 })?;
2080
2081 {
2083 let mut cache = self.cached_synapse_counts_per_area.write();
2084 cache
2085 .entry(*src_area_id)
2086 .or_insert_with(|| AtomicUsize::new(0))
2087 .fetch_add(synapse_count, Ordering::Relaxed);
2088 }
2089
2090 if let Some(state_manager) = StateManager::instance().try_read() {
2092 let core_state = state_manager.get_core_state();
2093 core_state.add_synapse_count(created_u32);
2094 state_manager
2095 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2096 state_manager
2097 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2098 }
2099 }
2100
2101 let src_idx_for_upstream = src_idx;
2104
2105 let has_mapping = self
2107 .cortical_areas
2108 .get(src_area_id)
2109 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2110 .and_then(|v| v.as_object())
2111 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2112 .is_some();
2113
2114 info!(target: "feagi-bdu",
2115 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2116 synapse_count,
2117 src_area_id.as_base_64(),
2118 dst_area_id.as_base_64(),
2119 has_mapping,
2120 if has_mapping { "" } else { "NOT " }
2121 );
2122
2123 if has_mapping {
2124 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2126
2127 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2128 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2129 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2130 warn!(
2131 target: "feagi-bdu",
2132 "Failed to ensure memory twin for {} -> {}: {}",
2133 src_area_id.as_base_64(),
2134 dst_area_id.as_base_64(),
2135 e
2136 );
2137 }
2138 }
2139 }
2140
2141 #[cfg(feature = "plasticity")]
2143 if let Some(ref executor) = self.plasticity_executor {
2144 use feagi_evolutionary::extract_memory_properties;
2145
2146 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2147 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2148 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2149 let upstream_non_memory =
2150 self.filter_non_memory_upstream_areas(&upstream_areas);
2151 debug!(
2152 target: "feagi-bdu",
2153 "Registering memory area idx={} id={} upstream={} depth={}",
2154 dst_area.cortical_idx,
2155 dst_area_id.as_base_64(),
2156 upstream_areas.len(),
2157 mem_props.temporal_depth
2158 );
2159
2160 if let Some(ref npu_arc) = self.npu {
2163 if let Ok(mut npu) = npu_arc.lock() {
2164 let existing_configs = npu.get_all_fire_ledger_configs();
2165 for &upstream_idx in &upstream_areas {
2166 let existing = existing_configs
2167 .iter()
2168 .find(|(idx, _)| *idx == upstream_idx)
2169 .map(|(_, w)| *w)
2170 .unwrap_or(0);
2171
2172 let desired = mem_props.temporal_depth as usize;
2173 let resolved = existing.max(desired);
2174 if resolved != existing {
2175 if let Err(e) =
2176 npu.configure_fire_ledger_window(upstream_idx, resolved)
2177 {
2178 warn!(
2179 target: "feagi-bdu",
2180 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2181 upstream_idx,
2182 resolved,
2183 e
2184 );
2185 }
2186 }
2187 }
2188 } else {
2189 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2190 }
2191 }
2192
2193 if let Ok(exec) = executor.lock() {
2194 use feagi_npu_plasticity::{
2195 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2196 };
2197
2198 let lifecycle_config = MemoryNeuronLifecycleConfig {
2199 initial_lifespan: mem_props.init_lifespan,
2200 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2201 longterm_threshold: mem_props.longterm_threshold,
2202 max_reactivations: 1000,
2203 };
2204
2205 exec.register_memory_area(
2206 dst_area.cortical_idx,
2207 dst_area_id.as_base_64(),
2208 mem_props.temporal_depth,
2209 upstream_non_memory,
2210 Some(lifecycle_config),
2211 );
2212 } else {
2213 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2214 }
2215 } else {
2216 debug!(
2217 target: "feagi-bdu",
2218 "Skipping plasticity registration: no memory properties for area {}",
2219 dst_area_id.as_base_64()
2220 );
2221 }
2222 } else {
2223 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2224 }
2225 } else {
2226 warn!(
2227 target: "feagi-bdu",
2228 "PlasticityExecutor not available; memory area {} not registered",
2229 dst_area_id.as_base_64()
2230 );
2231 }
2232
2233 #[cfg(not(feature = "plasticity"))]
2234 {
2235 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2236 }
2237 } else {
2238 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2240
2241 let mut npu = npu_arc.lock().unwrap();
2243 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2244 }
2245
2246 info!(
2247 target: "feagi-bdu",
2248 "Created {} new synapses: {} -> {}",
2249 synapse_count,
2250 src_area_id,
2251 dst_area_id
2252 );
2253
2254 if pruned_synapse_count > 0 || synapse_count == 0 {
2257 let mut npu = npu_arc.lock().unwrap();
2258 npu.rebuild_synapse_index();
2259 info!(
2260 target: "feagi-bdu",
2261 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2262 src_area_id,
2263 dst_area_id,
2264 pruned_synapse_count,
2265 synapse_count
2266 );
2267 } else {
2268 info!(
2269 target: "feagi-bdu",
2270 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2271 src_area_id,
2272 dst_area_id,
2273 synapse_count
2274 );
2275 }
2276
2277 {
2279 let npu = npu_arc.lock().unwrap();
2280 let fresh_count = npu.get_synapse_count();
2281 self.cached_synapse_count
2282 .store(fresh_count, Ordering::Relaxed);
2283 }
2284
2285 Ok(synapse_count)
2286 }
2287
2288 #[allow(clippy::too_many_arguments)]
2290 fn register_stdp_mapping_for_rule(
2291 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2292 src_area_id: &CorticalID,
2293 dst_area_id: &CorticalID,
2294 src_cortical_idx: u32,
2295 dst_cortical_idx: u32,
2296 rule_obj: &serde_json::Map<String, serde_json::Value>,
2297 bidirectional_stdp: bool,
2298 synapse_psp: u8,
2299 synapse_type: feagi_npu_neural::SynapseType,
2300 ) -> BduResult<()> {
2301 let plasticity_window = rule_obj
2302 .get("plasticity_window")
2303 .and_then(|v| v.as_u64())
2304 .ok_or_else(|| {
2305 BduError::Internal(format!(
2306 "Missing plasticity_window in plastic mapping rule {} -> {}",
2307 src_area_id, dst_area_id
2308 ))
2309 })? as usize;
2310 let plasticity_constant = rule_obj
2311 .get("plasticity_constant")
2312 .and_then(|v| v.as_i64())
2313 .ok_or_else(|| {
2314 BduError::Internal(format!(
2315 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2316 src_area_id, dst_area_id
2317 ))
2318 })?;
2319 let ltp_multiplier = rule_obj
2320 .get("ltp_multiplier")
2321 .and_then(|v| v.as_i64())
2322 .ok_or_else(|| {
2323 BduError::Internal(format!(
2324 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2325 src_area_id, dst_area_id
2326 ))
2327 })?;
2328 let ltd_multiplier = rule_obj
2329 .get("ltd_multiplier")
2330 .and_then(|v| v.as_i64())
2331 .ok_or_else(|| {
2332 BduError::Internal(format!(
2333 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2334 src_area_id, dst_area_id
2335 ))
2336 })?;
2337
2338 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2339 plasticity_window,
2340 plasticity_constant,
2341 ltp_multiplier,
2342 ltd_multiplier,
2343 bidirectional_stdp,
2344 synapse_psp,
2345 synapse_type,
2346 };
2347
2348 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2349 let mut npu_lock = npu
2350 .lock()
2351 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2352 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2353
2354 npu_lock
2355 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2356 .map_err(|e| {
2357 BduError::Internal(format!(
2358 "Failed to register STDP mapping {} -> {}: {}",
2359 src_area_id, dst_area_id, e
2360 ))
2361 })?;
2362
2363 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2365 for area_idx in [src_cortical_idx, dst_cortical_idx] {
2366 let existing = existing_configs
2367 .iter()
2368 .find(|(idx, _)| *idx == area_idx)
2369 .map(|(_, w)| *w)
2370 .unwrap_or(0);
2371 let resolved = existing.max(plasticity_window);
2372 if resolved != existing {
2373 npu_lock
2374 .configure_fire_ledger_window(area_idx, resolved)
2375 .map_err(|e| {
2376 BduError::Internal(format!(
2377 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2378 area_idx, resolved, e
2379 ))
2380 })?;
2381 }
2382 }
2383
2384 Ok(())
2385 }
2386
2387 fn resolve_synapse_params_for_rule(
2389 &self,
2390 src_area_id: &CorticalID,
2391 rule: &serde_json::Value,
2392 ) -> BduResult<(u8, u8, feagi_npu_neural::SynapseType)> {
2393 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2395 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2396 })?;
2397
2398 let (weight, synapse_type) = {
2404 let parse_i64 = |v: &serde_json::Value| -> Option<i64> {
2406 if let Some(i) = v.as_i64() {
2407 return Some(i);
2408 }
2409 let f = v.as_f64()?;
2410 if f.fract() == 0.0 {
2411 Some(f as i64)
2412 } else {
2413 None
2414 }
2415 };
2416
2417 let multiplier_i64: i64 = if let Some(obj) = rule.as_object() {
2418 obj.get("postSynapticCurrent_multiplier")
2419 .and_then(parse_i64)
2420 .unwrap_or(1) } else if let Some(arr) = rule.as_array() {
2422 arr.get(2).and_then(parse_i64).unwrap_or(1) } else {
2425 128 };
2427
2428 if multiplier_i64 < 0 {
2429 let abs = if multiplier_i64 == i64::MIN {
2430 i64::MAX
2431 } else {
2432 multiplier_i64.abs()
2433 };
2434 (
2435 abs.clamp(0, 255) as u8,
2436 feagi_npu_neural::SynapseType::Inhibitory,
2437 )
2438 } else {
2439 (
2440 multiplier_i64.clamp(0, 255) as u8,
2441 feagi_npu_neural::SynapseType::Excitatory,
2442 )
2443 }
2444 };
2445
2446 let (psp_f32, psp) = {
2453 use crate::models::cortical_area::CorticalAreaExt;
2454 let psp_f32 = src_area.postsynaptic_current();
2455 (psp_f32, psp_f32.clamp(0.0, 255.0) as u8)
2456 };
2457
2458 tracing::debug!(
2459 target: "feagi-bdu",
2460 "Resolved synapse params src={} weight={} psp={} psp_f32={} type={:?}",
2461 src_area_id.as_base_64(),
2462 weight,
2463 psp,
2464 psp_f32,
2465 synapse_type
2466 );
2467
2468 Ok((weight, psp, synapse_type))
2469 }
2470
2471 fn apply_cortical_mapping_for_pair(
2473 &mut self,
2474 src_area_id: &CorticalID,
2475 dst_area_id: &CorticalID,
2476 ) -> BduResult<usize> {
2477 let rules = {
2483 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2484 crate::types::BduError::InvalidArea(format!(
2485 "Source area not found: {}",
2486 src_area_id
2487 ))
2488 })?;
2489
2490 let Some(mapping_dst) = src_area
2491 .properties
2492 .get("cortical_mapping_dst")
2493 .and_then(|v| v.as_object())
2494 else {
2495 return Ok(0);
2496 };
2497
2498 let Some(rules) = mapping_dst
2499 .get(&dst_area_id.as_base_64())
2500 .and_then(|v| v.as_array())
2501 else {
2502 return Ok(0);
2503 };
2504
2505 rules.clone()
2506 }; if rules.is_empty() {
2509 return Ok(0);
2510 }
2511
2512 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2514 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2515 })?;
2516 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2517 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2518 })?;
2519
2520 let npu_arc = self
2522 .npu
2523 .as_ref()
2524 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2525 .clone();
2526
2527 tracing::debug!(
2528 target: "feagi-bdu",
2529 "Applying {} mapping rule(s) for {} -> {}",
2530 rules.len(),
2531 src_area_id,
2532 dst_area_id
2533 );
2534 let mut total_synapses = 0;
2536 for rule in &rules {
2537 let rule_obj = match rule.as_object() {
2538 Some(obj) => obj,
2539 None => continue,
2540 };
2541 let morphology_id = rule_obj
2542 .get("morphology_id")
2543 .and_then(|v| v.as_str())
2544 .unwrap_or("unknown");
2545
2546 let rule_keys: Vec<String> = rule_obj.keys().cloned().collect();
2547
2548 let mut plasticity_flag = rule_obj
2550 .get("plasticity_flag")
2551 .and_then(|v| v.as_bool())
2552 .unwrap_or(false);
2553 if morphology_id == "associative_memory" {
2554 plasticity_flag = true;
2555 }
2556 if plasticity_flag {
2557 let (_weight, psp, synapse_type) =
2558 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2559 let bidirectional_stdp = morphology_id == "associative_memory";
2560 if let Err(e) = Self::register_stdp_mapping_for_rule(
2561 &npu_arc,
2562 src_area_id,
2563 dst_area_id,
2564 src_cortical_idx,
2565 dst_cortical_idx,
2566 rule_obj,
2567 bidirectional_stdp,
2568 psp,
2569 synapse_type,
2570 ) {
2571 tracing::error!(
2572 target: "feagi-bdu",
2573 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2574 src_area_id,
2575 dst_area_id,
2576 morphology_id,
2577 rule_keys,
2578 e
2579 );
2580 return Err(e);
2581 }
2582 }
2583
2584 let synapse_count = match self.apply_single_morphology_rule(
2586 src_area_id,
2587 dst_area_id,
2588 rule,
2589 ) {
2590 Ok(count) => count,
2591 Err(e) => {
2592 tracing::error!(
2593 target: "feagi-bdu",
2594 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2595 src_area_id,
2596 dst_area_id,
2597 morphology_id,
2598 rule_keys,
2599 e
2600 );
2601 return Err(e);
2602 }
2603 };
2604 total_synapses += synapse_count;
2605 tracing::debug!(
2606 target: "feagi-bdu",
2607 "Rule {} created {} synapses for {} -> {}",
2608 morphology_id,
2609 synapse_count,
2610 src_area_id,
2611 dst_area_id
2612 );
2613 }
2614
2615 Ok(total_synapses)
2616 }
2617
2618 #[allow(clippy::too_many_arguments)]
2632 fn apply_function_morphology(
2633 &self,
2634 morphology_id: &str,
2635 rule: &serde_json::Value,
2636 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2637 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2638 src_area_id: &CorticalID,
2639 dst_area_id: &CorticalID,
2640 src_idx: u32,
2641 dst_idx: u32,
2642 weight: u8,
2643 psp: u8,
2644 synapse_attractivity: u8,
2645 synapse_type: feagi_npu_neural::SynapseType,
2646 ) -> BduResult<usize> {
2647 match morphology_id {
2648 "projector" => {
2649 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2651 crate::types::BduError::InvalidArea(format!(
2652 "Source area not found: {}",
2653 src_area_id
2654 ))
2655 })?;
2656 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2657 crate::types::BduError::InvalidArea(format!(
2658 "Destination area not found: {}",
2659 dst_area_id
2660 ))
2661 })?;
2662
2663 let src_dimensions = (
2664 src_area.dimensions.width as usize,
2665 src_area.dimensions.height as usize,
2666 src_area.dimensions.depth as usize,
2667 );
2668 let dst_dimensions = (
2669 dst_area.dimensions.width as usize,
2670 dst_area.dimensions.height as usize,
2671 dst_area.dimensions.depth as usize,
2672 );
2673
2674 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2675 let count = apply_projector_morphology_with_dimensions(
2676 npu,
2677 src_idx,
2678 dst_idx,
2679 src_dimensions,
2680 dst_dimensions,
2681 None, None, weight,
2684 psp,
2685 synapse_attractivity,
2686 synapse_type,
2687 )?;
2688 npu.rebuild_synapse_index();
2690 Ok(count as usize)
2691 }
2692 "episodic_memory" => {
2693 use tracing::trace;
2696 trace!(
2697 target: "feagi-bdu",
2698 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
2699 src_idx, dst_idx
2700 );
2701 Ok(0)
2702 }
2703 "memory_replay" => {
2704 use tracing::trace;
2706 trace!(
2707 target: "feagi-bdu",
2708 "Memory replay morphology: {} -> {} (no physical synapses)",
2709 src_idx, dst_idx
2710 );
2711 Ok(0)
2712 }
2713 "associative_memory" => {
2714 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2718 crate::types::BduError::InvalidArea(format!(
2719 "Source area not found: {}",
2720 src_area_id
2721 ))
2722 })?;
2723 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2724 crate::types::BduError::InvalidArea(format!(
2725 "Destination area not found: {}",
2726 dst_area_id
2727 ))
2728 })?;
2729
2730 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2731 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2732 {
2733 let src_dimensions = (
2734 src_area.dimensions.width as usize,
2735 src_area.dimensions.height as usize,
2736 src_area.dimensions.depth as usize,
2737 );
2738 let dst_dimensions = (
2739 dst_area.dimensions.width as usize,
2740 dst_area.dimensions.height as usize,
2741 dst_area.dimensions.depth as usize,
2742 );
2743 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2744 let count = apply_projector_morphology_with_dimensions(
2745 npu,
2746 src_idx,
2747 dst_idx,
2748 src_dimensions,
2749 dst_dimensions,
2750 None,
2751 None,
2752 weight,
2753 psp,
2754 synapse_attractivity,
2755 synapse_type,
2756 )?;
2757 npu.rebuild_synapse_index();
2758 Ok(count as usize)
2759 } else {
2760 Ok(0)
2761 }
2762 }
2763 "block_to_block" => {
2764 tracing::warn!(
2765 target: "feagi-bdu",
2766 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
2767 src_idx, dst_idx
2768 );
2769 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 let src_dimensions = (
2784 src_area.dimensions.width as usize,
2785 src_area.dimensions.height as usize,
2786 src_area.dimensions.depth as usize,
2787 );
2788 let dst_dimensions = (
2789 dst_area.dimensions.width as usize,
2790 dst_area.dimensions.height as usize,
2791 dst_area.dimensions.depth as usize,
2792 );
2793
2794 let scalar = if let Some(obj) = rule.as_object() {
2796 if let Some(scalar_arr) =
2798 obj.get("morphology_scalar").and_then(|v| v.as_array())
2799 {
2800 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
2802 } else {
2803 1 }
2805 } else if let Some(arr) = rule.as_array() {
2806 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
2808 } else {
2809 1 };
2811
2812 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
2815 let count = if estimated_neurons > 100_000 {
2816 let _ = npu;
2818
2819 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
2820 npu_arc,
2821 src_idx,
2822 dst_idx,
2823 src_dimensions,
2824 dst_dimensions,
2825 scalar, weight,
2827 psp,
2828 synapse_attractivity,
2829 synapse_type,
2830 )? as usize
2831 } else {
2832 tracing::warn!(
2834 target: "feagi-bdu",
2835 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
2836 src_idx, dst_idx, src_dimensions, dst_dimensions
2837 );
2838 let count =
2839 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
2840 npu,
2841 src_idx,
2842 dst_idx,
2843 src_dimensions,
2844 dst_dimensions,
2845 scalar, weight,
2847 psp,
2848 synapse_attractivity,
2849 synapse_type,
2850 )? as usize;
2851 tracing::warn!(
2852 target: "feagi-bdu",
2853 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
2854 count
2855 );
2856 if count > 0 {
2858 npu.rebuild_synapse_index();
2859 }
2860 count
2861 };
2862
2863 if count > 0 && estimated_neurons > 100_000 {
2865 let mut npu_lock = npu_arc.lock().unwrap();
2866 npu_lock.rebuild_synapse_index();
2867 }
2868
2869 Ok(count)
2870 }
2871 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
2872 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
2873 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2874 crate::types::BduError::InvalidArea(format!(
2875 "Source area not found: {}",
2876 src_area_id
2877 ))
2878 })?;
2879 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2880 crate::types::BduError::InvalidArea(format!(
2881 "Destination area not found: {}",
2882 dst_area_id
2883 ))
2884 })?;
2885
2886 let src_dimensions = (
2887 src_area.dimensions.width as usize,
2888 src_area.dimensions.height as usize,
2889 src_area.dimensions.depth as usize,
2890 );
2891 let dst_dimensions = (
2892 dst_area.dimensions.width as usize,
2893 dst_area.dimensions.height as usize,
2894 dst_area.dimensions.depth as usize,
2895 );
2896
2897 let (axis, mode) = match morphology_id {
2898 "bitmask_encoder_x" => (
2899 crate::connectivity::core_morphologies::BitmaskAxis::X,
2900 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2901 ),
2902 "bitmask_encoder_y" => (
2903 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2904 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2905 ),
2906 "bitmask_encoder_z" => (
2907 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2908 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2909 ),
2910 "bitmask_decoder_x" => (
2911 crate::connectivity::core_morphologies::BitmaskAxis::X,
2912 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2913 ),
2914 "bitmask_decoder_y" => (
2915 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2916 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2917 ),
2918 "bitmask_decoder_z" => (
2919 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2920 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2921 ),
2922 _ => unreachable!("matched bitmask morphology above"),
2923 };
2924
2925 let count =
2926 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
2927 npu,
2928 src_idx,
2929 dst_idx,
2930 src_dimensions,
2931 dst_dimensions,
2932 axis,
2933 mode,
2934 weight,
2935 psp,
2936 synapse_attractivity,
2937 synapse_type,
2938 )?;
2939 if count > 0 {
2940 npu.rebuild_synapse_index();
2941 }
2942 Ok(count as usize)
2943 }
2944 "sweeper" => {
2945 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2946 crate::types::BduError::InvalidArea(format!(
2947 "Destination area not found: {}",
2948 dst_area_id
2949 ))
2950 })?;
2951 let dst_dimensions = (
2952 dst_area.dimensions.width as usize,
2953 dst_area.dimensions.height as usize,
2954 dst_area.dimensions.depth as usize,
2955 );
2956
2957 let count =
2958 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
2959 npu,
2960 src_idx,
2961 dst_idx,
2962 dst_dimensions,
2963 weight,
2964 psp,
2965 synapse_attractivity,
2966 synapse_type,
2967 )?;
2968 if count > 0 {
2969 npu.rebuild_synapse_index();
2970 }
2971 Ok(count as usize)
2972 }
2973 "last_to_first" => {
2974 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2975 crate::types::BduError::InvalidArea(format!(
2976 "Source area not found: {}",
2977 src_area_id
2978 ))
2979 })?;
2980 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2981 crate::types::BduError::InvalidArea(format!(
2982 "Destination area not found: {}",
2983 dst_area_id
2984 ))
2985 })?;
2986 let src_dimensions = (
2987 src_area.dimensions.width as usize,
2988 src_area.dimensions.height as usize,
2989 src_area.dimensions.depth as usize,
2990 );
2991 let dst_dimensions = (
2992 dst_area.dimensions.width as usize,
2993 dst_area.dimensions.height as usize,
2994 dst_area.dimensions.depth as usize,
2995 );
2996
2997 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
2998 npu,
2999 src_idx,
3000 dst_idx,
3001 src_dimensions,
3002 dst_dimensions,
3003 weight,
3004 psp,
3005 synapse_attractivity,
3006 synapse_type,
3007 )?;
3008 if count > 0 {
3009 npu.rebuild_synapse_index();
3010 }
3011 Ok(count as usize)
3012 }
3013 "rotator_z" => {
3014 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3015 crate::types::BduError::InvalidArea(format!(
3016 "Source area not found: {}",
3017 src_area_id
3018 ))
3019 })?;
3020 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3021 crate::types::BduError::InvalidArea(format!(
3022 "Destination area not found: {}",
3023 dst_area_id
3024 ))
3025 })?;
3026 let src_dimensions = (
3027 src_area.dimensions.width as usize,
3028 src_area.dimensions.height as usize,
3029 src_area.dimensions.depth as usize,
3030 );
3031 let dst_dimensions = (
3032 dst_area.dimensions.width as usize,
3033 dst_area.dimensions.height as usize,
3034 dst_area.dimensions.depth as usize,
3035 );
3036
3037 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3038 npu,
3039 src_idx,
3040 dst_idx,
3041 src_dimensions,
3042 dst_dimensions,
3043 weight,
3044 psp,
3045 synapse_attractivity,
3046 synapse_type,
3047 )?;
3048 if count > 0 {
3049 npu.rebuild_synapse_index();
3050 }
3051 Ok(count as usize)
3052 }
3053 _ => {
3054 use tracing::debug;
3057 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3058 Ok(0)
3059 }
3060 }
3061 }
3062
3063 fn apply_single_morphology_rule(
3065 &mut self,
3066 src_area_id: &CorticalID,
3067 dst_area_id: &CorticalID,
3068 rule: &serde_json::Value,
3069 ) -> BduResult<usize> {
3070 let morphology_id = if let Some(arr) = rule.as_array() {
3072 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3073 } else if let Some(obj) = rule.as_object() {
3074 obj.get("morphology_id")
3075 .and_then(|v| v.as_str())
3076 .unwrap_or("")
3077 } else {
3078 return Ok(0);
3079 };
3080
3081 if morphology_id.is_empty() {
3082 return Ok(0);
3083 }
3084
3085 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3087 crate::types::BduError::InvalidMorphology(format!(
3088 "Morphology not found: {}",
3089 morphology_id
3090 ))
3091 })?;
3092
3093 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3095 crate::types::BduError::InvalidArea(format!(
3096 "Source area ID not found: {}",
3097 src_area_id
3098 ))
3099 })?;
3100 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3101 crate::types::BduError::InvalidArea(format!(
3102 "Destination area ID not found: {}",
3103 dst_area_id
3104 ))
3105 })?;
3106
3107 if let Some(ref npu_arc) = self.npu {
3109 let lock_start = std::time::Instant::now();
3110 let mut npu = npu_arc.lock().unwrap();
3111 let lock_wait = lock_start.elapsed();
3112 tracing::debug!(
3113 target: "feagi-bdu",
3114 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3115 lock_wait.as_secs_f64() * 1000.0,
3116 src_area_id,
3117 dst_area_id,
3118 morphology_id
3119 );
3120
3121 let (weight, psp, synapse_type) =
3122 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3123
3124 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3126 obj.get("synapse_attractivity")
3127 .and_then(|v| v.as_u64())
3128 .unwrap_or(100) as u8
3129 } else {
3130 100 };
3132
3133 match morphology.morphology_type {
3134 feagi_evolutionary::MorphologyType::Functions => {
3135 tracing::warn!(
3136 target: "feagi-bdu",
3137 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3138 morphology_id
3139 );
3140 self.apply_function_morphology(
3143 morphology_id,
3144 rule,
3145 npu_arc,
3146 &mut npu,
3147 src_area_id,
3148 dst_area_id,
3149 *src_idx,
3150 *dst_idx,
3151 weight,
3152 psp,
3153 synapse_attractivity,
3154 synapse_type,
3155 )
3156 }
3157 feagi_evolutionary::MorphologyType::Vectors => {
3158 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3159
3160 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3162 crate::types::BduError::InvalidArea(format!(
3163 "Destination area not found: {}",
3164 dst_area_id
3165 ))
3166 })?;
3167
3168 let dst_dimensions = (
3169 dst_area.dimensions.width as usize,
3170 dst_area.dimensions.height as usize,
3171 dst_area.dimensions.depth as usize,
3172 );
3173
3174 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3175 morphology.parameters
3176 {
3177 let vectors_tuples: Vec<(i32, i32, i32)> =
3179 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3180
3181 let count = apply_vectors_morphology_with_dimensions(
3182 &mut npu,
3183 *src_idx,
3184 *dst_idx,
3185 vectors_tuples,
3186 dst_dimensions,
3187 weight, psp, synapse_attractivity, synapse_type,
3191 )?;
3192 npu.rebuild_synapse_index();
3195 Ok(count as usize)
3196 } else {
3197 Ok(0)
3198 }
3199 }
3200 feagi_evolutionary::MorphologyType::Patterns => {
3201 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3202 use crate::connectivity::rules::patterns::{
3203 Pattern3D, PatternElement as RulePatternElement,
3204 };
3205 use feagi_evolutionary::PatternElement as EvoPatternElement;
3206
3207 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3208 morphology.parameters
3209 else {
3210 return Ok(0);
3211 };
3212
3213 let convert_element =
3214 |element: &EvoPatternElement|
3215 -> crate::types::BduResult<RulePatternElement> {
3216 match element {
3217 EvoPatternElement::Value(value) => {
3218 if *value < 0 {
3219 return Err(crate::types::BduError::InvalidMorphology(
3220 format!(
3221 "Pattern morphology {} contains negative voxel coordinate {}",
3222 morphology_id, value
3223 ),
3224 ));
3225 }
3226 Ok(RulePatternElement::Exact(*value))
3227 }
3228 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3229 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3230 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3231 }
3232 };
3233
3234 let mut converted_patterns = Vec::with_capacity(patterns.len());
3235 for pattern_pair in patterns {
3236 if pattern_pair.len() != 2 {
3237 return Err(crate::types::BduError::InvalidMorphology(format!(
3238 "Pattern morphology {} must contain [src, dst] pairs",
3239 morphology_id
3240 )));
3241 }
3242
3243 let src_pattern = &pattern_pair[0];
3244 let dst_pattern = &pattern_pair[1];
3245
3246 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3247 return Err(crate::types::BduError::InvalidMorphology(format!(
3248 "Pattern morphology {} requires 3-axis patterns",
3249 morphology_id
3250 )));
3251 }
3252
3253 let src: Pattern3D = (
3254 convert_element(&src_pattern[0])?,
3255 convert_element(&src_pattern[1])?,
3256 convert_element(&src_pattern[2])?,
3257 );
3258 let dst: Pattern3D = (
3259 convert_element(&dst_pattern[0])?,
3260 convert_element(&dst_pattern[1])?,
3261 convert_element(&dst_pattern[2])?,
3262 );
3263
3264 converted_patterns.push((src, dst));
3265 }
3266
3267 let count = apply_patterns_morphology(
3268 &mut npu,
3269 *src_idx,
3270 *dst_idx,
3271 converted_patterns,
3272 weight,
3273 psp,
3274 synapse_attractivity,
3275 synapse_type,
3276 )?;
3277 if count > 0 {
3278 npu.rebuild_synapse_index();
3279 }
3280 Ok(count as usize)
3281 }
3282 feagi_evolutionary::MorphologyType::Composite => {
3283 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3284 morphology.parameters
3285 else {
3286 return Ok(0);
3287 };
3288
3289 if morphology_id != "tile" {
3290 use tracing::debug;
3291 debug!(
3292 target: "feagi-bdu",
3293 "Composite morphology {} not yet implemented",
3294 morphology_id
3295 );
3296 return Ok(0);
3297 }
3298
3299 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3300 crate::types::BduError::InvalidArea(format!(
3301 "Source area not found: {}",
3302 src_area_id
3303 ))
3304 })?;
3305 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3306 crate::types::BduError::InvalidArea(format!(
3307 "Destination area not found: {}",
3308 dst_area_id
3309 ))
3310 })?;
3311 let src_dimensions = (
3312 src_area.dimensions.width as usize,
3313 src_area.dimensions.height as usize,
3314 src_area.dimensions.depth as usize,
3315 );
3316 let dst_dimensions = (
3317 dst_area.dimensions.width as usize,
3318 dst_area.dimensions.height as usize,
3319 dst_area.dimensions.depth as usize,
3320 );
3321
3322 let count =
3323 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3324 &mut npu,
3325 *src_idx,
3326 *dst_idx,
3327 src_dimensions,
3328 dst_dimensions,
3329 weight,
3330 psp,
3331 synapse_attractivity,
3332 synapse_type,
3333 )?;
3334 if count > 0 {
3335 npu.rebuild_synapse_index();
3336 }
3337 Ok(count as usize)
3338 }
3339 }
3340 } else {
3341 Ok(0) }
3343 }
3344
3345 pub fn set_npu(
3358 &mut self,
3359 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3360 ) {
3361 self.npu = Some(Arc::clone(&npu));
3362 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3363
3364 #[cfg(not(feature = "wasm"))]
3367 {
3368 use feagi_state_manager::StateManager;
3369 if let Some(state_manager) = StateManager::instance().try_read() {
3370 let core_state = state_manager.get_core_state();
3371 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3373 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3374 info!(
3375 target: "feagi-bdu",
3376 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3377 self.config.max_neurons, self.config.max_synapses
3378 );
3379 }
3380 }
3381
3382 let existing_area_count = self.cortical_id_to_idx.len();
3389 if existing_area_count > 0 {
3390 match npu.lock() {
3391 Ok(mut npu_lock) => {
3392 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3393 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3394 }
3395 info!(
3396 target: "feagi-bdu",
3397 "🔁 Backfilled {} cortical area registrations into NPU",
3398 existing_area_count
3399 );
3400 }
3401 Err(e) => {
3402 warn!(
3403 target: "feagi-bdu",
3404 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3405 e
3406 );
3407 }
3408 }
3409 }
3410
3411 self.update_all_cached_stats();
3413 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3414 self.get_neuron_count(), self.get_synapse_count());
3415 }
3416
3417 pub fn has_npu(&self) -> bool {
3419 self.npu.is_some()
3420 }
3421
3422 pub fn get_npu(
3429 &self,
3430 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3431 {
3432 self.npu.as_ref()
3433 }
3434
3435 #[cfg(feature = "plasticity")]
3438 pub fn set_plasticity_executor(
3439 &mut self,
3440 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3441 ) {
3442 self.plasticity_executor = Some(executor);
3443 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3444 }
3445
3446 #[cfg(feature = "plasticity")]
3448 pub fn get_plasticity_executor(
3449 &self,
3450 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3451 self.plasticity_executor.as_ref()
3452 }
3453
3454 pub fn get_neuron_capacity(&self) -> usize {
3466 self.config.max_neurons
3468 }
3469
3470 pub fn get_synapse_capacity(&self) -> usize {
3482 self.config.max_synapses
3484 }
3485
3486 pub fn update_fatigue_index(&self) -> Option<u8> {
3501 let mut last_calc = match self.last_fatigue_calculation.lock() {
3503 Ok(guard) => guard,
3504 Err(_) => return None, };
3506
3507 let now = std::time::Instant::now();
3508 if now.duration_since(*last_calc).as_secs() < 2 {
3509 return None; }
3511 *last_calc = now;
3512 drop(last_calc);
3513
3514 let regular_neuron_count = self.get_neuron_count();
3516 let regular_neuron_capacity = self.get_neuron_capacity();
3517 let regular_neuron_util = if regular_neuron_capacity > 0 {
3518 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3519 } else {
3520 0
3521 };
3522
3523 let memory_neuron_util = match StateManager::instance().try_read() {
3527 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3528 None => {
3529 return None;
3531 }
3532 };
3533
3534 let synapse_count = self.get_synapse_count();
3536 let synapse_capacity = self.get_synapse_capacity();
3537 let synapse_util = if synapse_capacity > 0 {
3538 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3539 } else {
3540 0
3541 };
3542
3543 let fatigue_index = regular_neuron_util
3545 .max(memory_neuron_util)
3546 .max(synapse_util);
3547
3548 let current_fatigue_active = {
3550 StateManager::instance()
3552 .try_read()
3553 .map(|m| m.get_core_state().is_fatigue_active())
3554 .unwrap_or(false)
3555 };
3556
3557 let new_fatigue_active = if fatigue_index >= 85 {
3558 true
3559 } else if fatigue_index < 80 {
3560 false
3561 } else {
3562 current_fatigue_active };
3564
3565 if let Some(state_manager) = StateManager::instance().try_write() {
3569 let core_state = state_manager.get_core_state();
3570 core_state.set_fatigue_index(fatigue_index);
3571 core_state.set_fatigue_active(new_fatigue_active);
3572 core_state.set_regular_neuron_util(regular_neuron_util);
3573 core_state.set_memory_neuron_util(memory_neuron_util);
3574 core_state.set_synapse_util(synapse_util);
3575 } else {
3576 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3578 }
3579
3580 if let Some(ref npu) = self.npu {
3582 if let Ok(mut npu_lock) = npu.lock() {
3583 npu_lock.set_fatigue_active(new_fatigue_active);
3584 }
3585 }
3586
3587 trace!(
3588 target: "feagi-bdu",
3589 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3590 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3591 );
3592
3593 Some(fatigue_index)
3594 }
3595
3596 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3613 let area = self
3615 .cortical_areas
3616 .get(cortical_id)
3617 .ok_or_else(|| {
3618 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3619 })?
3620 .clone();
3621
3622 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3624 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3625 })?;
3626
3627 let npu = self
3629 .npu
3630 .as_ref()
3631 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3632
3633 use crate::models::CorticalAreaExt;
3636 let per_voxel_cnt = area.neurons_per_voxel();
3637 let firing_threshold = area.firing_threshold();
3638 let firing_threshold_increment_x = area.firing_threshold_increment_x();
3639 let firing_threshold_increment_y = area.firing_threshold_increment_y();
3640 let firing_threshold_increment_z = area.firing_threshold_increment_z();
3641 let firing_threshold_limit_raw = area.firing_threshold_limit();
3643 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
3644 f32::MAX } else {
3646 firing_threshold_limit_raw
3647 };
3648
3649 if firing_threshold_increment_x != 0.0
3651 || firing_threshold_increment_y != 0.0
3652 || firing_threshold_increment_z != 0.0
3653 {
3654 info!(
3655 target: "feagi-bdu",
3656 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
3657 cortical_id.as_base_64(),
3658 firing_threshold_increment_x,
3659 firing_threshold_increment_y,
3660 firing_threshold_increment_z
3661 );
3662 } else {
3663 if area.properties.contains_key("firing_threshold_increment_x")
3665 || area.properties.contains_key("firing_threshold_increment_y")
3666 || area.properties.contains_key("firing_threshold_increment_z")
3667 {
3668 info!(
3669 target: "feagi-bdu",
3670 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
3671 cortical_id.as_base_64(),
3672 area.properties.get("firing_threshold_increment_x"),
3673 area.properties.get("firing_threshold_increment_y"),
3674 area.properties.get("firing_threshold_increment_z")
3675 );
3676 }
3677 }
3678
3679 let leak_coefficient = area.leak_coefficient();
3680 let excitability = area.neuron_excitability();
3681 let refractory_period = area.refractory_period();
3682 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
3684 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
3685 u16::MAX } else {
3687 consecutive_fire_limit_raw
3688 };
3689 let snooze_length = area.snooze_period();
3690 let mp_charge_accumulation = area.mp_charge_accumulation();
3691
3692 let voxels = area.dimensions.width as usize
3694 * area.dimensions.height as usize
3695 * area.dimensions.depth as usize;
3696 let expected_neurons = voxels * per_voxel_cnt as usize;
3697
3698 trace!(
3699 target: "feagi-bdu",
3700 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
3701 cortical_id.as_base_64(),
3702 area.dimensions.width,
3703 area.dimensions.height,
3704 area.dimensions.depth,
3705 per_voxel_cnt,
3706 expected_neurons
3707 );
3708
3709 let mut npu_lock = npu
3712 .lock()
3713 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3714
3715 let neuron_count = npu_lock
3716 .create_cortical_area_neurons(
3717 *cortical_idx,
3718 area.dimensions.width,
3719 area.dimensions.height,
3720 area.dimensions.depth,
3721 per_voxel_cnt,
3722 firing_threshold,
3723 firing_threshold_increment_x,
3724 firing_threshold_increment_y,
3725 firing_threshold_increment_z,
3726 firing_threshold_limit,
3727 leak_coefficient,
3728 0.0, 0, refractory_period,
3731 excitability,
3732 consecutive_fire_limit,
3733 snooze_length,
3734 mp_charge_accumulation,
3735 )
3736 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?;
3737
3738 trace!(
3739 target: "feagi-bdu",
3740 "Created {} neurons for area {} via NPU",
3741 neuron_count,
3742 cortical_id.as_base_64()
3743 );
3744
3745 {
3748 let mut cache = self.cached_neuron_counts_per_area.write();
3749 cache
3750 .entry(*cortical_id)
3751 .or_insert_with(|| AtomicUsize::new(0))
3752 .store(neuron_count as usize, Ordering::Relaxed);
3753 }
3754
3755 if let Some(state_manager) = StateManager::instance().try_read() {
3757 state_manager
3758 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
3759 }
3760
3761 self.cached_neuron_count
3763 .fetch_add(neuron_count as usize, Ordering::Relaxed);
3764
3765 if let Some(state_manager) = StateManager::instance().try_read() {
3767 let core_state = state_manager.get_core_state();
3768 core_state.add_neuron_count(neuron_count);
3769 core_state.add_regular_neuron_count(neuron_count);
3770 }
3771
3772 Ok(neuron_count)
3780 }
3781
3782 #[allow(clippy::too_many_arguments)]
3806 pub fn add_neuron(
3807 &mut self,
3808 cortical_id: &CorticalID,
3809 x: u32,
3810 y: u32,
3811 z: u32,
3812 firing_threshold: f32,
3813 firing_threshold_limit: f32,
3814 leak_coefficient: f32,
3815 resting_potential: f32,
3816 neuron_type: u8,
3817 refractory_period: u16,
3818 excitability: f32,
3819 consecutive_fire_limit: u16,
3820 snooze_length: u16,
3821 mp_charge_accumulation: bool,
3822 ) -> BduResult<u64> {
3823 if !self.cortical_areas.contains_key(cortical_id) {
3825 return Err(BduError::InvalidArea(format!(
3826 "Cortical area {} not found",
3827 cortical_id
3828 )));
3829 }
3830
3831 let cortical_idx = *self
3832 .cortical_id_to_idx
3833 .get(cortical_id)
3834 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
3835
3836 let npu = self
3838 .npu
3839 .as_ref()
3840 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3841
3842 let mut npu_lock = npu
3843 .lock()
3844 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3845
3846 let neuron_id = npu_lock
3848 .add_neuron(
3849 firing_threshold,
3850 firing_threshold_limit,
3851 leak_coefficient,
3852 resting_potential,
3853 neuron_type as i32,
3854 refractory_period,
3855 excitability,
3856 consecutive_fire_limit,
3857 snooze_length,
3858 mp_charge_accumulation,
3859 cortical_idx,
3860 x,
3861 y,
3862 z,
3863 )
3864 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
3865
3866 trace!(
3867 target: "feagi-bdu",
3868 "Created neuron {} in area {} at ({}, {}, {})",
3869 neuron_id.0,
3870 cortical_id,
3871 x,
3872 y,
3873 z
3874 );
3875
3876 if let Some(state_manager) = StateManager::instance().try_read() {
3878 let core_state = state_manager.get_core_state();
3879 core_state.add_neuron_count(1);
3880 core_state.add_regular_neuron_count(1);
3881 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3882 }
3883
3884 Ok(neuron_id.0 as u64)
3885 }
3886
3887 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
3898 let npu = self
3900 .npu
3901 .as_ref()
3902 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3903
3904 let mut npu_lock = npu
3905 .lock()
3906 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3907
3908 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
3909 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
3910
3911 let deleted = npu_lock.delete_neuron(neuron_id as u32);
3912
3913 if deleted {
3914 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
3915
3916 if let Some(state_manager) = StateManager::instance().try_read() {
3918 let core_state = state_manager.get_core_state();
3919 core_state.subtract_neuron_count(1);
3920 core_state.subtract_regular_neuron_count(1);
3921 if let Some(cortical_id) = cortical_id {
3922 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3923 }
3924 }
3925
3926 }
3930
3931 Ok(deleted)
3932 }
3933
3934 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
3948 let src_area = self
3950 .cortical_areas
3951 .get(src_cortical_id)
3952 .ok_or_else(|| {
3953 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
3954 })?
3955 .clone();
3956
3957 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
3959 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
3960 _ => return Ok(0), };
3962
3963 let src_cortical_idx = *self
3964 .cortical_id_to_idx
3965 .get(src_cortical_id)
3966 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
3967
3968 let mut total_synapses = 0u32;
3969 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
3973 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
3975 Ok(id) => id,
3976 Err(_) => {
3977 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
3978 continue;
3979 }
3980 };
3981
3982 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
3984 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
3985 continue;
3986 }
3987
3988 let synapse_count =
3990 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
3991 total_synapses += synapse_count as u32;
3992
3993 upstream_updates.push((dst_cortical_id, src_cortical_idx));
3996 }
3997
3998 for (dst_id, src_idx) in upstream_updates {
4000 self.add_upstream_area(&dst_id, src_idx);
4001 }
4002
4003 trace!(
4004 target: "feagi-bdu",
4005 "Created {} synapses for area {} via NPU",
4006 total_synapses,
4007 src_cortical_id
4008 );
4009
4010 if total_synapses > 0 {
4013 let mut cache = self.cached_synapse_counts_per_area.write();
4014 cache
4015 .entry(*src_cortical_id)
4016 .or_insert_with(|| AtomicUsize::new(0))
4017 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4018 }
4019
4020 self.cached_synapse_count
4022 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4023
4024 if total_synapses > 0 {
4026 if let Some(state_manager) = StateManager::instance().try_read() {
4027 let core_state = state_manager.get_core_state();
4028 core_state.add_synapse_count(total_synapses);
4029 }
4030 }
4031
4032 Ok(total_synapses)
4033 }
4034
4035 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4054 if let Some(ref npu) = self.npu {
4055 if let Ok(npu_lock) = npu.lock() {
4056 npu_lock.is_neuron_valid(neuron_id as u32)
4058 } else {
4059 false
4060 }
4061 } else {
4062 false
4063 }
4064 }
4065
4066 pub fn get_neuron_count(&self) -> usize {
4078 if let Some(ref npu) = self.npu {
4080 if let Ok(npu_lock) = npu.try_lock() {
4081 let fresh_count = npu_lock.get_neuron_count();
4082 self.cached_neuron_count
4083 .store(fresh_count, Ordering::Relaxed);
4084 }
4085 }
4087
4088 self.cached_neuron_count.load(Ordering::Relaxed)
4090 }
4091
4092 pub fn update_cached_neuron_count(&self) {
4098 if let Some(ref npu) = self.npu {
4099 if let Ok(npu_lock) = npu.try_lock() {
4100 let count = npu_lock.get_neuron_count();
4101 self.cached_neuron_count.store(count, Ordering::Relaxed);
4102 }
4103 }
4104 }
4105
4106 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4110 let npu = self.npu.as_ref()?;
4111 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4112 let npu_lock = npu.lock().ok()?;
4113 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4114 drop(npu_lock);
4115
4116 let mut cache = self.cached_neuron_counts_per_area.write();
4117 cache
4118 .entry(*cortical_id)
4119 .or_insert_with(|| AtomicUsize::new(0))
4120 .store(count, Ordering::Relaxed);
4121
4122 if let Some(state_manager) = StateManager::instance().try_read() {
4124 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4125 }
4126
4127 self.update_cached_neuron_count();
4128
4129 Some(count)
4130 }
4131
4132 pub fn get_synapse_count(&self) -> usize {
4144 if let Some(ref npu) = self.npu {
4146 if let Ok(npu_lock) = npu.try_lock() {
4147 let fresh_count = npu_lock.get_synapse_count();
4148 self.cached_synapse_count
4149 .store(fresh_count, Ordering::Relaxed);
4150 }
4151 }
4153
4154 self.cached_synapse_count.load(Ordering::Relaxed)
4156 }
4157
4158 pub fn update_cached_synapse_count(&self) {
4164 if let Some(ref npu) = self.npu {
4165 if let Ok(npu_lock) = npu.try_lock() {
4166 let count = npu_lock.get_synapse_count();
4167 self.cached_synapse_count.store(count, Ordering::Relaxed);
4168 }
4169 }
4170 }
4171
4172 pub fn update_all_cached_stats(&self) {
4178 self.update_cached_neuron_count();
4179 self.update_cached_synapse_count();
4180 }
4181
4182 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4193 if let Some(ref npu) = self.npu {
4194 if let Ok(npu_lock) = npu.lock() {
4195 npu_lock
4196 .get_neuron_coordinates(neuron_id as u32)
4197 .unwrap_or((0, 0, 0))
4198 } else {
4199 (0, 0, 0)
4200 }
4201 } else {
4202 (0, 0, 0)
4203 }
4204 }
4205
4206 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
4217 if let Some(ref npu) = self.npu {
4218 if let Ok(npu_lock) = npu.lock() {
4219 npu_lock.get_neuron_cortical_area(neuron_id as u32)
4220 } else {
4221 0
4222 }
4223 } else {
4224 0
4225 }
4226 }
4227
4228 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4239 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4241 Some(idx) => *idx,
4242 None => return Vec::new(),
4243 };
4244
4245 if let Some(ref npu) = self.npu {
4246 if let Ok(npu_lock) = npu.lock() {
4247 npu_lock
4249 .get_neurons_in_cortical_area(cortical_idx)
4250 .into_iter()
4251 .map(|id| id as u64)
4252 .collect()
4253 } else {
4254 Vec::new()
4255 }
4256 } else {
4257 Vec::new()
4258 }
4259 }
4260
4261 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4272 if let Some(ref npu) = self.npu {
4273 if let Ok(npu_lock) = npu.lock() {
4274 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4275 } else {
4276 Vec::new()
4277 }
4278 } else {
4279 Vec::new()
4280 }
4281 }
4282
4283 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4294 if let Some(ref npu) = self.npu {
4295 if let Ok(npu_lock) = npu.lock() {
4296 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4297 } else {
4298 Vec::new()
4299 }
4300 } else {
4301 Vec::new()
4302 }
4303 }
4304
4305 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4331 let cache = self.cached_neuron_counts_per_area.read();
4333 let base_count = cache
4334 .get(cortical_id)
4335 .map(|count| count.load(Ordering::Relaxed))
4336 .unwrap_or(0);
4337
4338 let memory_count = self
4340 .cortical_areas
4341 .get(cortical_id)
4342 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4343 .and_then(|_| {
4344 StateManager::instance()
4345 .try_read()
4346 .and_then(|state_manager| {
4347 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4348 })
4349 })
4350 .map(|stats| stats.neuron_count)
4351 .unwrap_or(0);
4352
4353 base_count.saturating_add(memory_count)
4354 }
4355
4356 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4363 let mut result = Vec::new();
4364
4365 for cortical_id in self.cortical_areas.keys() {
4366 let count = self.get_neuron_count_in_area(cortical_id);
4367 if count > 0 {
4368 result.push((cortical_id.to_string(), count));
4369 }
4370 }
4371
4372 result
4373 }
4374
4375 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4386 self.get_neuron_count_in_area(cortical_id) > 0
4387 }
4388
4389 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4405 let cache = self.cached_synapse_counts_per_area.read();
4407 cache
4408 .get(cortical_id)
4409 .map(|count| count.load(Ordering::Relaxed))
4410 .unwrap_or(0)
4411 }
4412
4413 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4423 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4424 return 0;
4425 }
4426
4427 if let Some(state_manager) = StateManager::instance().try_read() {
4428 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4429 return stats.incoming_synapse_count;
4430 }
4431 }
4432
4433 0
4434 }
4435
4436 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4446 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4447 return 0;
4448 }
4449
4450 if let Some(state_manager) = StateManager::instance().try_read() {
4451 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4452 return stats.outgoing_synapse_count;
4453 }
4454 }
4455
4456 0
4457 }
4458
4459 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4471 let synapses = self.get_outgoing_synapses(source_neuron_id);
4472 synapses
4473 .iter()
4474 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4475 }
4476
4477 pub fn get_connection_weight(
4489 &self,
4490 source_neuron_id: u64,
4491 target_neuron_id: u64,
4492 ) -> Option<u8> {
4493 let synapses = self.get_outgoing_synapses(source_neuron_id);
4494 synapses
4495 .iter()
4496 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4497 .map(|(_, weight, _, _)| *weight)
4498 }
4499
4500 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4511 let neurons = self.get_neurons_in_area(cortical_id);
4512 let neuron_count = neurons.len();
4513
4514 if neuron_count == 0 {
4515 return (0, 0, 0.0);
4516 }
4517
4518 let mut total_synapses = 0;
4519 for neuron_id in neurons {
4520 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4521 }
4522
4523 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4524
4525 (neuron_count, total_synapses, avg_synapses)
4526 }
4527
4528 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4539 let cortical_idx = self.get_neuron_cortical_idx(neuron_id);
4540 self.cortical_idx_to_id.get(&cortical_idx).copied()
4541 }
4542
4543 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4554 let area = match self.cortical_areas.get(cortical_id) {
4555 Some(a) => a,
4556 None => return 0.0,
4557 };
4558
4559 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4560 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4561
4562 if volume == 0 {
4563 return 0.0;
4564 }
4565
4566 neuron_count as f32 / volume as f32
4567 }
4568
4569 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
4576 let mut stats = Vec::new();
4577
4578 for cortical_id in self.cortical_areas.keys() {
4579 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4580 let synapse_count = self.get_synapse_count_in_area(cortical_id);
4581 let density = self.get_neuron_density(cortical_id);
4582
4583 stats.push((
4584 cortical_id.to_string(),
4585 neuron_count,
4586 synapse_count,
4587 density,
4588 ));
4589 }
4590
4591 stats
4592 }
4593
4594 pub fn get_config(&self) -> &ConnectomeConfig {
4600 &self.config
4601 }
4602
4603 pub fn set_config(&mut self, config: ConnectomeConfig) {
4605 self.config = config;
4606 }
4607
4608 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
4627 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
4628
4629 use feagi_structures::genomic::cortical_area::{
4630 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
4631 };
4632
4633 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
4635 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
4636 })?;
4637
4638 let core_position = (0, 0, 0).into();
4640
4641 let death_id = CoreCorticalType::Death.to_cortical_id();
4643 if !self.cortical_areas.contains_key(&death_id) {
4644 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
4645 let death_area = CorticalArea::new(
4646 death_id,
4647 0, "_death".to_string(),
4649 core_dimensions,
4650 core_position,
4651 CorticalAreaType::Core(CoreCorticalType::Death),
4652 )
4653 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
4654 match self.add_cortical_area(death_area) {
4655 Ok(idx) => {
4656 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
4657 }
4658 Err(e) => {
4659 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
4660 return Err(e);
4661 }
4662 }
4663 } else {
4664 info!(target: "feagi-bdu", " ✓ _death area already exists");
4665 }
4666
4667 let power_id = CoreCorticalType::Power.to_cortical_id();
4669 if !self.cortical_areas.contains_key(&power_id) {
4670 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
4671 let power_area = CorticalArea::new(
4672 power_id,
4673 1, "_power".to_string(),
4675 core_dimensions,
4676 core_position,
4677 CorticalAreaType::Core(CoreCorticalType::Power),
4678 )
4679 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
4680 match self.add_cortical_area(power_area) {
4681 Ok(idx) => {
4682 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
4683 }
4684 Err(e) => {
4685 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
4686 return Err(e);
4687 }
4688 }
4689 } else {
4690 info!(target: "feagi-bdu", " ✓ _power area already exists");
4691 }
4692
4693 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
4695 if !self.cortical_areas.contains_key(&fatigue_id) {
4696 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
4697 let fatigue_area = CorticalArea::new(
4698 fatigue_id,
4699 2, "_fatigue".to_string(),
4701 core_dimensions,
4702 core_position,
4703 CorticalAreaType::Core(CoreCorticalType::Fatigue),
4704 )
4705 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
4706 match self.add_cortical_area(fatigue_area) {
4707 Ok(idx) => {
4708 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
4709 }
4710 Err(e) => {
4711 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
4712 return Err(e);
4713 }
4714 }
4715 } else {
4716 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
4717 }
4718
4719 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
4720 Ok(())
4721 }
4722
4723 #[deprecated(
4740 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
4741 )]
4742 #[allow(deprecated)]
4743 pub fn save_genome_to_json(
4744 &self,
4745 genome_id: Option<String>,
4746 genome_title: Option<String>,
4747 ) -> BduResult<String> {
4748 let mut brain_regions_with_parents = std::collections::HashMap::new();
4750
4751 for region_id in self.brain_regions.get_all_region_ids() {
4752 if let Some(region) = self.brain_regions.get_region(region_id) {
4753 let parent_id = self
4754 .brain_regions
4755 .get_parent(region_id)
4756 .map(|s| s.to_string());
4757 brain_regions_with_parents
4758 .insert(region_id.to_string(), (region.clone(), parent_id));
4759 }
4760 }
4761
4762 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
4764 &self.cortical_areas,
4765 &brain_regions_with_parents,
4766 genome_id,
4767 genome_title,
4768 )?)
4769 }
4770
4771 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
4802 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
4803
4804 self.cortical_areas.clear();
4806 self.cortical_id_to_idx.clear();
4807 self.cortical_idx_to_id.clear();
4808 self.next_cortical_idx = 3;
4810 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4811
4812 self.brain_regions = BrainRegionHierarchy::new();
4814
4815 if let Some(ref npu) = self.npu {
4817 let mut npu_lock = npu
4818 .lock()
4819 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4820 npu_lock
4821 .reset_for_new_genome()
4822 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
4823 }
4824
4825 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
4826 Ok(())
4827 }
4828
4829 pub fn resize_for_genome(
4839 &mut self,
4840 genome: &feagi_evolutionary::RuntimeGenome,
4841 ) -> BduResult<()> {
4842 self.morphology_registry = genome.morphologies.clone();
4844 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
4845
4846 let required_neurons = genome.stats.innate_neuron_count;
4848 let required_synapses = genome.stats.innate_synapse_count;
4849
4850 info!(target: "feagi-bdu",
4851 "Genome requires: {} neurons, {} synapses",
4852 required_neurons,
4853 required_synapses
4854 );
4855
4856 let mut total_voxels = 0;
4858 for area in genome.cortical_areas.values() {
4859 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4860 }
4861
4862 info!(target: "feagi-bdu",
4863 "Genome has {} cortical areas with {} total voxels",
4864 genome.cortical_areas.len(),
4865 total_voxels
4866 );
4867
4868 Ok(())
4873 }
4874
4875 pub fn create_synapse(
4894 &mut self,
4895 source_neuron_id: u64,
4896 target_neuron_id: u64,
4897 weight: u8,
4898 psp: u8,
4899 synapse_type: u8,
4900 ) -> BduResult<()> {
4901 let npu = self
4903 .npu
4904 .as_ref()
4905 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4906
4907 let mut npu_lock = npu
4908 .lock()
4909 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4910
4911 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4913 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4914
4915 if !source_exists {
4916 return Err(BduError::InvalidNeuron(format!(
4917 "Source neuron {} not found",
4918 source_neuron_id
4919 )));
4920 }
4921 if !target_exists {
4922 return Err(BduError::InvalidNeuron(format!(
4923 "Target neuron {} not found",
4924 target_neuron_id
4925 )));
4926 }
4927
4928 let syn_type = if synapse_type == 0 {
4930 feagi_npu_neural::synapse::SynapseType::Excitatory
4931 } else {
4932 feagi_npu_neural::synapse::SynapseType::Inhibitory
4933 };
4934
4935 let synapse_idx = npu_lock
4936 .add_synapse(
4937 NeuronId(source_neuron_id as u32),
4938 NeuronId(target_neuron_id as u32),
4939 feagi_npu_neural::types::SynapticWeight(weight),
4940 feagi_npu_neural::types::SynapticPsp(psp),
4941 syn_type,
4942 )
4943 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
4944
4945 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
4946 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
4947
4948 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
4949 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
4950 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
4951 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
4952
4953 if let Some(state_manager) = StateManager::instance().try_read() {
4954 let core_state = state_manager.get_core_state();
4955 core_state.add_synapse_count(1);
4956 if let Some(cortical_id) = source_cortical_id {
4957 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
4958 }
4959 if let Some(cortical_id) = target_cortical_id {
4960 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
4961 }
4962 }
4963
4964 Ok(())
4969 }
4970
4971 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
4974 if let Some(ref npu) = self.npu {
4975 if let Ok(mut npu_lock) = npu.lock() {
4976 let mut psp_uniform_flags = ahash::AHashMap::new();
4978 let mut mp_driven_psp_flags = ahash::AHashMap::new();
4979
4980 for (cortical_id, area) in &self.cortical_areas {
4981 let default_psp_uniform =
4983 *cortical_id == CoreCorticalType::Power.to_cortical_id();
4984 let psp_uniform = area
4985 .get_property("psp_uniform_distribution")
4986 .and_then(|v| v.as_bool())
4987 .unwrap_or(default_psp_uniform);
4988 psp_uniform_flags.insert(*cortical_id, psp_uniform);
4989
4990 let mp_driven_psp = area
4992 .get_property("mp_driven_psp")
4993 .and_then(|v| v.as_bool())
4994 .unwrap_or(false);
4995 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
4996 }
4997
4998 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
5000 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5001
5002 trace!(
5003 target: "feagi-bdu",
5004 "Synchronized cortical area flags to NPU ({} areas)",
5005 self.cortical_areas.len()
5006 );
5007 }
5008 }
5009
5010 Ok(())
5011 }
5012
5013 pub fn get_synapse(
5025 &self,
5026 source_neuron_id: u64,
5027 target_neuron_id: u64,
5028 ) -> Option<(u8, u8, u8)> {
5029 let npu = self.npu.as_ref()?;
5031 let npu_lock = npu.lock().ok()?;
5032
5033 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
5036
5037 for (source_id, weight, psp, synapse_type) in incoming {
5039 if source_id == source_neuron_id as u32 {
5040 return Some((weight, psp, synapse_type));
5041 }
5042 }
5043
5044 None
5045 }
5046
5047 pub fn update_synapse_weight(
5060 &mut self,
5061 source_neuron_id: u64,
5062 target_neuron_id: u64,
5063 new_weight: u8,
5064 ) -> BduResult<()> {
5065 let npu = self
5067 .npu
5068 .as_ref()
5069 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5070
5071 let mut npu_lock = npu
5072 .lock()
5073 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5074
5075 let updated = npu_lock.update_synapse_weight(
5077 NeuronId(source_neuron_id as u32),
5078 NeuronId(target_neuron_id as u32),
5079 feagi_npu_neural::types::SynapticWeight(new_weight),
5080 );
5081
5082 if updated {
5083 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
5084 Ok(())
5085 } else {
5086 Err(BduError::InvalidSynapse(format!(
5087 "Synapse {} -> {} not found",
5088 source_neuron_id, target_neuron_id
5089 )))
5090 }
5091 }
5092
5093 pub fn remove_synapse(
5105 &mut self,
5106 source_neuron_id: u64,
5107 target_neuron_id: u64,
5108 ) -> BduResult<bool> {
5109 let npu = self
5111 .npu
5112 .as_ref()
5113 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5114
5115 let mut npu_lock = npu
5116 .lock()
5117 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5118
5119 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5120 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5121 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
5122 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
5123
5124 let removed = npu_lock.remove_synapse(
5126 NeuronId(source_neuron_id as u32),
5127 NeuronId(target_neuron_id as u32),
5128 );
5129
5130 if removed {
5131 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
5132
5133 if let Some(state_manager) = StateManager::instance().try_read() {
5135 let core_state = state_manager.get_core_state();
5136 core_state.subtract_synapse_count(1);
5137 if let Some(cortical_id) = source_cortical_id {
5138 state_manager
5139 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5140 }
5141 if let Some(cortical_id) = target_cortical_id {
5142 state_manager
5143 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5144 }
5145 }
5146 }
5147
5148 Ok(removed)
5149 }
5150
5151 pub fn batch_create_neurons(
5169 &mut self,
5170 cortical_id: &CorticalID,
5171 neurons: Vec<NeuronData>,
5172 ) -> BduResult<Vec<u64>> {
5173 let npu = self
5175 .npu
5176 .as_ref()
5177 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5178
5179 let mut npu_lock = npu
5180 .lock()
5181 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5182
5183 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5185 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5186 })?;
5187 let cortical_idx = area.cortical_idx;
5188
5189 let count = neurons.len();
5190
5191 let mut x_coords = Vec::with_capacity(count);
5193 let mut y_coords = Vec::with_capacity(count);
5194 let mut z_coords = Vec::with_capacity(count);
5195 let mut firing_thresholds = Vec::with_capacity(count);
5196 let mut threshold_limits = Vec::with_capacity(count);
5197 let mut leak_coeffs = Vec::with_capacity(count);
5198 let mut resting_potentials = Vec::with_capacity(count);
5199 let mut neuron_types = Vec::with_capacity(count);
5200 let mut refractory_periods = Vec::with_capacity(count);
5201 let mut excitabilities = Vec::with_capacity(count);
5202 let mut consec_fire_limits = Vec::with_capacity(count);
5203 let mut snooze_lengths = Vec::with_capacity(count);
5204 let mut mp_accums = Vec::with_capacity(count);
5205 let mut cortical_areas = Vec::with_capacity(count);
5206
5207 for (
5208 x,
5209 y,
5210 z,
5211 threshold,
5212 threshold_limit,
5213 leak,
5214 resting,
5215 ntype,
5216 refract,
5217 excit,
5218 consec_limit,
5219 snooze,
5220 mp_accum,
5221 ) in neurons
5222 {
5223 x_coords.push(x);
5224 y_coords.push(y);
5225 z_coords.push(z);
5226 firing_thresholds.push(threshold);
5227 threshold_limits.push(threshold_limit);
5228 leak_coeffs.push(leak);
5229 resting_potentials.push(resting);
5230 neuron_types.push(ntype);
5231 refractory_periods.push(refract);
5232 excitabilities.push(excit);
5233 consec_fire_limits.push(consec_limit);
5234 snooze_lengths.push(snooze);
5235 mp_accums.push(mp_accum);
5236 cortical_areas.push(cortical_idx);
5237 }
5238
5239 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5241
5242 let firing_thresholds_t = firing_thresholds;
5247 let threshold_limits_t = threshold_limits;
5248 let resting_potentials_t = resting_potentials;
5249 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5250 firing_thresholds_t,
5251 threshold_limits_t,
5252 leak_coeffs,
5253 resting_potentials_t,
5254 neuron_types,
5255 refractory_periods,
5256 excitabilities,
5257 consec_fire_limits,
5258 snooze_lengths,
5259 mp_accums,
5260 cortical_areas,
5261 x_coords,
5262 y_coords,
5263 z_coords,
5264 );
5265
5266 let mut neuron_ids = Vec::with_capacity(count);
5268 for i in 0..neurons_created {
5269 neuron_ids.push((first_neuron_id + i) as u64);
5270 }
5271
5272 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5273
5274 if let Some(state_manager) = StateManager::instance().try_read() {
5276 let core_state = state_manager.get_core_state();
5277 core_state.add_neuron_count(neurons_created);
5278 core_state.add_regular_neuron_count(neurons_created);
5279 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5280 }
5281
5282 {
5284 let mut cache = self.cached_neuron_counts_per_area.write();
5285 cache
5286 .entry(*cortical_id)
5287 .or_insert_with(|| AtomicUsize::new(0))
5288 .fetch_add(count, Ordering::Relaxed);
5289 }
5290
5291 Ok(neuron_ids)
5292 }
5293
5294 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5305 let npu = self
5307 .npu
5308 .as_ref()
5309 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5310
5311 let mut npu_lock = npu
5312 .lock()
5313 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5314
5315 let mut deleted_count = 0;
5316 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5317 std::collections::HashMap::new();
5318
5319 for neuron_id in neuron_ids {
5322 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5323 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
5324
5325 if npu_lock.delete_neuron(neuron_id as u32) {
5326 deleted_count += 1;
5327 if let Some(cortical_id) = cortical_id {
5328 let key = cortical_id.as_base_64();
5329 *per_area_deleted.entry(key).or_insert(0) += 1;
5330 }
5331 }
5332 }
5333
5334 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5335
5336 if deleted_count > 0 {
5338 if let Some(state_manager) = StateManager::instance().try_read() {
5339 let core_state = state_manager.get_core_state();
5340 core_state.subtract_neuron_count(deleted_count as u32);
5341 core_state.subtract_regular_neuron_count(deleted_count as u32);
5342 for (cortical_id, count) in per_area_deleted {
5343 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5344 }
5345 }
5346 }
5347
5348 Ok(deleted_count)
5355 }
5356
5357 pub fn update_neuron_properties(
5376 &mut self,
5377 neuron_id: u64,
5378 firing_threshold: Option<f32>,
5379 leak_coefficient: Option<f32>,
5380 resting_potential: Option<f32>,
5381 excitability: Option<f32>,
5382 ) -> BduResult<()> {
5383 let npu = self
5385 .npu
5386 .as_ref()
5387 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5388
5389 let mut npu_lock = npu
5390 .lock()
5391 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5392
5393 let neuron_id_u32 = neuron_id as u32;
5394
5395 let mut updated = false;
5397
5398 if let Some(threshold) = firing_threshold {
5400 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5401 updated = true;
5402 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5403 } else if !updated {
5404 return Err(BduError::InvalidNeuron(format!(
5405 "Neuron {} not found",
5406 neuron_id
5407 )));
5408 }
5409 }
5410
5411 if let Some(leak) = leak_coefficient {
5412 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5413 updated = true;
5414 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5415 } else if !updated {
5416 return Err(BduError::InvalidNeuron(format!(
5417 "Neuron {} not found",
5418 neuron_id
5419 )));
5420 }
5421 }
5422
5423 if let Some(resting) = resting_potential {
5424 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5425 updated = true;
5426 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5427 } else if !updated {
5428 return Err(BduError::InvalidNeuron(format!(
5429 "Neuron {} not found",
5430 neuron_id
5431 )));
5432 }
5433 }
5434
5435 if let Some(excit) = excitability {
5436 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
5437 updated = true;
5438 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
5439 } else if !updated {
5440 return Err(BduError::InvalidNeuron(format!(
5441 "Neuron {} not found",
5442 neuron_id
5443 )));
5444 }
5445 }
5446
5447 if !updated {
5448 return Err(BduError::Internal(
5449 "No properties provided for update".to_string(),
5450 ));
5451 }
5452
5453 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
5454
5455 Ok(())
5456 }
5457
5458 pub fn set_neuron_firing_threshold(
5470 &mut self,
5471 neuron_id: u64,
5472 new_threshold: f32,
5473 ) -> BduResult<()> {
5474 let npu = self
5476 .npu
5477 .as_ref()
5478 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5479
5480 let mut npu_lock = npu
5481 .lock()
5482 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5483
5484 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
5486 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
5487 Ok(())
5488 } else {
5489 Err(BduError::InvalidNeuron(format!(
5490 "Neuron {} not found",
5491 neuron_id
5492 )))
5493 }
5494 }
5495
5496 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
5511 self.cortical_areas
5512 .values()
5513 .find(|area| area.name == name)
5514 .cloned()
5515 }
5516
5517 pub fn resize_cortical_area(
5534 &mut self,
5535 cortical_id: &CorticalID,
5536 new_dimensions: CorticalAreaDimensions,
5537 ) -> BduResult<()> {
5538 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
5540 return Err(BduError::InvalidArea(format!(
5541 "Invalid dimensions: {:?} (all must be > 0)",
5542 new_dimensions
5543 )));
5544 }
5545
5546 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
5548 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5549 })?;
5550
5551 let old_dimensions = area.dimensions;
5552 area.dimensions = new_dimensions;
5553
5554 info!(target: "feagi-bdu",
5558 "Resized cortical area {} from {:?} to {:?}",
5559 cortical_id,
5560 old_dimensions,
5561 new_dimensions
5562 );
5563
5564 self.refresh_cortical_area_hashes(false, true);
5565
5566 Ok(())
5567 }
5568
5569 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
5580 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
5581 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5582 })?;
5583
5584 Ok(region
5586 .cortical_areas
5587 .iter()
5588 .map(|id| id.as_base_64())
5589 .collect())
5590 }
5591
5592 pub fn update_brain_region(
5605 &mut self,
5606 region_id: &str,
5607 new_name: Option<String>,
5608 new_description: Option<String>,
5609 ) -> BduResult<()> {
5610 let region = self
5611 .brain_regions
5612 .get_region_mut(region_id)
5613 .ok_or_else(|| {
5614 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5615 })?;
5616
5617 if let Some(name) = new_name {
5618 region.name = name;
5619 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
5620 }
5621
5622 if let Some(desc) = new_description {
5623 region
5625 .properties
5626 .insert("description".to_string(), serde_json::json!(desc));
5627 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
5628 }
5629
5630 info!(target: "feagi-bdu","Updated brain region {}", region_id);
5631
5632 self.refresh_brain_regions_hash();
5633
5634 Ok(())
5635 }
5636
5637 pub fn update_brain_region_properties(
5651 &mut self,
5652 region_id: &str,
5653 properties: std::collections::HashMap<String, serde_json::Value>,
5654 ) -> BduResult<()> {
5655 use tracing::{debug, info};
5656
5657 let region = self
5658 .brain_regions
5659 .get_region_mut(region_id)
5660 .ok_or_else(|| {
5661 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5662 })?;
5663
5664 for (key, value) in properties {
5665 match key.as_str() {
5666 "title" | "name" => {
5667 if let Some(name) = value.as_str() {
5668 region.name = name.to_string();
5669 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
5670 }
5671 }
5672 "coordinate_3d" | "coordinates_3d" => {
5673 region
5674 .properties
5675 .insert("coordinate_3d".to_string(), value.clone());
5676 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
5677 }
5678 "coordinate_2d" | "coordinates_2d" => {
5679 region
5680 .properties
5681 .insert("coordinate_2d".to_string(), value.clone());
5682 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
5683 }
5684 "description" => {
5685 region
5686 .properties
5687 .insert("description".to_string(), value.clone());
5688 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
5689 }
5690 "region_type" => {
5691 if let Some(type_str) = value.as_str() {
5692 region.region_type = feagi_structures::genomic::RegionType::Undefined;
5695 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
5696 }
5697 }
5698 _ => {
5700 region.properties.insert(key.clone(), value.clone());
5701 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
5702 }
5703 }
5704 }
5705
5706 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
5707
5708 Ok(())
5709 }
5710
5711 pub fn get_neuron_by_coordinates(
5729 &self,
5730 cortical_id: &CorticalID,
5731 x: u32,
5732 y: u32,
5733 z: u32,
5734 ) -> Option<u64> {
5735 let area = self.get_cortical_area(cortical_id)?;
5737 let cortical_idx = area.cortical_idx;
5738
5739 let npu = self.npu.as_ref()?;
5741 let npu_lock = npu.lock().ok()?;
5742
5743 npu_lock
5744 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
5745 .map(|id| id as u64)
5746 }
5747
5748 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
5759 let npu = self.npu.as_ref()?;
5760 let npu_lock = npu.lock().ok()?;
5761
5762 let neuron_count = npu_lock.get_neuron_count();
5764 if (neuron_id as usize) >= neuron_count {
5765 return None;
5766 }
5767
5768 Some(
5769 npu_lock
5770 .get_neuron_coordinates(neuron_id as u32)
5771 .unwrap_or((0, 0, 0)),
5772 )
5773 }
5774
5775 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
5786 let npu = self.npu.as_ref()?;
5787 let npu_lock = npu.lock().ok()?;
5788
5789 let neuron_count = npu_lock.get_neuron_count();
5791 if (neuron_id as usize) >= neuron_count {
5792 return None;
5793 }
5794
5795 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5796
5797 self.cortical_areas
5799 .values()
5800 .find(|area| area.cortical_idx == cortical_idx)
5801 .map(|area| area.cortical_id)
5802 }
5803
5804 pub fn get_neuron_properties(
5815 &self,
5816 neuron_id: u64,
5817 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5818 let npu = self.npu.as_ref()?;
5819 let npu_lock = npu.lock().ok()?;
5820
5821 let neuron_id_u32 = neuron_id as u32;
5822 let idx = neuron_id as usize;
5823
5824 let neuron_count = npu_lock.get_neuron_count();
5826 if idx >= neuron_count {
5827 return None;
5828 }
5829
5830 let mut properties = std::collections::HashMap::new();
5831
5832 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
5834
5835 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
5837 properties.insert("x".to_string(), serde_json::json!(x));
5838 properties.insert("y".to_string(), serde_json::json!(y));
5839 properties.insert("z".to_string(), serde_json::json!(z));
5840
5841 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32);
5843 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
5844
5845 if let Some((consec_count, consec_limit, snooze, mp, threshold, refract_countdown)) =
5847 npu_lock.get_neuron_state(NeuronId(neuron_id_u32))
5848 {
5849 properties.insert(
5850 "consecutive_fire_count".to_string(),
5851 serde_json::json!(consec_count),
5852 );
5853 properties.insert(
5854 "consecutive_fire_limit".to_string(),
5855 serde_json::json!(consec_limit),
5856 );
5857 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
5858 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
5859 properties.insert("threshold".to_string(), serde_json::json!(threshold));
5860 properties.insert(
5861 "refractory_countdown".to_string(),
5862 serde_json::json!(refract_countdown),
5863 );
5864 }
5865
5866 if let Some(leak) = npu_lock.get_neuron_property_by_index(idx, "leak_coefficient") {
5868 properties.insert("leak_coefficient".to_string(), serde_json::json!(leak));
5869 }
5870 if let Some(resting) = npu_lock.get_neuron_property_by_index(idx, "resting_potential") {
5871 properties.insert("resting_potential".to_string(), serde_json::json!(resting));
5872 }
5873 if let Some(excit) = npu_lock.get_neuron_property_by_index(idx, "excitability") {
5874 properties.insert("excitability".to_string(), serde_json::json!(excit));
5875 }
5876 if let Some(threshold_limit) = npu_lock.get_neuron_property_by_index(idx, "threshold_limit")
5877 {
5878 properties.insert(
5879 "threshold_limit".to_string(),
5880 serde_json::json!(threshold_limit),
5881 );
5882 }
5883
5884 if let Some(refract_period) =
5886 npu_lock.get_neuron_property_u16_by_index(idx, "refractory_period")
5887 {
5888 properties.insert(
5889 "refractory_period".to_string(),
5890 serde_json::json!(refract_period),
5891 );
5892 }
5893
5894 Some(properties)
5895 }
5896
5897 pub fn get_neuron_property(
5909 &self,
5910 neuron_id: u64,
5911 property_name: &str,
5912 ) -> Option<serde_json::Value> {
5913 self.get_neuron_properties(neuron_id)?
5914 .get(property_name)
5915 .cloned()
5916 }
5917
5918 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
5929 self.cortical_areas.keys().copied().collect()
5930 }
5931
5932 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
5939 self.cortical_areas
5940 .values()
5941 .map(|area| area.cortical_idx)
5942 .collect()
5943 }
5944
5945 pub fn get_cortical_area_names(&self) -> Vec<String> {
5952 self.cortical_areas
5953 .values()
5954 .map(|area| area.name.clone())
5955 .collect()
5956 }
5957
5958 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
5965 use crate::models::CorticalAreaExt;
5966 self.cortical_areas
5967 .values()
5968 .filter(|area| area.is_input_area())
5969 .map(|area| area.cortical_id)
5970 .collect()
5971 }
5972
5973 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
5980 use crate::models::CorticalAreaExt;
5981 self.cortical_areas
5982 .values()
5983 .filter(|area| area.is_output_area())
5984 .map(|area| area.cortical_id)
5985 .collect()
5986 }
5987
5988 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
5995 self.cortical_areas
5996 .values()
5997 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
5998 (
5999 max_w.max(area.dimensions.width as usize),
6000 max_h.max(area.dimensions.height as usize),
6001 max_d.max(area.dimensions.depth as usize),
6002 )
6003 })
6004 }
6005
6006 pub fn get_cortical_area_properties(
6017 &self,
6018 cortical_id: &CorticalID,
6019 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6020 let area = self.get_cortical_area(cortical_id)?;
6021
6022 let mut properties = std::collections::HashMap::new();
6023 properties.insert(
6024 "cortical_id".to_string(),
6025 serde_json::json!(area.cortical_id),
6026 );
6027 properties.insert(
6028 "cortical_id_s".to_string(),
6029 serde_json::json!(area.cortical_id.to_string()),
6030 );
6031 properties.insert(
6032 "cortical_idx".to_string(),
6033 serde_json::json!(area.cortical_idx),
6034 );
6035 properties.insert("name".to_string(), serde_json::json!(area.name));
6036 use crate::models::CorticalAreaExt;
6037 properties.insert(
6038 "area_type".to_string(),
6039 serde_json::json!(area.get_cortical_group()),
6040 );
6041 properties.insert(
6042 "dimensions".to_string(),
6043 serde_json::json!({
6044 "width": area.dimensions.width,
6045 "height": area.dimensions.height,
6046 "depth": area.dimensions.depth,
6047 }),
6048 );
6049 properties.insert("position".to_string(), serde_json::json!(area.position));
6050
6051 for (key, value) in &area.properties {
6053 properties.insert(key.clone(), value.clone());
6054 }
6055
6056 properties.extend(area.properties.clone());
6058
6059 Some(properties)
6060 }
6061
6062 pub fn get_all_cortical_area_properties(
6069 &self,
6070 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
6071 self.cortical_areas
6072 .keys()
6073 .filter_map(|id| self.get_cortical_area_properties(id))
6074 .collect()
6075 }
6076
6077 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
6088 self.brain_regions
6089 .get_all_region_ids()
6090 .into_iter()
6091 .cloned()
6092 .collect()
6093 }
6094
6095 pub fn get_brain_region_names(&self) -> Vec<String> {
6102 self.brain_regions
6103 .get_all_region_ids()
6104 .iter()
6105 .filter_map(|id| {
6106 self.brain_regions
6107 .get_region(id)
6108 .map(|region| region.name.clone())
6109 })
6110 .collect()
6111 }
6112
6113 pub fn get_brain_region_properties(
6124 &self,
6125 region_id: &str,
6126 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6127 let region = self.brain_regions.get_region(region_id)?;
6128
6129 let mut properties = std::collections::HashMap::new();
6130 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
6131 properties.insert("name".to_string(), serde_json::json!(region.name));
6132 properties.insert(
6133 "region_type".to_string(),
6134 serde_json::json!(format!("{:?}", region.region_type)),
6135 );
6136 properties.insert(
6137 "cortical_areas".to_string(),
6138 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
6139 );
6140
6141 properties.extend(region.properties.clone());
6143
6144 Some(properties)
6145 }
6146
6147 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
6158 self.cortical_areas.contains_key(cortical_id)
6159 }
6160
6161 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6172 self.brain_regions.get_region(region_id).is_some()
6173 }
6174
6175 pub fn get_brain_region_count(&self) -> usize {
6182 self.brain_regions.region_count()
6183 }
6184
6185 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6196 self.get_neurons_in_area(cortical_id)
6200 }
6201}
6202
6203impl std::fmt::Debug for ConnectomeManager {
6205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6206 f.debug_struct("ConnectomeManager")
6207 .field("cortical_areas", &self.cortical_areas.len())
6208 .field("next_cortical_idx", &self.next_cortical_idx)
6209 .field("brain_regions", &self.brain_regions)
6210 .field(
6211 "npu",
6212 &if self.npu.is_some() {
6213 "Connected"
6214 } else {
6215 "Not connected"
6216 },
6217 )
6218 .field("initialized", &self.initialized)
6219 .finish()
6220 }
6221}
6222
6223#[cfg(test)]
6224mod tests {
6225 use super::*;
6226 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6227
6228 #[test]
6229 fn test_singleton_instance() {
6230 let instance1 = ConnectomeManager::instance();
6231 let instance2 = ConnectomeManager::instance();
6232
6233 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6235 }
6236
6237 #[test]
6238 fn test_add_cortical_area() {
6239 ConnectomeManager::reset_for_testing();
6240
6241 let instance = ConnectomeManager::instance();
6242 let mut manager = instance.write();
6243
6244 use feagi_structures::genomic::cortical_area::{
6245 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6246 };
6247 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6249 let area = CorticalArea::new(
6250 cortical_id,
6251 0,
6252 "Visual Input".to_string(),
6253 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6254 (0, 0, 0).into(),
6255 cortical_type,
6256 )
6257 .unwrap();
6258
6259 let initial_count = manager.get_cortical_area_count();
6260 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6261
6262 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6263 assert!(manager.has_cortical_area(&cortical_id));
6264 assert!(manager.is_initialized());
6265 }
6266
6267 #[test]
6268 fn test_cortical_area_lookups() {
6269 ConnectomeManager::reset_for_testing();
6270
6271 let instance = ConnectomeManager::instance();
6272 let mut manager = instance.write();
6273
6274 use feagi_structures::genomic::cortical_area::{
6275 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6276 };
6277 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6279 let area = CorticalArea::new(
6280 cortical_id,
6281 0,
6282 "Test Area".to_string(),
6283 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6284 (0, 0, 0).into(),
6285 cortical_type,
6286 )
6287 .unwrap();
6288
6289 let cortical_idx = manager.add_cortical_area(area).unwrap();
6290
6291 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6293
6294 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6296
6297 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6299 assert_eq!(retrieved_area.name, "Test Area");
6300 }
6301
6302 #[test]
6303 fn test_remove_cortical_area() {
6304 ConnectomeManager::reset_for_testing();
6305
6306 let instance = ConnectomeManager::instance();
6307 let mut manager = instance.write();
6308
6309 use feagi_structures::genomic::cortical_area::{
6310 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6311 };
6312 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6313
6314 if manager.has_cortical_area(&cortical_id) {
6316 manager.remove_cortical_area(&cortical_id).unwrap();
6317 }
6318
6319 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6320 let area = CorticalArea::new(
6321 cortical_id,
6322 0,
6323 "Test".to_string(),
6324 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6325 (0, 0, 0).into(),
6326 cortical_type,
6327 )
6328 .unwrap();
6329
6330 let initial_count = manager.get_cortical_area_count();
6331 manager.add_cortical_area(area).unwrap();
6332 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6333
6334 manager.remove_cortical_area(&cortical_id).unwrap();
6335 assert_eq!(manager.get_cortical_area_count(), initial_count);
6336 assert!(!manager.has_cortical_area(&cortical_id));
6337 }
6338
6339 #[test]
6340 fn test_duplicate_area_error() {
6341 ConnectomeManager::reset_for_testing();
6342
6343 let instance = ConnectomeManager::instance();
6344 let mut manager = instance.write();
6345
6346 use feagi_structures::genomic::cortical_area::{
6347 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6348 };
6349 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
6352 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6353 let area1 = CorticalArea::new(
6354 cortical_id,
6355 0,
6356 "First".to_string(),
6357 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6358 (0, 0, 0).into(),
6359 cortical_type,
6360 )
6361 .unwrap();
6362
6363 let area2 = CorticalArea::new(
6364 cortical_id, 1,
6366 "Second".to_string(),
6367 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6368 (0, 0, 0).into(),
6369 cortical_type,
6370 )
6371 .unwrap();
6372
6373 manager.add_cortical_area(area1).unwrap();
6374 let result = manager.add_cortical_area(area2);
6375
6376 assert!(result.is_err());
6377 }
6378
6379 #[test]
6380 fn test_brain_region_management() {
6381 ConnectomeManager::reset_for_testing();
6382
6383 let instance = ConnectomeManager::instance();
6384 let mut manager = instance.write();
6385
6386 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
6387 let region_id_str = region_id.to_string();
6388 let root = BrainRegion::new(
6389 region_id,
6390 "Root".to_string(),
6391 feagi_structures::genomic::brain_regions::RegionType::Undefined,
6392 )
6393 .unwrap();
6394
6395 let initial_count = manager.get_brain_region_ids().len();
6396 manager.add_brain_region(root, None).unwrap();
6397
6398 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
6399 assert!(manager.get_brain_region(®ion_id_str).is_some());
6400 }
6401
6402 #[test]
6403 fn test_synapse_operations() {
6404 use feagi_npu_burst_engine::npu::RustNPU;
6405 use feagi_npu_burst_engine::TracingMutex;
6406 use std::sync::Arc;
6407
6408 use feagi_npu_burst_engine::backend::CPUBackend;
6410 use feagi_npu_burst_engine::DynamicNPU;
6411 use feagi_npu_runtime::StdRuntime;
6412
6413 let runtime = StdRuntime;
6414 let backend = CPUBackend::new();
6415 let npu_result =
6416 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
6417 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
6418 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
6419
6420 use feagi_structures::genomic::cortical_area::{
6422 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6423 };
6424 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6426 let area = CorticalArea::new(
6427 cortical_id,
6428 0, "Test Area".to_string(),
6430 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
6431 (0, 0, 0).into(), cortical_type,
6433 )
6434 .unwrap();
6435 let cortical_idx = manager.add_cortical_area(area).unwrap();
6436
6437 if let Some(npu_arc) = manager.get_npu() {
6439 if let Ok(mut npu_guard) = npu_arc.try_lock() {
6440 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
6441 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
6442 }
6443 }
6444 }
6445
6446 let neuron1_id = manager
6448 .add_neuron(
6449 &cortical_id,
6450 0,
6451 0,
6452 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
6464 .unwrap();
6465
6466 let neuron2_id = manager
6467 .add_neuron(
6468 &cortical_id,
6469 1,
6470 0,
6471 0, 100.0,
6473 f32::MAX, 0.1,
6475 -60.0,
6476 0,
6477 2,
6478 1.0,
6479 5,
6480 10,
6481 false,
6482 )
6483 .unwrap();
6484
6485 manager
6487 .create_synapse(
6488 neuron1_id, neuron2_id, 128, 64, 0, )
6492 .unwrap();
6493
6494 println!("✅ Synapse creation test passed");
6497 }
6498
6499 #[test]
6500 fn test_apply_cortical_mapping_missing_rules_is_ok() {
6501 let mut manager = ConnectomeManager::new_for_testing();
6504
6505 use feagi_structures::genomic::cortical_area::{
6506 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6507 };
6508
6509 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
6510 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
6511
6512 let src_area = CorticalArea::new(
6513 src_id,
6514 0,
6515 "src".to_string(),
6516 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6517 (0, 0, 0).into(),
6518 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6519 )
6520 .unwrap();
6521
6522 let dst_area = CorticalArea::new(
6523 dst_id,
6524 1,
6525 "dst".to_string(),
6526 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6527 (0, 0, 0).into(),
6528 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6529 )
6530 .unwrap();
6531
6532 manager.add_cortical_area(src_area).unwrap();
6533 manager.add_cortical_area(dst_area).unwrap();
6534
6535 let count = manager
6537 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6538 .unwrap();
6539 assert_eq!(count, 0);
6540
6541 manager
6543 .update_cortical_mapping(
6544 &src_id,
6545 &dst_id,
6546 vec![serde_json::json!({"morphology_id":"m1"})],
6547 )
6548 .unwrap();
6549 manager
6550 .update_cortical_mapping(&src_id, &dst_id, vec![])
6551 .unwrap();
6552
6553 let count2 = manager
6554 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6555 .unwrap();
6556 assert_eq!(count2, 0);
6557 }
6558
6559 #[test]
6560 fn test_mapping_deletion_prunes_synapses_between_areas() {
6561 use feagi_npu_burst_engine::backend::CPUBackend;
6562 use feagi_npu_burst_engine::RustNPU;
6563 use feagi_npu_burst_engine::TracingMutex;
6564 use feagi_npu_runtime::StdRuntime;
6565 use feagi_structures::genomic::cortical_area::{
6566 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6567 };
6568 use std::sync::Arc;
6569
6570 let runtime = StdRuntime;
6572 let backend = CPUBackend::new();
6573 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6574 let dyn_npu = Arc::new(TracingMutex::new(
6575 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6576 "TestNPU",
6577 ));
6578 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6579
6580 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
6582 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
6583
6584 let src_area = CorticalArea::new(
6585 src_id,
6586 0,
6587 "src".to_string(),
6588 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6589 (0, 0, 0).into(),
6590 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6591 )
6592 .unwrap();
6593 let dst_area = CorticalArea::new(
6594 dst_id,
6595 1,
6596 "dst".to_string(),
6597 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6598 (0, 0, 0).into(),
6599 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6600 )
6601 .unwrap();
6602
6603 manager.add_cortical_area(src_area).unwrap();
6604 manager.add_cortical_area(dst_area).unwrap();
6605
6606 let s0 = manager
6608 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6609 .unwrap();
6610 let s1 = manager
6611 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6612 .unwrap();
6613 let t0 = manager
6614 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6615 .unwrap();
6616 let t1 = manager
6617 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6618 .unwrap();
6619
6620 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6622 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6623
6624 {
6626 let mut npu = dyn_npu.lock().unwrap();
6627 npu.rebuild_synapse_index();
6628 assert_eq!(npu.get_synapse_count(), 2);
6629 }
6630
6631 manager
6633 .update_cortical_mapping(&src_id, &dst_id, vec![])
6634 .unwrap();
6635 let created = manager
6636 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6637 .unwrap();
6638 assert_eq!(created, 0);
6639
6640 {
6642 let mut npu = dyn_npu.lock().unwrap();
6643 npu.rebuild_synapse_index();
6645 assert_eq!(npu.get_synapse_count(), 0);
6646 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6647 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6648 }
6649 }
6650
6651 #[test]
6652 fn test_mapping_update_prunes_synapses_between_areas() {
6653 use feagi_npu_burst_engine::backend::CPUBackend;
6654 use feagi_npu_burst_engine::RustNPU;
6655 use feagi_npu_burst_engine::TracingMutex;
6656 use feagi_npu_runtime::StdRuntime;
6657 use feagi_structures::genomic::cortical_area::{
6658 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6659 };
6660 use std::sync::Arc;
6661
6662 let runtime = StdRuntime;
6664 let backend = CPUBackend::new();
6665 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6666 let dyn_npu = Arc::new(TracingMutex::new(
6667 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6668 "TestNPU",
6669 ));
6670 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6671
6672 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6674
6675 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
6678 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
6679
6680 let src_area = CorticalArea::new(
6681 src_id,
6682 0,
6683 "src".to_string(),
6684 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6685 (0, 0, 0).into(),
6686 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6687 )
6688 .unwrap();
6689 let dst_area = CorticalArea::new(
6690 dst_id,
6691 0,
6692 "dst".to_string(),
6693 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6694 (0, 0, 0).into(),
6695 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6696 )
6697 .unwrap();
6698
6699 manager.add_cortical_area(src_area).unwrap();
6700 manager.add_cortical_area(dst_area).unwrap();
6701
6702 let s0 = manager
6704 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6705 .unwrap();
6706 let s1 = manager
6707 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6708 .unwrap();
6709 let t0 = manager
6710 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6711 .unwrap();
6712 let t1 = manager
6713 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6714 .unwrap();
6715
6716 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6718 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6719
6720 {
6722 let mut npu = dyn_npu.lock().unwrap();
6723 npu.rebuild_synapse_index();
6724 assert_eq!(npu.get_synapse_count(), 2);
6725 }
6726
6727 manager
6733 .update_cortical_mapping(
6734 &src_id,
6735 &dst_id,
6736 vec![serde_json::json!({
6737 "morphology_id": "episodic_memory",
6738 "morphology_scalar": [1],
6739 "postSynapticCurrent_multiplier": 1,
6740 "plasticity_flag": false,
6741 "plasticity_constant": 0,
6742 "ltp_multiplier": 0,
6743 "ltd_multiplier": 0,
6744 "plasticity_window": 0,
6745 })],
6746 )
6747 .unwrap();
6748 let created = manager
6749 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6750 .unwrap();
6751 assert_eq!(created, 0);
6752
6753 {
6755 let mut npu = dyn_npu.lock().unwrap();
6756 npu.rebuild_synapse_index();
6758 assert_eq!(npu.get_synapse_count(), 0);
6759 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6760 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6761 }
6762 }
6763
6764 #[test]
6765 fn test_upstream_area_tracking() {
6766 use crate::models::cortical_area::CorticalArea;
6768 use feagi_npu_burst_engine::backend::CPUBackend;
6769 use feagi_npu_burst_engine::TracingMutex;
6770 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6771 use feagi_npu_runtime::StdRuntime;
6772 use feagi_structures::genomic::cortical_area::{
6773 CorticalAreaDimensions, CorticalAreaType, CorticalID,
6774 };
6775
6776 let runtime = StdRuntime;
6778 let backend = CPUBackend::new();
6779 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6780 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6781 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6782
6783 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6786
6787 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
6789 let src_area = CorticalArea::new(
6790 src_id,
6791 0,
6792 "Source Area".to_string(),
6793 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6794 (0, 0, 0).into(),
6795 CorticalAreaType::Custom(
6796 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6797 ),
6798 )
6799 .unwrap();
6800 let src_idx = manager.add_cortical_area(src_area).unwrap();
6801
6802 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
6804 let dst_area = CorticalArea::new(
6805 dst_id,
6806 0,
6807 "Dest Area".to_string(),
6808 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6809 (0, 0, 0).into(),
6810 CorticalAreaType::Custom(
6811 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6812 ),
6813 )
6814 .unwrap();
6815 manager.add_cortical_area(dst_area).unwrap();
6816
6817 {
6819 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
6820 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
6821 assert!(
6822 upstream.as_array().unwrap().is_empty(),
6823 "Upstream areas should be empty initially"
6824 );
6825 }
6826
6827 let mapping_data = vec![serde_json::json!({
6829 "morphology_id": "episodic_memory",
6830 "morphology_scalar": 1,
6831 "postSynapticCurrent_multiplier": 1.0,
6832 })];
6833 manager
6834 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
6835 .unwrap();
6836 manager
6837 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6838 .unwrap();
6839
6840 {
6842 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6843 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
6844 assert_eq!(
6845 upstream_areas[0], src_idx,
6846 "Upstream area should be src_idx"
6847 );
6848 }
6849
6850 manager
6852 .update_cortical_mapping(&src_id, &dst_id, vec![])
6853 .unwrap();
6854 manager
6855 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6856 .unwrap();
6857
6858 {
6860 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6861 assert_eq!(
6862 upstream_areas.len(),
6863 0,
6864 "Should have 0 upstream areas after deletion"
6865 );
6866 }
6867 }
6868
6869 #[test]
6870 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
6871 use crate::models::cortical_area::CorticalArea;
6872 use feagi_npu_burst_engine::backend::CPUBackend;
6873 use feagi_npu_burst_engine::TracingMutex;
6874 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6875 use feagi_npu_runtime::StdRuntime;
6876 use feagi_structures::genomic::cortical_area::{
6877 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
6878 };
6879 use std::sync::Arc;
6880
6881 let runtime = StdRuntime;
6882 let backend = CPUBackend::new();
6883 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6884 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6885 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6886 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6887
6888 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6889 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
6890 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
6891 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
6892
6893 let a1_area = CorticalArea::new(
6894 a1_id,
6895 0,
6896 "A1".to_string(),
6897 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6898 (0, 0, 0).into(),
6899 CorticalAreaType::Custom(
6900 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6901 ),
6902 )
6903 .unwrap();
6904 let a2_area = CorticalArea::new(
6905 a2_id,
6906 0,
6907 "A2".to_string(),
6908 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6909 (0, 0, 0).into(),
6910 CorticalAreaType::Custom(
6911 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6912 ),
6913 )
6914 .unwrap();
6915
6916 let mut m1_area = CorticalArea::new(
6917 m1_id,
6918 0,
6919 "M1".to_string(),
6920 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6921 (0, 0, 0).into(),
6922 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6923 )
6924 .unwrap();
6925 m1_area
6926 .properties
6927 .insert("is_mem_type".to_string(), serde_json::json!(true));
6928 m1_area
6929 .properties
6930 .insert("temporal_depth".to_string(), serde_json::json!(1));
6931
6932 let mut m2_area = CorticalArea::new(
6933 m2_id,
6934 0,
6935 "M2".to_string(),
6936 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6937 (0, 0, 0).into(),
6938 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6939 )
6940 .unwrap();
6941 m2_area
6942 .properties
6943 .insert("is_mem_type".to_string(), serde_json::json!(true));
6944 m2_area
6945 .properties
6946 .insert("temporal_depth".to_string(), serde_json::json!(1));
6947
6948 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
6949 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
6950 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
6951 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
6952
6953 manager
6954 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6955 .unwrap();
6956 manager
6957 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6958 .unwrap();
6959
6960 let episodic_mapping = vec![serde_json::json!({
6961 "morphology_id": "episodic_memory",
6962 "morphology_scalar": 1,
6963 "postSynapticCurrent_multiplier": 1.0,
6964 })];
6965 manager
6966 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
6967 .unwrap();
6968 manager
6969 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
6970 .unwrap();
6971 manager
6972 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
6973 .unwrap();
6974 manager
6975 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
6976 .unwrap();
6977
6978 let assoc_mapping = vec![serde_json::json!({
6979 "morphology_id": "associative_memory",
6980 "morphology_scalar": 1,
6981 "postSynapticCurrent_multiplier": 1.0,
6982 "plasticity_flag": true,
6983 "plasticity_constant": 1,
6984 "ltp_multiplier": 1,
6985 "ltd_multiplier": 1,
6986 "plasticity_window": 5,
6987 })];
6988 manager
6989 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping)
6990 .unwrap();
6991 manager
6992 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
6993 .unwrap();
6994
6995 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6996 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6997 assert_eq!(
6998 upstream_m1.len(),
6999 1,
7000 "M1 should have only 1 upstream before refresh"
7001 );
7002 assert_eq!(
7003 upstream_m2.len(),
7004 2,
7005 "M2 should have 2 upstreams before refresh"
7006 );
7007
7008 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
7009 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
7010
7011 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7012 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7013 assert_eq!(
7014 upstream_m1.len(),
7015 2,
7016 "M1 should have 2 upstreams after refresh"
7017 );
7018 assert_eq!(
7019 upstream_m2.len(),
7020 2,
7021 "M2 should have 2 upstreams after refresh"
7022 );
7023 assert!(upstream_m1.contains(&a1_idx));
7024 assert!(upstream_m1.contains(&m2_idx));
7025 assert!(upstream_m2.contains(&a2_idx));
7026 assert!(upstream_m2.contains(&m1_idx));
7027
7028 {
7030 let mut npu_lock = dyn_npu.lock().unwrap();
7031 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
7032 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
7033 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
7034 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
7035 npu_lock.process_burst().expect("Burst processing failed");
7036 }
7037
7038 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7039 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7040 assert_eq!(
7041 upstream_m1.len(),
7042 2,
7043 "M1 should keep 2 upstreams after firing"
7044 );
7045 assert_eq!(
7046 upstream_m2.len(),
7047 2,
7048 "M2 should keep 2 upstreams after firing"
7049 );
7050 }
7051
7052 #[test]
7053 fn test_memory_twin_created_for_memory_mapping() {
7054 use crate::models::cortical_area::CorticalArea;
7055 use feagi_npu_burst_engine::backend::CPUBackend;
7056 use feagi_npu_burst_engine::TracingMutex;
7057 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7058 use feagi_npu_runtime::StdRuntime;
7059 use feagi_structures::genomic::cortical_area::{
7060 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7061 MemoryCorticalType,
7062 };
7063 use std::sync::Arc;
7064
7065 let runtime = StdRuntime;
7066 let backend = CPUBackend::new();
7067 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7068 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7069 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7070 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7071
7072 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
7073 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
7074
7075 let src_area = CorticalArea::new(
7076 src_id,
7077 0,
7078 "Source Area".to_string(),
7079 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7080 (0, 0, 0).into(),
7081 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7082 )
7083 .unwrap();
7084 let mut dst_area = CorticalArea::new(
7085 dst_id,
7086 0,
7087 "Memory Area".to_string(),
7088 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7089 (0, 0, 0).into(),
7090 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7091 )
7092 .unwrap();
7093 dst_area
7094 .properties
7095 .insert("is_mem_type".to_string(), serde_json::json!(true));
7096 dst_area
7097 .properties
7098 .insert("temporal_depth".to_string(), serde_json::json!(1));
7099
7100 manager.add_cortical_area(src_area).unwrap();
7101 manager.add_cortical_area(dst_area).unwrap();
7102
7103 let mapping_data = vec![serde_json::json!({
7104 "morphology_id": "episodic_memory",
7105 "morphology_scalar": 1,
7106 "postSynapticCurrent_multiplier": 1.0,
7107 })];
7108 manager
7109 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7110 .unwrap();
7111 manager
7112 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7113 .unwrap();
7114
7115 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7116 let twin_map = memory_area
7117 .properties
7118 .get("memory_twin_areas")
7119 .and_then(|v| v.as_object())
7120 .expect("memory_twin_areas should be set");
7121 let twin_id_str = twin_map
7122 .get(&src_id.as_base_64())
7123 .and_then(|v| v.as_str())
7124 .expect("Missing twin entry for upstream area");
7125 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7126 let mapping = memory_area
7127 .properties
7128 .get("cortical_mapping_dst")
7129 .and_then(|v| v.as_object())
7130 .and_then(|map| map.get(&twin_id.as_base_64()))
7131 .and_then(|v| v.as_array())
7132 .expect("Missing memory replay mapping for twin area");
7133 let uses_replay = mapping.iter().any(|rule| {
7134 rule.get("morphology_id")
7135 .and_then(|v| v.as_str())
7136 .is_some_and(|id| id == "memory_replay")
7137 });
7138 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7139
7140 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7141 assert!(matches!(
7142 twin_area.cortical_type,
7143 CorticalAreaType::Custom(_)
7144 ));
7145 assert_eq!(
7146 twin_area
7147 .properties
7148 .get("memory_twin_of")
7149 .and_then(|v| v.as_str()),
7150 Some(src_id.as_base_64().as_str())
7151 );
7152 assert_eq!(
7153 twin_area
7154 .properties
7155 .get("memory_twin_for")
7156 .and_then(|v| v.as_str()),
7157 Some(dst_id.as_base_64().as_str())
7158 );
7159 }
7160
7161 #[test]
7162 fn test_associative_memory_between_memory_areas_creates_synapses() {
7163 use crate::models::cortical_area::CorticalArea;
7164 use feagi_npu_burst_engine::backend::CPUBackend;
7165 use feagi_npu_burst_engine::TracingMutex;
7166 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7167 use feagi_npu_runtime::StdRuntime;
7168 use feagi_structures::genomic::cortical_area::{
7169 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7170 };
7171 use std::sync::Arc;
7172
7173 let runtime = StdRuntime;
7174 let backend = CPUBackend::new();
7175 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7176 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7177 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7178 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7179
7180 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
7181 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
7182
7183 let mut m1_area = CorticalArea::new(
7184 m1_id,
7185 0,
7186 "Memory M1".to_string(),
7187 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7188 (0, 0, 0).into(),
7189 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7190 )
7191 .unwrap();
7192 m1_area
7193 .properties
7194 .insert("is_mem_type".to_string(), serde_json::json!(true));
7195 m1_area
7196 .properties
7197 .insert("temporal_depth".to_string(), serde_json::json!(1));
7198
7199 let mut m2_area = CorticalArea::new(
7200 m2_id,
7201 0,
7202 "Memory M2".to_string(),
7203 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7204 (0, 0, 0).into(),
7205 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7206 )
7207 .unwrap();
7208 m2_area
7209 .properties
7210 .insert("is_mem_type".to_string(), serde_json::json!(true));
7211 m2_area
7212 .properties
7213 .insert("temporal_depth".to_string(), serde_json::json!(1));
7214
7215 manager.add_cortical_area(m1_area).unwrap();
7216 manager.add_cortical_area(m2_area).unwrap();
7217
7218 manager
7219 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7220 .unwrap();
7221 manager
7222 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7223 .unwrap();
7224
7225 let mapping_data = vec![serde_json::json!({
7226 "morphology_id": "associative_memory",
7227 "morphology_scalar": 1,
7228 "postSynapticCurrent_multiplier": 1.0,
7229 "plasticity_flag": true,
7230 "plasticity_constant": 1,
7231 "ltp_multiplier": 1,
7232 "ltd_multiplier": 1,
7233 "plasticity_window": 5,
7234 })];
7235 manager
7236 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
7237 .unwrap();
7238 let created = manager
7239 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7240 .unwrap();
7241 assert!(
7242 created > 0,
7243 "Expected associative memory mapping between memory areas to create synapses"
7244 );
7245 }
7246
7247 #[test]
7248 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7249 use crate::models::cortical_area::CorticalArea;
7250 use feagi_npu_burst_engine::backend::CPUBackend;
7251 use feagi_npu_burst_engine::TracingMutex;
7252 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7253 use feagi_npu_runtime::StdRuntime;
7254 use feagi_structures::genomic::cortical_area::{
7255 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7256 MemoryCorticalType,
7257 };
7258 use std::sync::Arc;
7259
7260 let runtime = StdRuntime;
7261 let backend = CPUBackend::new();
7262 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7263 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7264 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7265 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7266
7267 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7268 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7269
7270 let src_area = CorticalArea::new(
7271 src_id,
7272 0,
7273 "Source Area".to_string(),
7274 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7275 (0, 0, 0).into(),
7276 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7277 )
7278 .unwrap();
7279 let mut mem_area = CorticalArea::new(
7280 mem_id,
7281 0,
7282 "Memory Area".to_string(),
7283 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7284 (0, 0, 0).into(),
7285 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7286 )
7287 .unwrap();
7288 mem_area
7289 .properties
7290 .insert("is_mem_type".to_string(), serde_json::json!(true));
7291 mem_area
7292 .properties
7293 .insert("temporal_depth".to_string(), serde_json::json!(1));
7294
7295 manager.add_cortical_area(src_area).unwrap();
7296 manager.add_cortical_area(mem_area).unwrap();
7297
7298 let twin_id = manager
7299 .build_memory_twin_id(&mem_id, &src_id)
7300 .expect("Failed to build twin id");
7301 let twin_area = CorticalArea::new(
7302 twin_id,
7303 0,
7304 "Source Area_twin".to_string(),
7305 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7306 (0, 0, 0).into(),
7307 CorticalAreaType::Custom(
7308 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7309 ),
7310 )
7311 .unwrap();
7312 manager.add_cortical_area(twin_area).unwrap();
7313
7314 let repaired = manager
7315 .ensure_memory_twin_area(&mem_id, &src_id)
7316 .expect("Failed to repair twin");
7317 assert_eq!(repaired, twin_id);
7318
7319 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
7320 let twin_map = mem_area
7321 .properties
7322 .get("memory_twin_areas")
7323 .and_then(|v| v.as_object())
7324 .expect("memory_twin_areas should be set");
7325 let twin_id_str = twin_map
7326 .get(&src_id.as_base_64())
7327 .and_then(|v| v.as_str())
7328 .expect("Missing twin entry for upstream area");
7329 assert_eq!(twin_id_str, twin_id.as_base_64());
7330
7331 let replay_map = mem_area
7332 .properties
7333 .get("cortical_mapping_dst")
7334 .and_then(|v| v.as_object())
7335 .and_then(|map| map.get(&twin_id.as_base_64()))
7336 .and_then(|v| v.as_array())
7337 .expect("Missing memory replay mapping for twin area");
7338 let uses_replay = replay_map.iter().any(|rule| {
7339 rule.get("morphology_id")
7340 .and_then(|v| v.as_str())
7341 .is_some_and(|id| id == "memory_replay")
7342 });
7343 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7344
7345 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7346 assert_eq!(
7347 twin_area
7348 .properties
7349 .get("memory_twin_of")
7350 .and_then(|v| v.as_str()),
7351 Some(src_id.as_base_64().as_str())
7352 );
7353 assert_eq!(
7354 twin_area
7355 .properties
7356 .get("memory_twin_for")
7357 .and_then(|v| v.as_str()),
7358 Some(mem_id.as_base_64().as_str())
7359 );
7360 }
7361}