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::{CorticalAreaType, CorticalID, CustomCorticalType};
47use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
48
49use feagi_state_manager::StateManager;
52
53const DATA_HASH_SEED: u64 = 0;
54const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
56
57static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
62 Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ConnectomeConfig {
67 pub max_neurons: usize,
69
70 pub max_synapses: usize,
72
73 pub backend: String,
75}
76
77impl Default for ConnectomeConfig {
78 fn default() -> Self {
79 Self {
80 max_neurons: 10_000_000,
81 max_synapses: 100_000_000,
82 backend: "cpu".to_string(),
83 }
84 }
85}
86
87pub struct ConnectomeManager {
109 cortical_areas: HashMap<CorticalID, CorticalArea>,
111
112 cortical_id_to_idx: HashMap<CorticalID, u32>,
114
115 cortical_idx_to_id: HashMap<u32, CorticalID>,
117
118 next_cortical_idx: u32,
120
121 brain_regions: BrainRegionHierarchy,
123
124 morphology_registry: feagi_evolutionary::MorphologyRegistry,
126
127 config: ConnectomeConfig,
129
130 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
136
137 #[cfg(feature = "plasticity")]
139 plasticity_executor:
140 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
141
142 cached_neuron_count: Arc<AtomicUsize>,
145
146 cached_synapse_count: Arc<AtomicUsize>,
149
150 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
153
154 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
157
158 initialized: bool,
160
161 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
163}
164
165type NeuronData = (
167 u32,
168 u32,
169 u32,
170 f32,
171 f32,
172 f32,
173 f32,
174 i32,
175 u16,
176 f32,
177 u16,
178 u16,
179 bool,
180);
181
182impl ConnectomeManager {
183 fn new() -> Self {
185 Self {
186 cortical_areas: HashMap::new(),
187 cortical_id_to_idx: HashMap::new(),
188 cortical_idx_to_id: HashMap::new(),
189 next_cortical_idx: 3, brain_regions: BrainRegionHierarchy::new(),
192 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
193 config: ConnectomeConfig::default(),
194 npu: None,
195 #[cfg(feature = "plasticity")]
196 plasticity_executor: None,
197 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
198 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
199 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
200 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
201 initialized: false,
202 last_fatigue_calculation: Arc::new(Mutex::new(
203 std::time::Instant::now() - std::time::Duration::from_secs(10),
204 )), }
206 }
207
208 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
225 Arc::clone(&*INSTANCE)
228 }
229
230 pub fn new_for_testing() -> Self {
258 Self {
259 cortical_areas: HashMap::new(),
260 cortical_id_to_idx: HashMap::new(),
261 cortical_idx_to_id: HashMap::new(),
262 next_cortical_idx: 0,
263 brain_regions: BrainRegionHierarchy::new(),
264 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
265 config: ConnectomeConfig::default(),
266 npu: None,
267 #[cfg(feature = "plasticity")]
268 plasticity_executor: None,
269 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
270 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
271 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
272 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
273 initialized: false,
274 last_fatigue_calculation: Arc::new(Mutex::new(
275 std::time::Instant::now() - std::time::Duration::from_secs(10),
276 )),
277 }
278 }
279
280 pub fn new_for_testing_with_npu(
296 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
297 ) -> Self {
298 Self {
299 cortical_areas: HashMap::new(),
300 cortical_id_to_idx: HashMap::new(),
301 cortical_idx_to_id: HashMap::new(),
302 next_cortical_idx: 3,
303 brain_regions: BrainRegionHierarchy::new(),
304 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
305 config: ConnectomeConfig::default(),
306 npu: Some(npu),
307 #[cfg(feature = "plasticity")]
308 plasticity_executor: None,
309 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
310 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
311 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
312 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
313 initialized: false,
314 last_fatigue_calculation: Arc::new(Mutex::new(
315 std::time::Instant::now() - std::time::Duration::from_secs(10),
316 )),
317 }
318 }
319
320 pub fn setup_core_morphologies_for_testing(&mut self) {
330 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
331 }
332
333 #[cfg(test)]
342 pub fn reset_for_testing() {
343 let mut instance = INSTANCE.write();
344 *instance = Self::new();
345 }
346
347 fn update_state_hashes(
353 &self,
354 brain_regions: Option<u64>,
355 cortical_areas: Option<u64>,
356 brain_geometry: Option<u64>,
357 morphologies: Option<u64>,
358 cortical_mappings: Option<u64>,
359 ) {
360 let state_manager = StateManager::instance();
361 let state_manager = state_manager.read();
362 if let Some(value) = brain_regions {
363 state_manager.set_brain_regions_hash(value);
364 }
365 if let Some(value) = cortical_areas {
366 state_manager.set_cortical_areas_hash(value);
367 }
368 if let Some(value) = brain_geometry {
369 state_manager.set_brain_geometry_hash(value);
370 }
371 if let Some(value) = morphologies {
372 state_manager.set_morphologies_hash(value);
373 }
374 if let Some(value) = cortical_mappings {
375 state_manager.set_cortical_mappings_hash(value);
376 }
377 }
378
379 fn refresh_brain_regions_hash(&self) {
381 let hash = self.compute_brain_regions_hash();
382 self.update_state_hashes(Some(hash), None, None, None, None);
383 }
384
385 #[allow(dead_code)]
386 fn refresh_cortical_areas_hash(&self) {
388 let hash = self.compute_cortical_areas_hash();
389 self.update_state_hashes(None, Some(hash), None, None, None);
390 }
391
392 #[allow(dead_code)]
393 fn refresh_brain_geometry_hash(&self) {
395 let hash = self.compute_brain_geometry_hash();
396 self.update_state_hashes(None, None, Some(hash), None, None);
397 }
398
399 fn refresh_morphologies_hash(&self) {
401 let hash = self.compute_morphologies_hash();
402 self.update_state_hashes(None, None, None, Some(hash), None);
403 }
404
405 fn refresh_cortical_mappings_hash(&self) {
407 let hash = self.compute_cortical_mappings_hash();
408 self.update_state_hashes(None, None, None, None, Some(hash));
409 }
410
411 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
413 let cortical_hash = if properties_changed {
414 Some(self.compute_cortical_areas_hash())
415 } else {
416 None
417 };
418 let geometry_hash = if geometry_changed {
419 Some(self.compute_brain_geometry_hash())
420 } else {
421 None
422 };
423 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
424 }
425
426 fn compute_brain_regions_hash(&self) -> u64 {
428 let mut hasher = Xxh64::new(DATA_HASH_SEED);
429 let mut region_ids: Vec<String> = self
430 .brain_regions
431 .get_all_region_ids()
432 .into_iter()
433 .cloned()
434 .collect();
435 region_ids.sort();
436
437 for region_id in region_ids {
438 let Some(region) = self.brain_regions.get_region(®ion_id) else {
439 continue;
440 };
441 Self::hash_str(&mut hasher, ®ion_id);
442 Self::hash_str(&mut hasher, ®ion.name);
443 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
444 let parent_id = self.brain_regions.get_parent(®ion_id);
445 match parent_id {
446 Some(parent) => Self::hash_str(&mut hasher, parent),
447 None => Self::hash_str(&mut hasher, "null"),
448 }
449
450 let mut cortical_ids: Vec<String> = region
451 .cortical_areas
452 .iter()
453 .map(|id| id.as_base_64())
454 .collect();
455 cortical_ids.sort();
456 for cortical_id in cortical_ids {
457 Self::hash_str(&mut hasher, &cortical_id);
458 }
459
460 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
461 }
462
463 hasher.finish() & HASH_SAFE_MASK
464 }
465
466 fn compute_cortical_areas_hash(&self) -> u64 {
468 let mut hasher = Xxh64::new(DATA_HASH_SEED);
469 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
470 areas.sort_by_key(|area| area.cortical_id.as_base_64());
471
472 for area in areas {
473 let cortical_id = area.cortical_id.as_base_64();
474 Self::hash_str(&mut hasher, &cortical_id);
475 hasher.write_u32(area.cortical_idx);
476 Self::hash_str(&mut hasher, &area.name);
477 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
478
479 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
480 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
481 }
482
483 hasher.finish() & HASH_SAFE_MASK
484 }
485
486 fn compute_brain_geometry_hash(&self) -> u64 {
488 let mut hasher = Xxh64::new(DATA_HASH_SEED);
489 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
490 areas.sort_by_key(|area| area.cortical_id.as_base_64());
491
492 for area in areas {
493 let cortical_id = area.cortical_id.as_base_64();
494 Self::hash_str(&mut hasher, &cortical_id);
495
496 Self::hash_i32(&mut hasher, area.position.x);
497 Self::hash_i32(&mut hasher, area.position.y);
498 Self::hash_i32(&mut hasher, area.position.z);
499
500 Self::hash_u32(&mut hasher, area.dimensions.width);
501 Self::hash_u32(&mut hasher, area.dimensions.height);
502 Self::hash_u32(&mut hasher, area.dimensions.depth);
503
504 let coord_2d = area
505 .properties
506 .get("coordinate_2d")
507 .or_else(|| area.properties.get("coordinates_2d"));
508 match coord_2d {
509 Some(value) => Self::hash_json_value(&mut hasher, value),
510 None => Self::hash_str(&mut hasher, "null"),
511 }
512 }
513
514 hasher.finish() & HASH_SAFE_MASK
515 }
516
517 fn compute_morphologies_hash(&self) -> u64 {
519 let mut hasher = Xxh64::new(DATA_HASH_SEED);
520 let mut morphology_ids = self.morphology_registry.morphology_ids();
521 morphology_ids.sort();
522
523 for morphology_id in morphology_ids {
524 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
525 Self::hash_str(&mut hasher, &morphology_id);
526 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
527 Self::hash_str(&mut hasher, &morphology.class);
528 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
529 Self::hash_json_value(&mut hasher, &value);
530 }
531 }
532 }
533
534 hasher.finish() & HASH_SAFE_MASK
535 }
536
537 fn compute_cortical_mappings_hash(&self) -> u64 {
539 let mut hasher = Xxh64::new(DATA_HASH_SEED);
540 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
541 areas.sort_by_key(|area| area.cortical_id.as_base_64());
542
543 for area in areas {
544 let cortical_id = area.cortical_id.as_base_64();
545 Self::hash_str(&mut hasher, &cortical_id);
546 if let Some(serde_json::Value::Object(map)) =
547 area.properties.get("cortical_mapping_dst")
548 {
549 let mut dest_ids: Vec<&String> = map.keys().collect();
550 dest_ids.sort();
551 for dest_id in dest_ids {
552 Self::hash_str(&mut hasher, dest_id);
553 if let Some(value) = map.get(dest_id) {
554 Self::hash_json_value(&mut hasher, value);
555 }
556 }
557 } else {
558 Self::hash_str(&mut hasher, "null");
559 }
560 }
561
562 hasher.finish() & HASH_SAFE_MASK
563 }
564
565 fn hash_str(hasher: &mut Xxh64, value: &str) {
567 hasher.write(value.as_bytes());
568 hasher.write_u8(0);
569 }
570
571 fn hash_i32(hasher: &mut Xxh64, value: i32) {
573 hasher.write(&value.to_le_bytes());
574 }
575
576 fn hash_u32(hasher: &mut Xxh64, value: u32) {
578 hasher.write(&value.to_le_bytes());
579 }
580
581 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
583 match value {
584 serde_json::Value::Null => {
585 hasher.write_u8(0);
586 }
587 serde_json::Value::Bool(val) => {
588 hasher.write_u8(1);
589 hasher.write_u8(*val as u8);
590 }
591 serde_json::Value::Number(num) => {
592 hasher.write_u8(2);
593 Self::hash_str(hasher, &num.to_string());
594 }
595 serde_json::Value::String(val) => {
596 hasher.write_u8(3);
597 Self::hash_str(hasher, val);
598 }
599 serde_json::Value::Array(items) => {
600 hasher.write_u8(4);
601 for item in items {
602 Self::hash_json_value(hasher, item);
603 }
604 }
605 serde_json::Value::Object(map) => {
606 hasher.write_u8(5);
607 let mut keys: Vec<&String> = map.keys().collect();
608 keys.sort();
609 for key in keys {
610 Self::hash_str(hasher, key);
611 if let Some(val) = map.get(key) {
612 Self::hash_json_value(hasher, val);
613 }
614 }
615 }
616 }
617 }
618
619 fn hash_properties_filtered(
621 hasher: &mut Xxh64,
622 properties: &HashMap<String, serde_json::Value>,
623 excluded_keys: &[&str],
624 ) {
625 let mut keys: Vec<&String> = properties.keys().collect();
626 keys.sort();
627 for key in keys {
628 if excluded_keys.contains(&key.as_str()) {
629 continue;
630 }
631 Self::hash_str(hasher, key);
632 if let Some(value) = properties.get(key) {
633 Self::hash_json_value(hasher, value);
634 }
635 }
636 }
637
638 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
659 if self.cortical_areas.contains_key(&area.cortical_id) {
661 return Err(BduError::InvalidArea(format!(
662 "Cortical area {} already exists",
663 area.cortical_id
664 )));
665 }
666
667 use feagi_structures::genomic::cortical_area::CoreCorticalType;
670
671 let death_id = CoreCorticalType::Death.to_cortical_id();
672 let power_id = CoreCorticalType::Power.to_cortical_id();
673 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
674
675 let is_death_area = area.cortical_id == death_id;
676 let is_power_area = area.cortical_id == power_id;
677 let is_fatigue_area = area.cortical_id == fatigue_id;
678
679 if is_death_area {
680 trace!(
681 target: "feagi-bdu",
682 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
683 area.cortical_id
684 );
685 area.cortical_idx = 0;
686 } else if is_power_area {
687 trace!(
688 target: "feagi-bdu",
689 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
690 area.cortical_id
691 );
692 area.cortical_idx = 1;
693 } else if is_fatigue_area {
694 trace!(
695 target: "feagi-bdu",
696 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
697 area.cortical_id
698 );
699 area.cortical_idx = 2;
700 } else {
701 if area.cortical_idx == 0 {
703 area.cortical_idx = self.next_cortical_idx;
704 self.next_cortical_idx += 1;
705 trace!(
706 target: "feagi-bdu",
707 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be ≥3)",
708 area.cortical_idx,
709 area.cortical_id.as_base_64()
710 );
711 } else {
712 if area.cortical_idx == 0 || area.cortical_idx == 1 || area.cortical_idx == 2 {
714 warn!(
715 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
716 area.cortical_id, area.cortical_idx);
717 area.cortical_idx = self.next_cortical_idx;
718 self.next_cortical_idx += 1;
719 info!(
720 " Reassigned '{}' to cortical_idx={}",
721 area.cortical_id, area.cortical_idx
722 );
723 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
724 return Err(BduError::InvalidArea(format!(
725 "Cortical index {} is already in use",
726 area.cortical_idx
727 )));
728 }
729
730 if area.cortical_idx >= self.next_cortical_idx {
732 self.next_cortical_idx = area.cortical_idx + 1;
733 }
734 }
735 }
736
737 let cortical_id = area.cortical_id;
738 let cortical_idx = area.cortical_idx;
739
740 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
742 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
743
744 area.properties
746 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
747
748 let parent_region_id = area
757 .properties
758 .get("parent_region_id")
759 .and_then(|v| v.as_str())
760 .map(|s| s.to_string());
761
762 self.cortical_areas.insert(cortical_id, area);
764
765 if let Some(region_id) = parent_region_id {
767 let region = self
768 .brain_regions
769 .get_region_mut(®ion_id)
770 .ok_or_else(|| {
771 BduError::InvalidArea(format!(
772 "Unknown parent_region_id '{}' for cortical area {}",
773 region_id,
774 cortical_id.as_base_64()
775 ))
776 })?;
777 region.add_area(cortical_id);
778 }
779
780 {
783 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
784 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
785 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
786 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
787 }
788 if let Some(state_manager) = StateManager::instance().try_read() {
790 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
791 }
792
793 if let Some(ref npu) = self.npu {
797 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
798 if let Ok(mut npu_lock) = npu.lock() {
799 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
800 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
801 trace!(
802 target: "feagi-bdu",
803 "Registered cortical area idx={} -> '{}' in NPU",
804 cortical_idx,
805 cortical_id.as_base_64()
806 );
807 }
808 }
809
810 self.sync_cortical_area_flags_to_npu()?;
812
813 self.initialized = true;
814
815 self.refresh_cortical_area_hashes(true, true);
816 self.refresh_brain_regions_hash();
817
818 Ok(cortical_idx)
819 }
820
821 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
836 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
837 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
838 })?;
839
840 self.cortical_id_to_idx.remove(cortical_id);
842 self.cortical_idx_to_id.remove(&area.cortical_idx);
843
844 self.refresh_cortical_area_hashes(true, true);
845 Ok(())
846 }
847
848 pub fn rename_cortical_area_id(
852 &mut self,
853 old_id: &CorticalID,
854 new_id: CorticalID,
855 new_cortical_type: CorticalAreaType,
856 ) -> BduResult<()> {
857 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
858 }
859
860 pub fn rename_cortical_area_id_with_options(
862 &mut self,
863 old_id: &CorticalID,
864 new_id: CorticalID,
865 new_cortical_type: CorticalAreaType,
866 update_npu_registry: bool,
867 ) -> BduResult<()> {
868 if !self.cortical_areas.contains_key(old_id) {
869 return Err(BduError::InvalidArea(format!(
870 "Cortical area {} does not exist",
871 old_id
872 )));
873 }
874 if self.cortical_areas.contains_key(&new_id) {
875 return Err(BduError::InvalidArea(format!(
876 "Cortical area {} already exists",
877 new_id
878 )));
879 }
880
881 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
882 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
883 })?;
884 let cortical_idx = area.cortical_idx;
885 area.cortical_id = new_id;
886 area.cortical_type = new_cortical_type;
887
888 self.cortical_areas.insert(new_id, area);
889 self.cortical_id_to_idx.remove(old_id);
890 self.cortical_id_to_idx.insert(new_id, cortical_idx);
891 self.cortical_idx_to_id.insert(cortical_idx, new_id);
892
893 {
895 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
896 if let Some(value) = neuron_cache.remove(old_id) {
897 neuron_cache.insert(new_id, value);
898 }
899 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
900 if let Some(value) = synapse_cache.remove(old_id) {
901 synapse_cache.insert(new_id, value);
902 }
903 }
904
905 self.brain_regions.rename_cortical_area_id(old_id, new_id);
907
908 let old_id_str = old_id.as_base_64();
910 let new_id_str = new_id.as_base_64();
911 for area in self.cortical_areas.values_mut() {
912 if let Some(mapping) = area
913 .properties
914 .get_mut("cortical_mapping_dst")
915 .and_then(|v| v.as_object_mut())
916 {
917 if let Some(value) = mapping.remove(&old_id_str) {
918 mapping.insert(new_id_str.clone(), value);
919 }
920 }
921 }
922
923 if update_npu_registry {
925 if let Some(ref npu) = self.npu {
926 if let Ok(mut npu_lock) = npu.lock() {
927 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
928 }
929 }
930 }
931
932 self.refresh_cortical_area_hashes(true, true);
933 self.refresh_brain_regions_hash();
934 self.refresh_cortical_mappings_hash();
935
936 Ok(())
937 }
938
939 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
941 self.cortical_areas.get(cortical_id)
942 }
943
944 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
946 self.cortical_areas.get_mut(cortical_id)
947 }
948
949 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
951 self.cortical_id_to_idx.get(cortical_id).copied()
952 }
953
954 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
966 self.brain_regions.find_region_containing_area(cortical_id)
967 }
968
969 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
980 use std::collections::HashSet;
981
982 let region_ids: Vec<String> = self
983 .brain_regions
984 .get_all_region_ids()
985 .into_iter()
986 .cloned()
987 .collect();
988
989 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
990 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
991
992 for rid in ®ion_ids {
994 inputs_by_region.insert(rid.clone(), HashSet::new());
995 outputs_by_region.insert(rid.clone(), HashSet::new());
996 }
997
998 for (src_id, src_area) in &self.cortical_areas {
999 let Some(dstmap) = src_area
1000 .properties
1001 .get("cortical_mapping_dst")
1002 .and_then(|v| v.as_object())
1003 else {
1004 continue;
1005 };
1006
1007 let src_region_id = self.brain_regions.find_region_containing_area(src_id).ok_or_else(|| {
1008 BduError::InvalidArea(format!(
1009 "Unable to recompute region IO: source cortical area {} is not assigned to any region",
1010 src_id.as_base_64()
1011 ))
1012 })?;
1013
1014 for dst_id_str in dstmap.keys() {
1015 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1016 BduError::InvalidArea(format!(
1017 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1018 dst_id_str,
1019 src_id.as_base_64(),
1020 e
1021 ))
1022 })?;
1023
1024 let dst_region_id =
1025 self.brain_regions.find_region_containing_area(&dst_id).ok_or_else(|| {
1026 BduError::InvalidArea(format!(
1027 "Unable to recompute region IO: destination cortical area {} is not assigned to any region",
1028 dst_id.as_base_64()
1029 ))
1030 })?;
1031
1032 if src_region_id == dst_region_id {
1033 continue;
1034 }
1035
1036 outputs_by_region
1037 .entry(src_region_id.clone())
1038 .or_default()
1039 .insert(src_id.as_base_64());
1040 inputs_by_region
1041 .entry(dst_region_id.clone())
1042 .or_default()
1043 .insert(dst_id.as_base_64());
1044 }
1045 }
1046
1047 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1048 for rid in region_ids {
1049 let mut inputs: Vec<String> = inputs_by_region
1050 .remove(&rid)
1051 .unwrap_or_default()
1052 .into_iter()
1053 .collect();
1054 let mut outputs: Vec<String> = outputs_by_region
1055 .remove(&rid)
1056 .unwrap_or_default()
1057 .into_iter()
1058 .collect();
1059
1060 inputs.sort();
1061 outputs.sort();
1062
1063 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1064 BduError::InvalidArea(format!(
1065 "Unable to recompute region IO: region '{}' not found in hierarchy",
1066 rid
1067 ))
1068 })?;
1069
1070 if inputs.is_empty() {
1071 region.properties.remove("inputs");
1072 } else {
1073 region
1074 .properties
1075 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1076 }
1077
1078 if outputs.is_empty() {
1079 region.properties.remove("outputs");
1080 } else {
1081 region
1082 .properties
1083 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1084 }
1085
1086 computed.insert(rid, (inputs, outputs));
1087 }
1088
1089 self.refresh_brain_regions_hash();
1090
1091 Ok(computed)
1092 }
1093
1094 pub fn get_root_region_id(&self) -> Option<String> {
1100 self.brain_regions.get_root_region_id()
1101 }
1102
1103 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1105 self.cortical_idx_to_id.get(&cortical_idx)
1106 }
1107
1108 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1111 self.cortical_idx_to_id
1112 .iter()
1113 .map(|(idx, id)| (*idx, id.as_base_64()))
1114 .collect()
1115 }
1116
1117 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1122 let mut granularities = ahash::AHashMap::new();
1123 for (cortical_id, area) in &self.cortical_areas {
1124 let cortical_idx = self
1125 .cortical_id_to_idx
1126 .get(cortical_id)
1127 .copied()
1128 .unwrap_or(0);
1129
1130 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1133 if let Some(arr) = granularity_json.as_array() {
1134 if arr.len() == 3 {
1135 let x_opt = arr[0]
1136 .as_u64()
1137 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1138 let y_opt = arr[1]
1139 .as_u64()
1140 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1141 let z_opt = arr[2]
1142 .as_u64()
1143 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1144
1145 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1146 let granularity = (x as u32, y as u32, z as u32);
1147 if granularity != (1, 1, 1) {
1149 granularities.insert(cortical_idx, granularity);
1150 }
1151 }
1152 }
1153 }
1154 }
1155 }
1156 granularities
1157 }
1158
1159 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1161 self.cortical_areas.keys().collect()
1162 }
1163
1164 pub fn get_cortical_area_count(&self) -> usize {
1166 self.cortical_areas.len()
1167 }
1168
1169 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1183 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1184 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1185 if let Some(upstream_array) = upstream_prop.as_array() {
1186 return upstream_array
1187 .iter()
1188 .filter_map(|v| v.as_u64().map(|n| n as u32))
1189 .collect();
1190 }
1191 }
1192
1193 warn!(target: "feagi-bdu",
1195 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1196 target_cortical_id.as_base_64()
1197 );
1198 }
1199
1200 Vec::new()
1201 }
1202
1203 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1205 upstream
1206 .iter()
1207 .filter_map(|idx| {
1208 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1209 let area = self.cortical_areas.get(cortical_id)?;
1210 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1211 None
1212 } else {
1213 Some(*idx)
1214 }
1215 })
1216 .collect()
1217 }
1218
1219 pub fn refresh_upstream_cortical_areas_from_mappings(
1224 &mut self,
1225 target_cortical_id: &CorticalID,
1226 ) -> Vec<u32> {
1227 use std::collections::HashSet;
1228 let target_id_str = target_cortical_id.as_base_64();
1229 let mut upstream_idxs = HashSet::new();
1230 for (src_id, src_area) in &self.cortical_areas {
1231 if src_id == target_cortical_id {
1232 continue;
1233 }
1234 if let Some(mapping) = src_area
1235 .properties
1236 .get("cortical_mapping_dst")
1237 .and_then(|v| v.as_object())
1238 {
1239 if mapping.contains_key(&target_id_str) {
1240 upstream_idxs.insert(src_area.cortical_idx);
1241 }
1242 }
1243 }
1244
1245 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1246 upstream_list.sort_unstable();
1247
1248 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1249 target_area.properties.insert(
1250 "upstream_cortical_areas".to_string(),
1251 serde_json::json!(upstream_list),
1252 );
1253 }
1254
1255 self.get_upstream_cortical_areas(target_cortical_id)
1256 }
1257
1258 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1268 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1269 let upstream_array = area
1270 .properties
1271 .entry("upstream_cortical_areas".to_string())
1272 .or_insert_with(|| serde_json::json!([]));
1273
1274 if let Some(arr) = upstream_array.as_array_mut() {
1275 let src_value = serde_json::json!(src_cortical_idx);
1276 if !arr.contains(&src_value) {
1277 arr.push(src_value);
1278 info!(target: "feagi-bdu",
1279 "✓ Added upstream area idx={} to cortical area '{}'",
1280 src_cortical_idx, target_cortical_id.as_base_64()
1281 );
1282 }
1283 }
1284 }
1285 }
1286
1287 pub fn get_memory_twin_for_upstream_idx(
1289 &self,
1290 memory_area_idx: u32,
1291 upstream_idx: u32,
1292 ) -> Option<CorticalID> {
1293 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1294 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1295 let area = self.cortical_areas.get(memory_id)?;
1296 let mapping = area
1297 .properties
1298 .get("memory_twin_areas")
1299 .and_then(|v| v.as_object())?;
1300 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1301 CorticalID::try_from_base_64(twin_b64).ok()
1302 }
1303
1304 pub fn ensure_memory_twin_area(
1306 &mut self,
1307 memory_area_id: &CorticalID,
1308 upstream_area_id: &CorticalID,
1309 ) -> BduResult<CorticalID> {
1310 use crate::models::CorticalAreaExt;
1311
1312 let register_replay_mapping = |manager: &mut ConnectomeManager,
1313 twin_id: &CorticalID|
1314 -> BduResult<()> {
1315 let Some(npu) = manager.npu.as_ref() else {
1316 return Ok(());
1317 };
1318 let memory_area_idx =
1319 *manager
1320 .cortical_id_to_idx
1321 .get(memory_area_id)
1322 .ok_or_else(|| {
1323 BduError::InvalidArea(format!(
1324 "Memory area idx missing for {}",
1325 memory_area_id.as_base_64()
1326 ))
1327 })?;
1328 let upstream_area_idx = *manager
1329 .cortical_id_to_idx
1330 .get(upstream_area_id)
1331 .ok_or_else(|| {
1332 BduError::InvalidArea(format!(
1333 "Upstream area idx missing for {}",
1334 upstream_area_id.as_base_64()
1335 ))
1336 })?;
1337 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1338 BduError::InvalidArea(format!(
1339 "Twin area idx missing for {}",
1340 twin_id.as_base_64()
1341 ))
1342 })?;
1343 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1344 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1345 })?;
1346 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1347 if let Ok(mut npu_lock) = npu.lock() {
1348 npu_lock.register_memory_twin_mapping(
1349 memory_area_idx,
1350 upstream_area_idx,
1351 twin_area_idx,
1352 potential,
1353 );
1354 }
1355 Ok(())
1356 };
1357
1358 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1359 BduError::InvalidArea(format!(
1360 "Memory area {} not found",
1361 memory_area_id.as_base_64()
1362 ))
1363 })?;
1364 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1365 BduError::InvalidArea(format!(
1366 "Upstream area {} not found",
1367 upstream_area_id.as_base_64()
1368 ))
1369 })?;
1370
1371 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1372 return Err(BduError::InvalidArea(format!(
1373 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1374 upstream_area_id.as_base_64()
1375 )));
1376 }
1377
1378 if let Some(existing) = memory_area
1379 .properties
1380 .get("memory_twin_areas")
1381 .and_then(|v| v.as_object())
1382 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1383 .and_then(|v| v.as_str())
1384 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1385 {
1386 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1387 register_replay_mapping(self, &existing)?;
1388 self.refresh_cortical_mappings_hash();
1389 return Ok(existing);
1390 }
1391
1392 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1393 if self.cortical_areas.contains_key(&twin_id) {
1394 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1395 let expected_source = upstream_area_id.as_base_64();
1396 let expected_target = memory_area_id.as_base_64();
1397 let existing_source = existing
1398 .properties
1399 .get("memory_twin_of")
1400 .and_then(|v| v.as_str());
1401 let existing_target = existing
1402 .properties
1403 .get("memory_twin_for")
1404 .and_then(|v| v.as_str());
1405 if existing_source != Some(expected_source.as_str())
1406 || existing_target != Some(expected_target.as_str())
1407 {
1408 warn!(
1409 target: "feagi-bdu",
1410 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1411 upstream_area_id.as_base_64(),
1412 memory_area_id.as_base_64()
1413 );
1414 existing.properties.insert(
1415 "memory_twin_of".to_string(),
1416 serde_json::json!(expected_source),
1417 );
1418 existing.properties.insert(
1419 "memory_twin_for".to_string(),
1420 serde_json::json!(expected_target),
1421 );
1422 }
1423 }
1424 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1425 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1426 register_replay_mapping(self, &twin_id)?;
1427 self.refresh_cortical_mappings_hash();
1428 return Ok(twin_id);
1429 }
1430
1431 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1432 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1433 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1434 let mut twin_area = CorticalArea::new(
1435 twin_id,
1436 0,
1437 twin_name,
1438 upstream_area.dimensions,
1439 twin_position,
1440 twin_type,
1441 )?;
1442 twin_area.properties = self.build_memory_twin_properties(
1443 memory_area,
1444 upstream_area,
1445 memory_area_id,
1446 upstream_area_id,
1447 );
1448
1449 let _twin_idx = self.add_cortical_area(twin_area)?;
1450 let _ = self.create_neurons_for_area(&twin_id);
1451
1452 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1453 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1454 register_replay_mapping(self, &twin_id)?;
1455 self.refresh_cortical_mappings_hash();
1456 Ok(twin_id)
1457 }
1458
1459 fn build_memory_twin_position(
1460 &self,
1461 memory_area: &CorticalArea,
1462 upstream_area: &CorticalArea,
1463 ) -> GenomeCoordinate3D {
1464 let memory_parent = memory_area
1465 .properties
1466 .get("parent_region_id")
1467 .and_then(|v| v.as_str());
1468 let upstream_parent = upstream_area
1469 .properties
1470 .get("parent_region_id")
1471 .and_then(|v| v.as_str());
1472 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1473
1474 if !same_region {
1475 return GenomeCoordinate3D::new(
1476 memory_area.position.x + 20,
1477 memory_area.position.y,
1478 memory_area.position.z,
1479 );
1480 }
1481
1482 let width = upstream_area.dimensions.width as f32;
1483 let margin = (width * 0.25).ceil() as i32;
1484 let offset = upstream_area.dimensions.width as i32 + margin;
1485 GenomeCoordinate3D::new(
1486 upstream_area.position.x + offset,
1487 upstream_area.position.y,
1488 upstream_area.position.z,
1489 )
1490 }
1491
1492 fn build_memory_twin_id(
1493 &self,
1494 memory_area_id: &CorticalID,
1495 upstream_area_id: &CorticalID,
1496 ) -> BduResult<CorticalID> {
1497 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1498 hasher.write(memory_area_id.as_base_64().as_bytes());
1499 hasher.write(upstream_area_id.as_base_64().as_bytes());
1500 hasher.write(b"memory_twin");
1501 let hash = hasher.finish();
1502 let mut bytes = hash.to_be_bytes();
1503 bytes[0] = b'c';
1504 CorticalID::try_from_bytes(&bytes)
1505 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1506 }
1507
1508 fn build_memory_twin_properties(
1509 &self,
1510 memory_area: &CorticalArea,
1511 upstream_area: &CorticalArea,
1512 memory_area_id: &CorticalID,
1513 upstream_area_id: &CorticalID,
1514 ) -> HashMap<String, serde_json::Value> {
1515 let mut props = upstream_area.properties.clone();
1516 props.remove("cortical_mapping_dst");
1517 props.remove("upstream_cortical_areas");
1518 props.remove("parent_region_id");
1519 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1520 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1521 props.insert(
1522 "memory_twin_of".to_string(),
1523 serde_json::json!(upstream_area_id.as_base_64()),
1524 );
1525 props.insert(
1526 "memory_twin_for".to_string(),
1527 serde_json::json!(memory_area_id.as_base_64()),
1528 );
1529 if let Some(parent_region_id) = memory_area
1530 .properties
1531 .get("parent_region_id")
1532 .and_then(|v| v.as_str())
1533 {
1534 props.insert(
1535 "parent_region_id".to_string(),
1536 serde_json::json!(parent_region_id),
1537 );
1538 }
1539 props
1540 }
1541
1542 fn set_memory_twin_mapping(
1543 &mut self,
1544 memory_area_id: &CorticalID,
1545 upstream_area_id: &CorticalID,
1546 twin_id: &CorticalID,
1547 ) {
1548 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1549 let mapping = memory_area
1550 .properties
1551 .entry("memory_twin_areas".to_string())
1552 .or_insert_with(|| serde_json::json!({}));
1553 if let Some(map) = mapping.as_object_mut() {
1554 map.insert(
1555 upstream_area_id.as_base_64(),
1556 serde_json::json!(twin_id.as_base_64()),
1557 );
1558 }
1559 }
1560 }
1561
1562 fn ensure_memory_replay_mapping(
1563 &mut self,
1564 memory_area_id: &CorticalID,
1565 twin_id: &CorticalID,
1566 ) -> BduResult<()> {
1567 if !self.morphology_registry.contains("memory_replay") {
1568 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1569 }
1570 self.refresh_morphologies_hash();
1571 let mapping_data = vec![serde_json::json!({
1572 "morphology_id": "memory_replay",
1573 "morphology_scalar": [1, 1, 1],
1574 "postSynapticCurrent_multiplier": 1,
1575 "plasticity_flag": false,
1576 "plasticity_constant": 0,
1577 "ltp_multiplier": 0,
1578 "ltd_multiplier": 0,
1579 "plasticity_window": 0,
1580 })];
1581 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1582 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1583 self.refresh_cortical_area_hashes(true, false);
1585 Ok(())
1586 }
1587
1588 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1598 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1599 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1600 if let Some(arr) = upstream_prop.as_array_mut() {
1601 let src_value = serde_json::json!(src_cortical_idx);
1602 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1603 arr.remove(pos);
1604 debug!(target: "feagi-bdu",
1605 "Removed upstream area idx={} from cortical area '{}'",
1606 src_cortical_idx, target_cortical_id.as_base_64()
1607 );
1608 }
1609 }
1610 }
1611 }
1612 }
1613
1614 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1616 self.cortical_areas.contains_key(cortical_id)
1617 }
1618
1619 pub fn is_initialized(&self) -> bool {
1621 self.initialized && !self.cortical_areas.is_empty()
1622 }
1623
1624 pub fn add_brain_region(
1630 &mut self,
1631 region: BrainRegion,
1632 parent_id: Option<String>,
1633 ) -> BduResult<()> {
1634 self.brain_regions.add_region(region, parent_id)?;
1635 self.refresh_brain_regions_hash();
1636 Ok(())
1637 }
1638
1639 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1641 self.brain_regions.remove_region(region_id)?;
1642 self.refresh_brain_regions_hash();
1643 Ok(())
1644 }
1645
1646 pub fn change_brain_region_parent(
1648 &mut self,
1649 region_id: &str,
1650 new_parent_id: &str,
1651 ) -> BduResult<()> {
1652 self.brain_regions.change_parent(region_id, new_parent_id)?;
1653 self.refresh_brain_regions_hash();
1654 Ok(())
1655 }
1656
1657 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1659 self.brain_regions.get_region(region_id)
1660 }
1661
1662 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1664 self.brain_regions.get_region_mut(region_id)
1665 }
1666
1667 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1669 self.brain_regions.get_all_region_ids()
1670 }
1671
1672 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1674 &self.brain_regions
1675 }
1676
1677 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1683 &self.morphology_registry
1684 }
1685
1686 pub fn get_morphology_count(&self) -> usize {
1688 self.morphology_registry.count()
1689 }
1690
1691 pub fn upsert_morphology(
1696 &mut self,
1697 morphology_id: String,
1698 morphology: feagi_evolutionary::Morphology,
1699 ) {
1700 self.morphology_registry
1701 .add_morphology(morphology_id, morphology);
1702 self.refresh_morphologies_hash();
1703 }
1704
1705 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1711 let removed = self.morphology_registry.remove_morphology(morphology_id);
1712 if removed {
1713 self.refresh_morphologies_hash();
1714 }
1715 removed
1716 }
1717
1718 pub fn update_cortical_mapping(
1736 &mut self,
1737 src_area_id: &CorticalID,
1738 dst_area_id: &CorticalID,
1739 mapping_data: Vec<serde_json::Value>,
1740 ) -> BduResult<()> {
1741 use tracing::info;
1742
1743 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1744
1745 let mapping_has_bidirectional_stdp = |rules: &[serde_json::Value]| -> bool {
1747 for rule in rules {
1748 if let Some(obj) = rule.as_object() {
1749 if let Some(morphology_id) = obj.get("morphology_id").and_then(|v| v.as_str()) {
1750 if morphology_id == "associative_memory" {
1751 return true;
1752 }
1753 }
1754 }
1755 }
1756 false
1757 };
1758
1759 let requested_bidirectional = mapping_has_bidirectional_stdp(&mapping_data);
1760 let existing_bidirectional = self
1761 .cortical_areas
1762 .get(src_area_id)
1763 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1764 .and_then(|v| v.as_object())
1765 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1766 .and_then(|v| v.as_array())
1767 .map(|arr| mapping_has_bidirectional_stdp(arr))
1768 .unwrap_or(false);
1769 let should_apply_bidirectional = requested_bidirectional || existing_bidirectional;
1770
1771 {
1772 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1774 crate::types::BduError::InvalidArea(format!(
1775 "Source area not found: {}",
1776 src_area_id
1777 ))
1778 })?;
1779
1780 let cortical_mapping_dst =
1782 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1783 existing.as_object_mut().ok_or_else(|| {
1784 crate::types::BduError::InvalidMorphology(
1785 "cortical_mapping_dst is not an object".to_string(),
1786 )
1787 })?
1788 } else {
1789 src_area
1791 .properties
1792 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1793 src_area
1794 .properties
1795 .get_mut("cortical_mapping_dst")
1796 .unwrap()
1797 .as_object_mut()
1798 .unwrap()
1799 };
1800
1801 if mapping_data.is_empty() {
1803 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1805 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1806 } else {
1807 cortical_mapping_dst.insert(
1808 dst_area_id.as_base_64(),
1809 serde_json::Value::Array(mapping_data.clone()),
1810 );
1811 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1812 src_area_id, dst_area_id, mapping_data.len());
1813 }
1814 }
1815
1816 if should_apply_bidirectional {
1818 let dst_area = self.cortical_areas.get_mut(dst_area_id).ok_or_else(|| {
1819 crate::types::BduError::InvalidArea(format!(
1820 "Destination area not found: {}",
1821 dst_area_id
1822 ))
1823 })?;
1824 let dst_mapping_dst =
1825 if let Some(existing) = dst_area.properties.get_mut("cortical_mapping_dst") {
1826 existing.as_object_mut().ok_or_else(|| {
1827 crate::types::BduError::InvalidMorphology(
1828 "cortical_mapping_dst is not an object".to_string(),
1829 )
1830 })?
1831 } else {
1832 dst_area
1833 .properties
1834 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1835 dst_area
1836 .properties
1837 .get_mut("cortical_mapping_dst")
1838 .unwrap()
1839 .as_object_mut()
1840 .unwrap()
1841 };
1842
1843 if mapping_data.is_empty() {
1844 dst_mapping_dst.remove(&src_area_id.as_base_64());
1845 info!(
1846 target: "feagi-bdu",
1847 "Removed bi-directional STDP mirror mapping from {} to {}",
1848 dst_area_id,
1849 src_area_id
1850 );
1851 } else {
1852 dst_mapping_dst.insert(
1853 src_area_id.as_base_64(),
1854 serde_json::Value::Array(mapping_data.clone()),
1855 );
1856 info!(
1857 target: "feagi-bdu",
1858 "Updated bi-directional STDP mirror mapping from {} to {} with {} connections",
1859 dst_area_id,
1860 src_area_id,
1861 mapping_data.len()
1862 );
1863 }
1864 }
1865
1866 self.refresh_cortical_mappings_hash();
1867
1868 Ok(())
1869 }
1870
1871 pub fn regenerate_synapses_for_mapping(
1884 &mut self,
1885 src_area_id: &CorticalID,
1886 dst_area_id: &CorticalID,
1887 ) -> BduResult<usize> {
1888 use tracing::info;
1889
1890 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1891
1892 let mapping_rules_len = self
1893 .cortical_areas
1894 .get(src_area_id)
1895 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1896 .and_then(|v| v.as_object())
1897 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1898 .and_then(|v| v.as_array())
1899 .map(|arr| arr.len())
1900 .unwrap_or(0);
1901 tracing::debug!(
1902 target: "feagi-bdu",
1903 "Mapping rules for {} -> {}: {}",
1904 src_area_id,
1905 dst_area_id,
1906 mapping_rules_len
1907 );
1908
1909 let Some(npu_arc) = self.npu.clone() else {
1911 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
1912 return Ok(0);
1913 };
1914
1915 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
1925 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
1926 })?;
1927 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
1928 BduError::InvalidArea(format!(
1929 "No cortical idx for destination area {}",
1930 dst_area_id
1931 ))
1932 })?;
1933
1934 let mut pruned_synapse_count: usize = 0;
1937 use std::time::Instant;
1938 let start = Instant::now();
1939
1940 let (sources, targets) = {
1946 let lock_start = std::time::Instant::now();
1947 let npu = npu_arc.lock().unwrap();
1948 let lock_wait = lock_start.elapsed();
1949 tracing::debug!(
1950 target: "feagi-bdu",
1951 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
1952 lock_wait.as_secs_f64() * 1000.0,
1953 src_area_id,
1954 dst_area_id
1955 );
1956 let sources: Vec<NeuronId> = npu
1957 .get_neurons_in_cortical_area(src_idx)
1958 .into_iter()
1959 .map(NeuronId)
1960 .collect();
1961 let targets: Vec<NeuronId> = npu
1962 .get_neurons_in_cortical_area(dst_idx)
1963 .into_iter()
1964 .map(NeuronId)
1965 .collect();
1966 (sources, targets)
1967 };
1968
1969 tracing::debug!(
1970 target: "feagi-bdu",
1971 "Prune synapses: {} sources, {} targets",
1972 sources.len(),
1973 targets.len()
1974 );
1975
1976 if !sources.is_empty() && !targets.is_empty() {
1977 let remove_start = Instant::now();
1978 pruned_synapse_count = {
1979 let lock_start = std::time::Instant::now();
1980 let mut npu = npu_arc.lock().unwrap();
1981 let lock_wait = lock_start.elapsed();
1982 tracing::debug!(
1983 target: "feagi-bdu",
1984 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
1985 lock_wait.as_secs_f64() * 1000.0,
1986 src_area_id,
1987 dst_area_id
1988 );
1989 npu.remove_synapses_from_sources_to_targets(sources, targets)
1990 };
1991 let remove_time = remove_start.elapsed();
1992 let total_time = start.elapsed();
1993
1994 info!(
1995 target: "feagi-bdu",
1996 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
1997 pruned_synapse_count,
1998 src_area_id,
1999 dst_area_id,
2000 total_time.as_millis(),
2001 remove_time.as_millis()
2002 );
2003
2004 if pruned_synapse_count > 0 {
2006 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2007 BduError::Internal(format!(
2008 "Pruned synapse count overflow (usize -> u32): {}",
2009 pruned_synapse_count
2010 ))
2011 })?;
2012 if let Some(state_manager) = StateManager::instance().try_read() {
2013 let core_state = state_manager.get_core_state();
2014 core_state.subtract_synapse_count(pruned_u32);
2015 state_manager.subtract_cortical_area_outgoing_synapses(
2016 &src_area_id.as_base_64(),
2017 pruned_synapse_count,
2018 );
2019 state_manager.subtract_cortical_area_incoming_synapses(
2020 &dst_area_id.as_base_64(),
2021 pruned_synapse_count,
2022 );
2023 }
2024
2025 {
2029 let mut cache = self.cached_synapse_counts_per_area.write();
2030 let entry = cache
2031 .entry(*src_area_id)
2032 .or_insert_with(|| AtomicUsize::new(0));
2033 let mut current = entry.load(Ordering::Relaxed);
2034 loop {
2035 let next = current.saturating_sub(pruned_synapse_count);
2036 match entry.compare_exchange(
2037 current,
2038 next,
2039 Ordering::Relaxed,
2040 Ordering::Relaxed,
2041 ) {
2042 Ok(_) => break,
2043 Err(v) => current = v,
2044 }
2045 }
2046 }
2047 }
2048 }
2049
2050 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2057 tracing::debug!(
2058 target: "feagi-bdu",
2059 "Synaptogenesis created {} synapses for {} -> {}",
2060 synapse_count,
2061 src_area_id,
2062 dst_area_id
2063 );
2064
2065 if synapse_count > 0 {
2068 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2069 BduError::Internal(format!(
2070 "Created synapse count overflow (usize -> u32): {}",
2071 synapse_count
2072 ))
2073 })?;
2074
2075 {
2077 let mut cache = self.cached_synapse_counts_per_area.write();
2078 cache
2079 .entry(*src_area_id)
2080 .or_insert_with(|| AtomicUsize::new(0))
2081 .fetch_add(synapse_count, Ordering::Relaxed);
2082 }
2083
2084 if let Some(state_manager) = StateManager::instance().try_read() {
2086 let core_state = state_manager.get_core_state();
2087 core_state.add_synapse_count(created_u32);
2088 state_manager
2089 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2090 state_manager
2091 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2092 }
2093 }
2094
2095 let src_idx_for_upstream = src_idx;
2098
2099 let has_mapping = self
2101 .cortical_areas
2102 .get(src_area_id)
2103 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2104 .and_then(|v| v.as_object())
2105 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2106 .is_some();
2107
2108 info!(target: "feagi-bdu",
2109 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2110 synapse_count,
2111 src_area_id.as_base_64(),
2112 dst_area_id.as_base_64(),
2113 has_mapping,
2114 if has_mapping { "" } else { "NOT " }
2115 );
2116
2117 if has_mapping {
2118 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2120
2121 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2122 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2123 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2124 warn!(
2125 target: "feagi-bdu",
2126 "Failed to ensure memory twin for {} -> {}: {}",
2127 src_area_id.as_base_64(),
2128 dst_area_id.as_base_64(),
2129 e
2130 );
2131 }
2132 }
2133 }
2134
2135 #[cfg(feature = "plasticity")]
2137 if let Some(ref executor) = self.plasticity_executor {
2138 use feagi_evolutionary::extract_memory_properties;
2139
2140 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2141 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2142 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2143 let upstream_non_memory =
2144 self.filter_non_memory_upstream_areas(&upstream_areas);
2145 debug!(
2146 target: "feagi-bdu",
2147 "Registering memory area idx={} id={} upstream={} depth={}",
2148 dst_area.cortical_idx,
2149 dst_area_id.as_base_64(),
2150 upstream_areas.len(),
2151 mem_props.temporal_depth
2152 );
2153
2154 if let Some(ref npu_arc) = self.npu {
2157 if let Ok(mut npu) = npu_arc.lock() {
2158 let existing_configs = npu.get_all_fire_ledger_configs();
2159 for &upstream_idx in &upstream_areas {
2160 let existing = existing_configs
2161 .iter()
2162 .find(|(idx, _)| *idx == upstream_idx)
2163 .map(|(_, w)| *w)
2164 .unwrap_or(0);
2165
2166 let desired = mem_props.temporal_depth as usize;
2167 let resolved = existing.max(desired);
2168 if resolved != existing {
2169 if let Err(e) =
2170 npu.configure_fire_ledger_window(upstream_idx, resolved)
2171 {
2172 warn!(
2173 target: "feagi-bdu",
2174 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2175 upstream_idx,
2176 resolved,
2177 e
2178 );
2179 }
2180 }
2181 }
2182 } else {
2183 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2184 }
2185 }
2186
2187 if let Ok(exec) = executor.lock() {
2188 use feagi_npu_plasticity::{
2189 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2190 };
2191
2192 let lifecycle_config = MemoryNeuronLifecycleConfig {
2193 initial_lifespan: mem_props.init_lifespan,
2194 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2195 longterm_threshold: mem_props.longterm_threshold,
2196 max_reactivations: 1000,
2197 };
2198
2199 exec.register_memory_area(
2200 dst_area.cortical_idx,
2201 dst_area_id.as_base_64(),
2202 mem_props.temporal_depth,
2203 upstream_non_memory,
2204 Some(lifecycle_config),
2205 );
2206 } else {
2207 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2208 }
2209 } else {
2210 debug!(
2211 target: "feagi-bdu",
2212 "Skipping plasticity registration: no memory properties for area {}",
2213 dst_area_id.as_base_64()
2214 );
2215 }
2216 } else {
2217 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2218 }
2219 } else {
2220 warn!(
2221 target: "feagi-bdu",
2222 "PlasticityExecutor not available; memory area {} not registered",
2223 dst_area_id.as_base_64()
2224 );
2225 }
2226
2227 #[cfg(not(feature = "plasticity"))]
2228 {
2229 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2230 }
2231 } else {
2232 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2234
2235 let mut npu = npu_arc.lock().unwrap();
2237 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2238 }
2239
2240 info!(
2241 target: "feagi-bdu",
2242 "Created {} new synapses: {} -> {}",
2243 synapse_count,
2244 src_area_id,
2245 dst_area_id
2246 );
2247
2248 if pruned_synapse_count > 0 || synapse_count == 0 {
2251 let mut npu = npu_arc.lock().unwrap();
2252 npu.rebuild_synapse_index();
2253 info!(
2254 target: "feagi-bdu",
2255 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2256 src_area_id,
2257 dst_area_id,
2258 pruned_synapse_count,
2259 synapse_count
2260 );
2261 } else {
2262 info!(
2263 target: "feagi-bdu",
2264 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2265 src_area_id,
2266 dst_area_id,
2267 synapse_count
2268 );
2269 }
2270
2271 {
2273 let npu = npu_arc.lock().unwrap();
2274 let fresh_count = npu.get_synapse_count();
2275 self.cached_synapse_count
2276 .store(fresh_count, Ordering::Relaxed);
2277 }
2278
2279 Ok(synapse_count)
2280 }
2281
2282 #[allow(clippy::too_many_arguments)]
2284 fn register_stdp_mapping_for_rule(
2285 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2286 src_area_id: &CorticalID,
2287 dst_area_id: &CorticalID,
2288 src_cortical_idx: u32,
2289 dst_cortical_idx: u32,
2290 rule_obj: &serde_json::Map<String, serde_json::Value>,
2291 bidirectional_stdp: bool,
2292 synapse_psp: u8,
2293 synapse_type: feagi_npu_neural::SynapseType,
2294 ) -> BduResult<()> {
2295 let plasticity_window = rule_obj
2296 .get("plasticity_window")
2297 .and_then(|v| v.as_u64())
2298 .ok_or_else(|| {
2299 BduError::Internal(format!(
2300 "Missing plasticity_window in plastic mapping rule {} -> {}",
2301 src_area_id, dst_area_id
2302 ))
2303 })? as usize;
2304 let plasticity_constant = rule_obj
2305 .get("plasticity_constant")
2306 .and_then(|v| v.as_i64())
2307 .ok_or_else(|| {
2308 BduError::Internal(format!(
2309 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2310 src_area_id, dst_area_id
2311 ))
2312 })?;
2313 let ltp_multiplier = rule_obj
2314 .get("ltp_multiplier")
2315 .and_then(|v| v.as_i64())
2316 .ok_or_else(|| {
2317 BduError::Internal(format!(
2318 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2319 src_area_id, dst_area_id
2320 ))
2321 })?;
2322 let ltd_multiplier = rule_obj
2323 .get("ltd_multiplier")
2324 .and_then(|v| v.as_i64())
2325 .ok_or_else(|| {
2326 BduError::Internal(format!(
2327 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2328 src_area_id, dst_area_id
2329 ))
2330 })?;
2331
2332 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2333 plasticity_window,
2334 plasticity_constant,
2335 ltp_multiplier,
2336 ltd_multiplier,
2337 bidirectional_stdp,
2338 synapse_psp,
2339 synapse_type,
2340 };
2341
2342 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2343 let mut npu_lock = npu
2344 .lock()
2345 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2346 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2347
2348 npu_lock
2349 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2350 .map_err(|e| {
2351 BduError::Internal(format!(
2352 "Failed to register STDP mapping {} -> {}: {}",
2353 src_area_id, dst_area_id, e
2354 ))
2355 })?;
2356
2357 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2359 for area_idx in [src_cortical_idx, dst_cortical_idx] {
2360 let existing = existing_configs
2361 .iter()
2362 .find(|(idx, _)| *idx == area_idx)
2363 .map(|(_, w)| *w)
2364 .unwrap_or(0);
2365 let resolved = existing.max(plasticity_window);
2366 if resolved != existing {
2367 npu_lock
2368 .configure_fire_ledger_window(area_idx, resolved)
2369 .map_err(|e| {
2370 BduError::Internal(format!(
2371 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2372 area_idx, resolved, e
2373 ))
2374 })?;
2375 }
2376 }
2377
2378 Ok(())
2379 }
2380
2381 fn resolve_synapse_params_for_rule(
2383 &self,
2384 src_area_id: &CorticalID,
2385 rule: &serde_json::Value,
2386 ) -> BduResult<(u8, u8, feagi_npu_neural::SynapseType)> {
2387 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2389 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2390 })?;
2391
2392 let (weight, synapse_type) = {
2398 let parse_i64 = |v: &serde_json::Value| -> Option<i64> {
2400 if let Some(i) = v.as_i64() {
2401 return Some(i);
2402 }
2403 let f = v.as_f64()?;
2404 if f.fract() == 0.0 {
2405 Some(f as i64)
2406 } else {
2407 None
2408 }
2409 };
2410
2411 let multiplier_i64: i64 = if let Some(obj) = rule.as_object() {
2412 obj.get("postSynapticCurrent_multiplier")
2413 .and_then(parse_i64)
2414 .unwrap_or(1) } else if let Some(arr) = rule.as_array() {
2416 arr.get(2).and_then(parse_i64).unwrap_or(1) } else {
2419 128 };
2421
2422 if multiplier_i64 < 0 {
2423 let abs = if multiplier_i64 == i64::MIN {
2424 i64::MAX
2425 } else {
2426 multiplier_i64.abs()
2427 };
2428 (
2429 abs.clamp(0, 255) as u8,
2430 feagi_npu_neural::SynapseType::Inhibitory,
2431 )
2432 } else {
2433 (
2434 multiplier_i64.clamp(0, 255) as u8,
2435 feagi_npu_neural::SynapseType::Excitatory,
2436 )
2437 }
2438 };
2439
2440 let (psp_f32, psp) = {
2447 use crate::models::cortical_area::CorticalAreaExt;
2448 let psp_f32 = src_area.postsynaptic_current();
2449 (psp_f32, psp_f32.clamp(0.0, 255.0) as u8)
2450 };
2451
2452 tracing::debug!(
2453 target: "feagi-bdu",
2454 "Resolved synapse params src={} weight={} psp={} psp_f32={} type={:?}",
2455 src_area_id.as_base_64(),
2456 weight,
2457 psp,
2458 psp_f32,
2459 synapse_type
2460 );
2461
2462 Ok((weight, psp, synapse_type))
2463 }
2464
2465 fn apply_cortical_mapping_for_pair(
2467 &mut self,
2468 src_area_id: &CorticalID,
2469 dst_area_id: &CorticalID,
2470 ) -> BduResult<usize> {
2471 let rules = {
2477 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2478 crate::types::BduError::InvalidArea(format!(
2479 "Source area not found: {}",
2480 src_area_id
2481 ))
2482 })?;
2483
2484 let Some(mapping_dst) = src_area
2485 .properties
2486 .get("cortical_mapping_dst")
2487 .and_then(|v| v.as_object())
2488 else {
2489 return Ok(0);
2490 };
2491
2492 let Some(rules) = mapping_dst
2493 .get(&dst_area_id.as_base_64())
2494 .and_then(|v| v.as_array())
2495 else {
2496 return Ok(0);
2497 };
2498
2499 rules.clone()
2500 }; if rules.is_empty() {
2503 return Ok(0);
2504 }
2505
2506 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2508 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2509 })?;
2510 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2511 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2512 })?;
2513
2514 let npu_arc = self
2516 .npu
2517 .as_ref()
2518 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2519 .clone();
2520
2521 tracing::debug!(
2522 target: "feagi-bdu",
2523 "Applying {} mapping rule(s) for {} -> {}",
2524 rules.len(),
2525 src_area_id,
2526 dst_area_id
2527 );
2528 let mut total_synapses = 0;
2530 for rule in &rules {
2531 let rule_obj = match rule.as_object() {
2532 Some(obj) => obj,
2533 None => continue,
2534 };
2535 let morphology_id = rule_obj
2536 .get("morphology_id")
2537 .and_then(|v| v.as_str())
2538 .unwrap_or("unknown");
2539
2540 let rule_keys: Vec<String> = rule_obj.keys().cloned().collect();
2541
2542 let mut plasticity_flag = rule_obj
2544 .get("plasticity_flag")
2545 .and_then(|v| v.as_bool())
2546 .unwrap_or(false);
2547 if morphology_id == "associative_memory" {
2548 plasticity_flag = true;
2549 }
2550 if plasticity_flag {
2551 let (_weight, psp, synapse_type) =
2552 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2553 let bidirectional_stdp = morphology_id == "associative_memory";
2554 if let Err(e) = Self::register_stdp_mapping_for_rule(
2555 &npu_arc,
2556 src_area_id,
2557 dst_area_id,
2558 src_cortical_idx,
2559 dst_cortical_idx,
2560 rule_obj,
2561 bidirectional_stdp,
2562 psp,
2563 synapse_type,
2564 ) {
2565 tracing::error!(
2566 target: "feagi-bdu",
2567 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2568 src_area_id,
2569 dst_area_id,
2570 morphology_id,
2571 rule_keys,
2572 e
2573 );
2574 return Err(e);
2575 }
2576 }
2577
2578 let synapse_count = match self.apply_single_morphology_rule(
2580 src_area_id,
2581 dst_area_id,
2582 rule,
2583 ) {
2584 Ok(count) => count,
2585 Err(e) => {
2586 tracing::error!(
2587 target: "feagi-bdu",
2588 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2589 src_area_id,
2590 dst_area_id,
2591 morphology_id,
2592 rule_keys,
2593 e
2594 );
2595 return Err(e);
2596 }
2597 };
2598 total_synapses += synapse_count;
2599 tracing::debug!(
2600 target: "feagi-bdu",
2601 "Rule {} created {} synapses for {} -> {}",
2602 morphology_id,
2603 synapse_count,
2604 src_area_id,
2605 dst_area_id
2606 );
2607 }
2608
2609 Ok(total_synapses)
2610 }
2611
2612 #[allow(clippy::too_many_arguments)]
2626 fn apply_function_morphology(
2627 &self,
2628 morphology_id: &str,
2629 rule: &serde_json::Value,
2630 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2631 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2632 src_area_id: &CorticalID,
2633 dst_area_id: &CorticalID,
2634 src_idx: u32,
2635 dst_idx: u32,
2636 weight: u8,
2637 psp: u8,
2638 synapse_attractivity: u8,
2639 synapse_type: feagi_npu_neural::SynapseType,
2640 ) -> BduResult<usize> {
2641 match morphology_id {
2642 "projector" => {
2643 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2645 crate::types::BduError::InvalidArea(format!(
2646 "Source area not found: {}",
2647 src_area_id
2648 ))
2649 })?;
2650 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2651 crate::types::BduError::InvalidArea(format!(
2652 "Destination area not found: {}",
2653 dst_area_id
2654 ))
2655 })?;
2656
2657 let src_dimensions = (
2658 src_area.dimensions.width as usize,
2659 src_area.dimensions.height as usize,
2660 src_area.dimensions.depth as usize,
2661 );
2662 let dst_dimensions = (
2663 dst_area.dimensions.width as usize,
2664 dst_area.dimensions.height as usize,
2665 dst_area.dimensions.depth as usize,
2666 );
2667
2668 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2669 let count = apply_projector_morphology_with_dimensions(
2670 npu,
2671 src_idx,
2672 dst_idx,
2673 src_dimensions,
2674 dst_dimensions,
2675 None, None, weight,
2678 psp,
2679 synapse_attractivity,
2680 synapse_type,
2681 )?;
2682 npu.rebuild_synapse_index();
2684 Ok(count as usize)
2685 }
2686 "episodic_memory" => {
2687 use tracing::trace;
2690 trace!(
2691 target: "feagi-bdu",
2692 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
2693 src_idx, dst_idx
2694 );
2695 Ok(0)
2696 }
2697 "memory_replay" => {
2698 use tracing::trace;
2700 trace!(
2701 target: "feagi-bdu",
2702 "Memory replay morphology: {} -> {} (no physical synapses)",
2703 src_idx, dst_idx
2704 );
2705 Ok(0)
2706 }
2707 "associative_memory" => {
2708 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2712 crate::types::BduError::InvalidArea(format!(
2713 "Source area not found: {}",
2714 src_area_id
2715 ))
2716 })?;
2717 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2718 crate::types::BduError::InvalidArea(format!(
2719 "Destination area not found: {}",
2720 dst_area_id
2721 ))
2722 })?;
2723
2724 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2725 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2726 {
2727 let src_dimensions = (
2728 src_area.dimensions.width as usize,
2729 src_area.dimensions.height as usize,
2730 src_area.dimensions.depth as usize,
2731 );
2732 let dst_dimensions = (
2733 dst_area.dimensions.width as usize,
2734 dst_area.dimensions.height as usize,
2735 dst_area.dimensions.depth as usize,
2736 );
2737 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2738 let count = apply_projector_morphology_with_dimensions(
2739 npu,
2740 src_idx,
2741 dst_idx,
2742 src_dimensions,
2743 dst_dimensions,
2744 None,
2745 None,
2746 weight,
2747 psp,
2748 synapse_attractivity,
2749 synapse_type,
2750 )?;
2751 npu.rebuild_synapse_index();
2752 Ok(count as usize)
2753 } else {
2754 Ok(0)
2755 }
2756 }
2757 "block_to_block" => {
2758 tracing::warn!(
2759 target: "feagi-bdu",
2760 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
2761 src_idx, dst_idx
2762 );
2763 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2765 crate::types::BduError::InvalidArea(format!(
2766 "Source area not found: {}",
2767 src_area_id
2768 ))
2769 })?;
2770 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2771 crate::types::BduError::InvalidArea(format!(
2772 "Destination area not found: {}",
2773 dst_area_id
2774 ))
2775 })?;
2776
2777 let src_dimensions = (
2778 src_area.dimensions.width as usize,
2779 src_area.dimensions.height as usize,
2780 src_area.dimensions.depth as usize,
2781 );
2782 let dst_dimensions = (
2783 dst_area.dimensions.width as usize,
2784 dst_area.dimensions.height as usize,
2785 dst_area.dimensions.depth as usize,
2786 );
2787
2788 let scalar = if let Some(obj) = rule.as_object() {
2790 if let Some(scalar_arr) =
2792 obj.get("morphology_scalar").and_then(|v| v.as_array())
2793 {
2794 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
2796 } else {
2797 1 }
2799 } else if let Some(arr) = rule.as_array() {
2800 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
2802 } else {
2803 1 };
2805
2806 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
2809 let count = if estimated_neurons > 100_000 {
2810 let _ = npu;
2812
2813 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
2814 npu_arc,
2815 src_idx,
2816 dst_idx,
2817 src_dimensions,
2818 dst_dimensions,
2819 scalar, weight,
2821 psp,
2822 synapse_attractivity,
2823 synapse_type,
2824 )? as usize
2825 } else {
2826 tracing::warn!(
2828 target: "feagi-bdu",
2829 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
2830 src_idx, dst_idx, src_dimensions, dst_dimensions
2831 );
2832 let count =
2833 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
2834 npu,
2835 src_idx,
2836 dst_idx,
2837 src_dimensions,
2838 dst_dimensions,
2839 scalar, weight,
2841 psp,
2842 synapse_attractivity,
2843 synapse_type,
2844 )? as usize;
2845 tracing::warn!(
2846 target: "feagi-bdu",
2847 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
2848 count
2849 );
2850 if count > 0 {
2852 npu.rebuild_synapse_index();
2853 }
2854 count
2855 };
2856
2857 if count > 0 && estimated_neurons > 100_000 {
2859 let mut npu_lock = npu_arc.lock().unwrap();
2860 npu_lock.rebuild_synapse_index();
2861 }
2862
2863 Ok(count)
2864 }
2865 _ => {
2866 use tracing::debug;
2869 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
2870 Ok(0)
2871 }
2872 }
2873 }
2874
2875 fn apply_single_morphology_rule(
2877 &mut self,
2878 src_area_id: &CorticalID,
2879 dst_area_id: &CorticalID,
2880 rule: &serde_json::Value,
2881 ) -> BduResult<usize> {
2882 let morphology_id = if let Some(arr) = rule.as_array() {
2884 arr.first().and_then(|v| v.as_str()).unwrap_or("")
2885 } else if let Some(obj) = rule.as_object() {
2886 obj.get("morphology_id")
2887 .and_then(|v| v.as_str())
2888 .unwrap_or("")
2889 } else {
2890 return Ok(0);
2891 };
2892
2893 if morphology_id.is_empty() {
2894 return Ok(0);
2895 }
2896
2897 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
2899 crate::types::BduError::InvalidMorphology(format!(
2900 "Morphology not found: {}",
2901 morphology_id
2902 ))
2903 })?;
2904
2905 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2907 crate::types::BduError::InvalidArea(format!(
2908 "Source area ID not found: {}",
2909 src_area_id
2910 ))
2911 })?;
2912 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2913 crate::types::BduError::InvalidArea(format!(
2914 "Destination area ID not found: {}",
2915 dst_area_id
2916 ))
2917 })?;
2918
2919 if let Some(ref npu_arc) = self.npu {
2921 let lock_start = std::time::Instant::now();
2922 let mut npu = npu_arc.lock().unwrap();
2923 let lock_wait = lock_start.elapsed();
2924 tracing::debug!(
2925 target: "feagi-bdu",
2926 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
2927 lock_wait.as_secs_f64() * 1000.0,
2928 src_area_id,
2929 dst_area_id,
2930 morphology_id
2931 );
2932
2933 let (weight, psp, synapse_type) =
2934 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2935
2936 let synapse_attractivity = if let Some(obj) = rule.as_object() {
2938 obj.get("synapse_attractivity")
2939 .and_then(|v| v.as_u64())
2940 .unwrap_or(100) as u8
2941 } else {
2942 100 };
2944
2945 match morphology.morphology_type {
2946 feagi_evolutionary::MorphologyType::Functions => {
2947 tracing::warn!(
2948 target: "feagi-bdu",
2949 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
2950 morphology_id
2951 );
2952 self.apply_function_morphology(
2955 morphology_id,
2956 rule,
2957 npu_arc,
2958 &mut npu,
2959 src_area_id,
2960 dst_area_id,
2961 *src_idx,
2962 *dst_idx,
2963 weight,
2964 psp,
2965 synapse_attractivity,
2966 synapse_type,
2967 )
2968 }
2969 feagi_evolutionary::MorphologyType::Vectors => {
2970 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
2971
2972 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2974 crate::types::BduError::InvalidArea(format!(
2975 "Destination area not found: {}",
2976 dst_area_id
2977 ))
2978 })?;
2979
2980 let dst_dimensions = (
2981 dst_area.dimensions.width as usize,
2982 dst_area.dimensions.height as usize,
2983 dst_area.dimensions.depth as usize,
2984 );
2985
2986 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
2987 morphology.parameters
2988 {
2989 let vectors_tuples: Vec<(i32, i32, i32)> =
2991 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
2992
2993 let count = apply_vectors_morphology_with_dimensions(
2994 &mut npu,
2995 *src_idx,
2996 *dst_idx,
2997 vectors_tuples,
2998 dst_dimensions,
2999 weight, psp, synapse_attractivity, synapse_type,
3003 )?;
3004 npu.rebuild_synapse_index();
3007 Ok(count as usize)
3008 } else {
3009 Ok(0)
3010 }
3011 }
3012 feagi_evolutionary::MorphologyType::Patterns => {
3013 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3014 use crate::connectivity::rules::patterns::{
3015 Pattern3D, PatternElement as RulePatternElement,
3016 };
3017 use feagi_evolutionary::PatternElement as EvoPatternElement;
3018
3019 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3020 morphology.parameters
3021 else {
3022 return Ok(0);
3023 };
3024
3025 let convert_element =
3026 |element: &EvoPatternElement|
3027 -> crate::types::BduResult<RulePatternElement> {
3028 match element {
3029 EvoPatternElement::Value(value) => {
3030 if *value < 0 {
3031 return Err(crate::types::BduError::InvalidMorphology(
3032 format!(
3033 "Pattern morphology {} contains negative voxel coordinate {}",
3034 morphology_id, value
3035 ),
3036 ));
3037 }
3038 Ok(RulePatternElement::Exact(*value))
3039 }
3040 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3041 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3042 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3043 }
3044 };
3045
3046 let mut converted_patterns = Vec::with_capacity(patterns.len());
3047 for pattern_pair in patterns {
3048 if pattern_pair.len() != 2 {
3049 return Err(crate::types::BduError::InvalidMorphology(format!(
3050 "Pattern morphology {} must contain [src, dst] pairs",
3051 morphology_id
3052 )));
3053 }
3054
3055 let src_pattern = &pattern_pair[0];
3056 let dst_pattern = &pattern_pair[1];
3057
3058 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3059 return Err(crate::types::BduError::InvalidMorphology(format!(
3060 "Pattern morphology {} requires 3-axis patterns",
3061 morphology_id
3062 )));
3063 }
3064
3065 let src: Pattern3D = (
3066 convert_element(&src_pattern[0])?,
3067 convert_element(&src_pattern[1])?,
3068 convert_element(&src_pattern[2])?,
3069 );
3070 let dst: Pattern3D = (
3071 convert_element(&dst_pattern[0])?,
3072 convert_element(&dst_pattern[1])?,
3073 convert_element(&dst_pattern[2])?,
3074 );
3075
3076 converted_patterns.push((src, dst));
3077 }
3078
3079 let count = apply_patterns_morphology(
3080 &mut npu,
3081 *src_idx,
3082 *dst_idx,
3083 converted_patterns,
3084 weight,
3085 psp,
3086 synapse_attractivity,
3087 synapse_type,
3088 )?;
3089 if count > 0 {
3090 npu.rebuild_synapse_index();
3091 }
3092 Ok(count as usize)
3093 }
3094 _ => {
3095 use tracing::debug;
3096 debug!(target: "feagi-bdu", "Morphology type {:?} not yet fully implemented", morphology.morphology_type);
3097 Ok(0)
3098 }
3099 }
3100 } else {
3101 Ok(0) }
3103 }
3104
3105 pub fn set_npu(
3118 &mut self,
3119 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3120 ) {
3121 self.npu = Some(Arc::clone(&npu));
3122 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3123
3124 #[cfg(not(feature = "wasm"))]
3127 {
3128 use feagi_state_manager::StateManager;
3129 if let Some(state_manager) = StateManager::instance().try_read() {
3130 let core_state = state_manager.get_core_state();
3131 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3133 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3134 info!(
3135 target: "feagi-bdu",
3136 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3137 self.config.max_neurons, self.config.max_synapses
3138 );
3139 }
3140 }
3141
3142 let existing_area_count = self.cortical_id_to_idx.len();
3149 if existing_area_count > 0 {
3150 match npu.lock() {
3151 Ok(mut npu_lock) => {
3152 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3153 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3154 }
3155 info!(
3156 target: "feagi-bdu",
3157 "🔁 Backfilled {} cortical area registrations into NPU",
3158 existing_area_count
3159 );
3160 }
3161 Err(e) => {
3162 warn!(
3163 target: "feagi-bdu",
3164 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3165 e
3166 );
3167 }
3168 }
3169 }
3170
3171 self.update_all_cached_stats();
3173 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3174 self.get_neuron_count(), self.get_synapse_count());
3175 }
3176
3177 pub fn has_npu(&self) -> bool {
3179 self.npu.is_some()
3180 }
3181
3182 pub fn get_npu(
3189 &self,
3190 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3191 {
3192 self.npu.as_ref()
3193 }
3194
3195 #[cfg(feature = "plasticity")]
3198 pub fn set_plasticity_executor(
3199 &mut self,
3200 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3201 ) {
3202 self.plasticity_executor = Some(executor);
3203 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3204 }
3205
3206 #[cfg(feature = "plasticity")]
3208 pub fn get_plasticity_executor(
3209 &self,
3210 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3211 self.plasticity_executor.as_ref()
3212 }
3213
3214 pub fn get_neuron_capacity(&self) -> usize {
3226 self.config.max_neurons
3228 }
3229
3230 pub fn get_synapse_capacity(&self) -> usize {
3242 self.config.max_synapses
3244 }
3245
3246 pub fn update_fatigue_index(&self) -> Option<u8> {
3261 let mut last_calc = match self.last_fatigue_calculation.lock() {
3263 Ok(guard) => guard,
3264 Err(_) => return None, };
3266
3267 let now = std::time::Instant::now();
3268 if now.duration_since(*last_calc).as_secs() < 2 {
3269 return None; }
3271 *last_calc = now;
3272 drop(last_calc);
3273
3274 let regular_neuron_count = self.get_neuron_count();
3276 let regular_neuron_capacity = self.get_neuron_capacity();
3277 let regular_neuron_util = if regular_neuron_capacity > 0 {
3278 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3279 } else {
3280 0
3281 };
3282
3283 let memory_neuron_util = match StateManager::instance().try_read() {
3287 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3288 None => {
3289 return None;
3291 }
3292 };
3293
3294 let synapse_count = self.get_synapse_count();
3296 let synapse_capacity = self.get_synapse_capacity();
3297 let synapse_util = if synapse_capacity > 0 {
3298 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3299 } else {
3300 0
3301 };
3302
3303 let fatigue_index = regular_neuron_util
3305 .max(memory_neuron_util)
3306 .max(synapse_util);
3307
3308 let current_fatigue_active = {
3310 StateManager::instance()
3312 .try_read()
3313 .map(|m| m.get_core_state().is_fatigue_active())
3314 .unwrap_or(false)
3315 };
3316
3317 let new_fatigue_active = if fatigue_index >= 85 {
3318 true
3319 } else if fatigue_index < 80 {
3320 false
3321 } else {
3322 current_fatigue_active };
3324
3325 if let Some(state_manager) = StateManager::instance().try_write() {
3329 let core_state = state_manager.get_core_state();
3330 core_state.set_fatigue_index(fatigue_index);
3331 core_state.set_fatigue_active(new_fatigue_active);
3332 core_state.set_regular_neuron_util(regular_neuron_util);
3333 core_state.set_memory_neuron_util(memory_neuron_util);
3334 core_state.set_synapse_util(synapse_util);
3335 } else {
3336 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3338 }
3339
3340 if let Some(ref npu) = self.npu {
3342 if let Ok(mut npu_lock) = npu.lock() {
3343 npu_lock.set_fatigue_active(new_fatigue_active);
3344 }
3345 }
3346
3347 trace!(
3348 target: "feagi-bdu",
3349 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3350 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3351 );
3352
3353 Some(fatigue_index)
3354 }
3355
3356 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3373 let area = self
3375 .cortical_areas
3376 .get(cortical_id)
3377 .ok_or_else(|| {
3378 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3379 })?
3380 .clone();
3381
3382 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3384 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3385 })?;
3386
3387 let npu = self
3389 .npu
3390 .as_ref()
3391 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3392
3393 use crate::models::CorticalAreaExt;
3396 let per_voxel_cnt = area.neurons_per_voxel();
3397 let firing_threshold = area.firing_threshold();
3398 let firing_threshold_increment_x = area.firing_threshold_increment_x();
3399 let firing_threshold_increment_y = area.firing_threshold_increment_y();
3400 let firing_threshold_increment_z = area.firing_threshold_increment_z();
3401 let firing_threshold_limit_raw = area.firing_threshold_limit();
3403 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
3404 f32::MAX } else {
3406 firing_threshold_limit_raw
3407 };
3408
3409 if firing_threshold_increment_x != 0.0
3411 || firing_threshold_increment_y != 0.0
3412 || firing_threshold_increment_z != 0.0
3413 {
3414 info!(
3415 target: "feagi-bdu",
3416 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
3417 cortical_id.as_base_64(),
3418 firing_threshold_increment_x,
3419 firing_threshold_increment_y,
3420 firing_threshold_increment_z
3421 );
3422 } else {
3423 if area.properties.contains_key("firing_threshold_increment_x")
3425 || area.properties.contains_key("firing_threshold_increment_y")
3426 || area.properties.contains_key("firing_threshold_increment_z")
3427 {
3428 info!(
3429 target: "feagi-bdu",
3430 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
3431 cortical_id.as_base_64(),
3432 area.properties.get("firing_threshold_increment_x"),
3433 area.properties.get("firing_threshold_increment_y"),
3434 area.properties.get("firing_threshold_increment_z")
3435 );
3436 }
3437 }
3438
3439 let leak_coefficient = area.leak_coefficient();
3440 let excitability = area.neuron_excitability();
3441 let refractory_period = area.refractory_period();
3442 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
3444 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
3445 u16::MAX } else {
3447 consecutive_fire_limit_raw
3448 };
3449 let snooze_length = area.snooze_period();
3450 let mp_charge_accumulation = area.mp_charge_accumulation();
3451
3452 let voxels = area.dimensions.width as usize
3454 * area.dimensions.height as usize
3455 * area.dimensions.depth as usize;
3456 let expected_neurons = voxels * per_voxel_cnt as usize;
3457
3458 trace!(
3459 target: "feagi-bdu",
3460 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
3461 cortical_id.as_base_64(),
3462 area.dimensions.width,
3463 area.dimensions.height,
3464 area.dimensions.depth,
3465 per_voxel_cnt,
3466 expected_neurons
3467 );
3468
3469 let mut npu_lock = npu
3472 .lock()
3473 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3474
3475 let neuron_count = npu_lock
3476 .create_cortical_area_neurons(
3477 *cortical_idx,
3478 area.dimensions.width,
3479 area.dimensions.height,
3480 area.dimensions.depth,
3481 per_voxel_cnt,
3482 firing_threshold,
3483 firing_threshold_increment_x,
3484 firing_threshold_increment_y,
3485 firing_threshold_increment_z,
3486 firing_threshold_limit,
3487 leak_coefficient,
3488 0.0, 0, refractory_period,
3491 excitability,
3492 consecutive_fire_limit,
3493 snooze_length,
3494 mp_charge_accumulation,
3495 )
3496 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?;
3497
3498 trace!(
3499 target: "feagi-bdu",
3500 "Created {} neurons for area {} via NPU",
3501 neuron_count,
3502 cortical_id.as_base_64()
3503 );
3504
3505 {
3508 let mut cache = self.cached_neuron_counts_per_area.write();
3509 cache
3510 .entry(*cortical_id)
3511 .or_insert_with(|| AtomicUsize::new(0))
3512 .store(neuron_count as usize, Ordering::Relaxed);
3513 }
3514
3515 if let Some(state_manager) = StateManager::instance().try_read() {
3517 state_manager
3518 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
3519 }
3520
3521 self.cached_neuron_count
3523 .fetch_add(neuron_count as usize, Ordering::Relaxed);
3524
3525 if let Some(state_manager) = StateManager::instance().try_read() {
3527 let core_state = state_manager.get_core_state();
3528 core_state.add_neuron_count(neuron_count);
3529 core_state.add_regular_neuron_count(neuron_count);
3530 }
3531
3532 Ok(neuron_count)
3540 }
3541
3542 #[allow(clippy::too_many_arguments)]
3566 pub fn add_neuron(
3567 &mut self,
3568 cortical_id: &CorticalID,
3569 x: u32,
3570 y: u32,
3571 z: u32,
3572 firing_threshold: f32,
3573 firing_threshold_limit: f32,
3574 leak_coefficient: f32,
3575 resting_potential: f32,
3576 neuron_type: u8,
3577 refractory_period: u16,
3578 excitability: f32,
3579 consecutive_fire_limit: u16,
3580 snooze_length: u16,
3581 mp_charge_accumulation: bool,
3582 ) -> BduResult<u64> {
3583 if !self.cortical_areas.contains_key(cortical_id) {
3585 return Err(BduError::InvalidArea(format!(
3586 "Cortical area {} not found",
3587 cortical_id
3588 )));
3589 }
3590
3591 let cortical_idx = *self
3592 .cortical_id_to_idx
3593 .get(cortical_id)
3594 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
3595
3596 let npu = self
3598 .npu
3599 .as_ref()
3600 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3601
3602 let mut npu_lock = npu
3603 .lock()
3604 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3605
3606 let neuron_id = npu_lock
3608 .add_neuron(
3609 firing_threshold,
3610 firing_threshold_limit,
3611 leak_coefficient,
3612 resting_potential,
3613 neuron_type as i32,
3614 refractory_period,
3615 excitability,
3616 consecutive_fire_limit,
3617 snooze_length,
3618 mp_charge_accumulation,
3619 cortical_idx,
3620 x,
3621 y,
3622 z,
3623 )
3624 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
3625
3626 trace!(
3627 target: "feagi-bdu",
3628 "Created neuron {} in area {} at ({}, {}, {})",
3629 neuron_id.0,
3630 cortical_id,
3631 x,
3632 y,
3633 z
3634 );
3635
3636 if let Some(state_manager) = StateManager::instance().try_read() {
3638 let core_state = state_manager.get_core_state();
3639 core_state.add_neuron_count(1);
3640 core_state.add_regular_neuron_count(1);
3641 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3642 }
3643
3644 Ok(neuron_id.0 as u64)
3645 }
3646
3647 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
3658 let npu = self
3660 .npu
3661 .as_ref()
3662 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3663
3664 let mut npu_lock = npu
3665 .lock()
3666 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3667
3668 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
3669 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
3670
3671 let deleted = npu_lock.delete_neuron(neuron_id as u32);
3672
3673 if deleted {
3674 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
3675
3676 if let Some(state_manager) = StateManager::instance().try_read() {
3678 let core_state = state_manager.get_core_state();
3679 core_state.subtract_neuron_count(1);
3680 core_state.subtract_regular_neuron_count(1);
3681 if let Some(cortical_id) = cortical_id {
3682 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3683 }
3684 }
3685
3686 }
3690
3691 Ok(deleted)
3692 }
3693
3694 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
3708 let src_area = self
3710 .cortical_areas
3711 .get(src_cortical_id)
3712 .ok_or_else(|| {
3713 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
3714 })?
3715 .clone();
3716
3717 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
3719 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
3720 _ => return Ok(0), };
3722
3723 let src_cortical_idx = *self
3724 .cortical_id_to_idx
3725 .get(src_cortical_id)
3726 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
3727
3728 let mut total_synapses = 0u32;
3729 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
3733 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
3735 Ok(id) => id,
3736 Err(_) => {
3737 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
3738 continue;
3739 }
3740 };
3741
3742 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
3744 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
3745 continue;
3746 }
3747
3748 let synapse_count =
3750 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
3751 total_synapses += synapse_count as u32;
3752
3753 upstream_updates.push((dst_cortical_id, src_cortical_idx));
3756 }
3757
3758 for (dst_id, src_idx) in upstream_updates {
3760 self.add_upstream_area(&dst_id, src_idx);
3761 }
3762
3763 trace!(
3764 target: "feagi-bdu",
3765 "Created {} synapses for area {} via NPU",
3766 total_synapses,
3767 src_cortical_id
3768 );
3769
3770 if total_synapses > 0 {
3773 let mut cache = self.cached_synapse_counts_per_area.write();
3774 cache
3775 .entry(*src_cortical_id)
3776 .or_insert_with(|| AtomicUsize::new(0))
3777 .fetch_add(total_synapses as usize, Ordering::Relaxed);
3778 }
3779
3780 self.cached_synapse_count
3782 .fetch_add(total_synapses as usize, Ordering::Relaxed);
3783
3784 if total_synapses > 0 {
3786 if let Some(state_manager) = StateManager::instance().try_read() {
3787 let core_state = state_manager.get_core_state();
3788 core_state.add_synapse_count(total_synapses);
3789 }
3790 }
3791
3792 Ok(total_synapses)
3793 }
3794
3795 pub fn has_neuron(&self, neuron_id: u64) -> bool {
3814 if let Some(ref npu) = self.npu {
3815 if let Ok(npu_lock) = npu.lock() {
3816 npu_lock.is_neuron_valid(neuron_id as u32)
3818 } else {
3819 false
3820 }
3821 } else {
3822 false
3823 }
3824 }
3825
3826 pub fn get_neuron_count(&self) -> usize {
3838 if let Some(ref npu) = self.npu {
3840 if let Ok(npu_lock) = npu.try_lock() {
3841 let fresh_count = npu_lock.get_neuron_count();
3842 self.cached_neuron_count
3843 .store(fresh_count, Ordering::Relaxed);
3844 }
3845 }
3847
3848 self.cached_neuron_count.load(Ordering::Relaxed)
3850 }
3851
3852 pub fn update_cached_neuron_count(&self) {
3858 if let Some(ref npu) = self.npu {
3859 if let Ok(npu_lock) = npu.try_lock() {
3860 let count = npu_lock.get_neuron_count();
3861 self.cached_neuron_count.store(count, Ordering::Relaxed);
3862 }
3863 }
3864 }
3865
3866 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
3870 let npu = self.npu.as_ref()?;
3871 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
3872 let npu_lock = npu.lock().ok()?;
3873 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
3874 drop(npu_lock);
3875
3876 let mut cache = self.cached_neuron_counts_per_area.write();
3877 cache
3878 .entry(*cortical_id)
3879 .or_insert_with(|| AtomicUsize::new(0))
3880 .store(count, Ordering::Relaxed);
3881
3882 if let Some(state_manager) = StateManager::instance().try_read() {
3884 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
3885 }
3886
3887 self.update_cached_neuron_count();
3888
3889 Some(count)
3890 }
3891
3892 pub fn get_synapse_count(&self) -> usize {
3904 if let Some(ref npu) = self.npu {
3906 if let Ok(npu_lock) = npu.try_lock() {
3907 let fresh_count = npu_lock.get_synapse_count();
3908 self.cached_synapse_count
3909 .store(fresh_count, Ordering::Relaxed);
3910 }
3911 }
3913
3914 self.cached_synapse_count.load(Ordering::Relaxed)
3916 }
3917
3918 pub fn update_cached_synapse_count(&self) {
3924 if let Some(ref npu) = self.npu {
3925 if let Ok(npu_lock) = npu.try_lock() {
3926 let count = npu_lock.get_synapse_count();
3927 self.cached_synapse_count.store(count, Ordering::Relaxed);
3928 }
3929 }
3930 }
3931
3932 pub fn update_all_cached_stats(&self) {
3938 self.update_cached_neuron_count();
3939 self.update_cached_synapse_count();
3940 }
3941
3942 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
3953 if let Some(ref npu) = self.npu {
3954 if let Ok(npu_lock) = npu.lock() {
3955 npu_lock
3956 .get_neuron_coordinates(neuron_id as u32)
3957 .unwrap_or((0, 0, 0))
3958 } else {
3959 (0, 0, 0)
3960 }
3961 } else {
3962 (0, 0, 0)
3963 }
3964 }
3965
3966 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
3977 if let Some(ref npu) = self.npu {
3978 if let Ok(npu_lock) = npu.lock() {
3979 npu_lock.get_neuron_cortical_area(neuron_id as u32)
3980 } else {
3981 0
3982 }
3983 } else {
3984 0
3985 }
3986 }
3987
3988 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
3999 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4001 Some(idx) => *idx,
4002 None => return Vec::new(),
4003 };
4004
4005 if let Some(ref npu) = self.npu {
4006 if let Ok(npu_lock) = npu.lock() {
4007 npu_lock
4009 .get_neurons_in_cortical_area(cortical_idx)
4010 .into_iter()
4011 .map(|id| id as u64)
4012 .collect()
4013 } else {
4014 Vec::new()
4015 }
4016 } else {
4017 Vec::new()
4018 }
4019 }
4020
4021 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4032 if let Some(ref npu) = self.npu {
4033 if let Ok(npu_lock) = npu.lock() {
4034 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4035 } else {
4036 Vec::new()
4037 }
4038 } else {
4039 Vec::new()
4040 }
4041 }
4042
4043 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4054 if let Some(ref npu) = self.npu {
4055 if let Ok(npu_lock) = npu.lock() {
4056 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4057 } else {
4058 Vec::new()
4059 }
4060 } else {
4061 Vec::new()
4062 }
4063 }
4064
4065 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4091 let cache = self.cached_neuron_counts_per_area.read();
4093 let base_count = cache
4094 .get(cortical_id)
4095 .map(|count| count.load(Ordering::Relaxed))
4096 .unwrap_or(0);
4097
4098 let memory_count = self
4100 .cortical_areas
4101 .get(cortical_id)
4102 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4103 .and_then(|_| {
4104 StateManager::instance()
4105 .try_read()
4106 .and_then(|state_manager| {
4107 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4108 })
4109 })
4110 .map(|stats| stats.neuron_count)
4111 .unwrap_or(0);
4112
4113 base_count.saturating_add(memory_count)
4114 }
4115
4116 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4123 let mut result = Vec::new();
4124
4125 for cortical_id in self.cortical_areas.keys() {
4126 let count = self.get_neuron_count_in_area(cortical_id);
4127 if count > 0 {
4128 result.push((cortical_id.to_string(), count));
4129 }
4130 }
4131
4132 result
4133 }
4134
4135 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4146 self.get_neuron_count_in_area(cortical_id) > 0
4147 }
4148
4149 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4165 let cache = self.cached_synapse_counts_per_area.read();
4167 cache
4168 .get(cortical_id)
4169 .map(|count| count.load(Ordering::Relaxed))
4170 .unwrap_or(0)
4171 }
4172
4173 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4183 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4184 return 0;
4185 }
4186
4187 if let Some(state_manager) = StateManager::instance().try_read() {
4188 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4189 return stats.incoming_synapse_count;
4190 }
4191 }
4192
4193 0
4194 }
4195
4196 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4206 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4207 return 0;
4208 }
4209
4210 if let Some(state_manager) = StateManager::instance().try_read() {
4211 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4212 return stats.outgoing_synapse_count;
4213 }
4214 }
4215
4216 0
4217 }
4218
4219 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4231 let synapses = self.get_outgoing_synapses(source_neuron_id);
4232 synapses
4233 .iter()
4234 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4235 }
4236
4237 pub fn get_connection_weight(
4249 &self,
4250 source_neuron_id: u64,
4251 target_neuron_id: u64,
4252 ) -> Option<u8> {
4253 let synapses = self.get_outgoing_synapses(source_neuron_id);
4254 synapses
4255 .iter()
4256 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4257 .map(|(_, weight, _, _)| *weight)
4258 }
4259
4260 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4271 let neurons = self.get_neurons_in_area(cortical_id);
4272 let neuron_count = neurons.len();
4273
4274 if neuron_count == 0 {
4275 return (0, 0, 0.0);
4276 }
4277
4278 let mut total_synapses = 0;
4279 for neuron_id in neurons {
4280 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4281 }
4282
4283 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4284
4285 (neuron_count, total_synapses, avg_synapses)
4286 }
4287
4288 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4299 let cortical_idx = self.get_neuron_cortical_idx(neuron_id);
4300 self.cortical_idx_to_id.get(&cortical_idx).copied()
4301 }
4302
4303 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4314 let area = match self.cortical_areas.get(cortical_id) {
4315 Some(a) => a,
4316 None => return 0.0,
4317 };
4318
4319 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4320 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4321
4322 if volume == 0 {
4323 return 0.0;
4324 }
4325
4326 neuron_count as f32 / volume as f32
4327 }
4328
4329 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
4336 let mut stats = Vec::new();
4337
4338 for cortical_id in self.cortical_areas.keys() {
4339 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4340 let synapse_count = self.get_synapse_count_in_area(cortical_id);
4341 let density = self.get_neuron_density(cortical_id);
4342
4343 stats.push((
4344 cortical_id.to_string(),
4345 neuron_count,
4346 synapse_count,
4347 density,
4348 ));
4349 }
4350
4351 stats
4352 }
4353
4354 pub fn get_config(&self) -> &ConnectomeConfig {
4360 &self.config
4361 }
4362
4363 pub fn set_config(&mut self, config: ConnectomeConfig) {
4365 self.config = config;
4366 }
4367
4368 pub fn load_genome_from_json(&mut self, json_str: &str) -> BduResult<()> {
4409 let parsed = feagi_evolutionary::GenomeParser::parse(json_str)?;
4411
4412 info!(target: "feagi-bdu","🧬 Loading genome: {} (version {})",
4413 parsed.genome_title, parsed.version);
4414 info!(target: "feagi-bdu","🧬 Cortical areas: {}", parsed.cortical_areas.len());
4415 info!(target: "feagi-bdu","🧬 Brain regions: {}", parsed.brain_regions.len());
4416
4417 self.cortical_areas.clear();
4419 self.cortical_id_to_idx.clear();
4420 self.cortical_idx_to_id.clear();
4421 self.next_cortical_idx = 3;
4423 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4424 self.brain_regions = crate::models::BrainRegionHierarchy::new();
4425
4426 for area in parsed.cortical_areas {
4428 let cortical_idx = self.add_cortical_area(area)?;
4429 debug!(target: "feagi-bdu"," ✅ Added cortical area {} (idx: {})",
4430 self.cortical_idx_to_id.get(&cortical_idx).unwrap(), cortical_idx);
4431 }
4432
4433 self.ensure_core_cortical_areas()?;
4436
4437 for (region, parent_id) in parsed.brain_regions {
4439 let region_id = region.region_id;
4440 let parent_id_clone = parent_id.clone();
4441 self.brain_regions.add_region(region, parent_id_clone)?;
4442 debug!(target: "feagi-bdu"," ✅ Added brain region {} (parent: {:?})",
4443 region_id, parent_id);
4444 }
4445
4446 self.initialized = true;
4447
4448 info!(target: "feagi-bdu","🧬 ✅ Genome loaded successfully!");
4449
4450 Ok(())
4451 }
4452
4453 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
4468 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
4469
4470 use feagi_structures::genomic::cortical_area::{
4471 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
4472 };
4473
4474 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
4476 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
4477 })?;
4478
4479 let core_position = (0, 0, 0).into();
4481
4482 let death_id = CoreCorticalType::Death.to_cortical_id();
4484 if !self.cortical_areas.contains_key(&death_id) {
4485 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
4486 let death_area = CorticalArea::new(
4487 death_id,
4488 0, "_death".to_string(),
4490 core_dimensions,
4491 core_position,
4492 CorticalAreaType::Core(CoreCorticalType::Death),
4493 )
4494 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
4495 match self.add_cortical_area(death_area) {
4496 Ok(idx) => {
4497 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
4498 }
4499 Err(e) => {
4500 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
4501 return Err(e);
4502 }
4503 }
4504 } else {
4505 info!(target: "feagi-bdu", " ✓ _death area already exists");
4506 }
4507
4508 let power_id = CoreCorticalType::Power.to_cortical_id();
4510 if !self.cortical_areas.contains_key(&power_id) {
4511 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
4512 let power_area = CorticalArea::new(
4513 power_id,
4514 1, "_power".to_string(),
4516 core_dimensions,
4517 core_position,
4518 CorticalAreaType::Core(CoreCorticalType::Power),
4519 )
4520 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
4521 match self.add_cortical_area(power_area) {
4522 Ok(idx) => {
4523 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
4524 }
4525 Err(e) => {
4526 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
4527 return Err(e);
4528 }
4529 }
4530 } else {
4531 info!(target: "feagi-bdu", " ✓ _power area already exists");
4532 }
4533
4534 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
4536 if !self.cortical_areas.contains_key(&fatigue_id) {
4537 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
4538 let fatigue_area = CorticalArea::new(
4539 fatigue_id,
4540 2, "_fatigue".to_string(),
4542 core_dimensions,
4543 core_position,
4544 CorticalAreaType::Core(CoreCorticalType::Fatigue),
4545 )
4546 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
4547 match self.add_cortical_area(fatigue_area) {
4548 Ok(idx) => {
4549 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
4550 }
4551 Err(e) => {
4552 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
4553 return Err(e);
4554 }
4555 }
4556 } else {
4557 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
4558 }
4559
4560 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
4561 Ok(())
4562 }
4563
4564 #[deprecated(
4581 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
4582 )]
4583 #[allow(deprecated)]
4584 pub fn save_genome_to_json(
4585 &self,
4586 genome_id: Option<String>,
4587 genome_title: Option<String>,
4588 ) -> BduResult<String> {
4589 let mut brain_regions_with_parents = std::collections::HashMap::new();
4591
4592 for region_id in self.brain_regions.get_all_region_ids() {
4593 if let Some(region) = self.brain_regions.get_region(region_id) {
4594 let parent_id = self
4595 .brain_regions
4596 .get_parent(region_id)
4597 .map(|s| s.to_string());
4598 brain_regions_with_parents
4599 .insert(region_id.to_string(), (region.clone(), parent_id));
4600 }
4601 }
4602
4603 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
4605 &self.cortical_areas,
4606 &brain_regions_with_parents,
4607 genome_id,
4608 genome_title,
4609 )?)
4610 }
4611
4612 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
4643 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
4644
4645 self.cortical_areas.clear();
4647 self.cortical_id_to_idx.clear();
4648 self.cortical_idx_to_id.clear();
4649 self.next_cortical_idx = 3;
4651 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4652
4653 self.brain_regions = BrainRegionHierarchy::new();
4655
4656 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
4664 Ok(())
4665 }
4666
4667 pub fn resize_for_genome(
4677 &mut self,
4678 genome: &feagi_evolutionary::RuntimeGenome,
4679 ) -> BduResult<()> {
4680 self.morphology_registry = genome.morphologies.clone();
4682 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
4683
4684 let required_neurons = genome.stats.innate_neuron_count;
4686 let required_synapses = genome.stats.innate_synapse_count;
4687
4688 info!(target: "feagi-bdu",
4689 "Genome requires: {} neurons, {} synapses",
4690 required_neurons,
4691 required_synapses
4692 );
4693
4694 let mut total_voxels = 0;
4696 for area in genome.cortical_areas.values() {
4697 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4698 }
4699
4700 info!(target: "feagi-bdu",
4701 "Genome has {} cortical areas with {} total voxels",
4702 genome.cortical_areas.len(),
4703 total_voxels
4704 );
4705
4706 Ok(())
4711 }
4712
4713 pub fn create_synapse(
4732 &mut self,
4733 source_neuron_id: u64,
4734 target_neuron_id: u64,
4735 weight: u8,
4736 psp: u8,
4737 synapse_type: u8,
4738 ) -> BduResult<()> {
4739 let npu = self
4741 .npu
4742 .as_ref()
4743 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4744
4745 let mut npu_lock = npu
4746 .lock()
4747 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4748
4749 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4751 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4752
4753 if !source_exists {
4754 return Err(BduError::InvalidNeuron(format!(
4755 "Source neuron {} not found",
4756 source_neuron_id
4757 )));
4758 }
4759 if !target_exists {
4760 return Err(BduError::InvalidNeuron(format!(
4761 "Target neuron {} not found",
4762 target_neuron_id
4763 )));
4764 }
4765
4766 let syn_type = if synapse_type == 0 {
4768 feagi_npu_neural::synapse::SynapseType::Excitatory
4769 } else {
4770 feagi_npu_neural::synapse::SynapseType::Inhibitory
4771 };
4772
4773 let synapse_idx = npu_lock
4774 .add_synapse(
4775 NeuronId(source_neuron_id as u32),
4776 NeuronId(target_neuron_id as u32),
4777 feagi_npu_neural::types::SynapticWeight(weight),
4778 feagi_npu_neural::types::SynapticPsp(psp),
4779 syn_type,
4780 )
4781 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
4782
4783 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
4784 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
4785
4786 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
4787 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
4788 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
4789 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
4790
4791 if let Some(state_manager) = StateManager::instance().try_read() {
4792 let core_state = state_manager.get_core_state();
4793 core_state.add_synapse_count(1);
4794 if let Some(cortical_id) = source_cortical_id {
4795 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
4796 }
4797 if let Some(cortical_id) = target_cortical_id {
4798 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
4799 }
4800 }
4801
4802 Ok(())
4807 }
4808
4809 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
4812 if let Some(ref npu) = self.npu {
4813 if let Ok(mut npu_lock) = npu.lock() {
4814 let mut psp_uniform_flags = ahash::AHashMap::new();
4816 let mut mp_driven_psp_flags = ahash::AHashMap::new();
4817
4818 for (cortical_id, area) in &self.cortical_areas {
4819 let psp_uniform = area
4821 .get_property("psp_uniform_distribution")
4822 .and_then(|v| v.as_bool())
4823 .unwrap_or(false);
4824 psp_uniform_flags.insert(*cortical_id, psp_uniform);
4825
4826 let mp_driven_psp = area
4828 .get_property("mp_driven_psp")
4829 .and_then(|v| v.as_bool())
4830 .unwrap_or(false);
4831 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
4832 }
4833
4834 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
4836 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
4837
4838 trace!(
4839 target: "feagi-bdu",
4840 "Synchronized cortical area flags to NPU ({} areas)",
4841 self.cortical_areas.len()
4842 );
4843 }
4844 }
4845
4846 Ok(())
4847 }
4848
4849 pub fn get_synapse(
4861 &self,
4862 source_neuron_id: u64,
4863 target_neuron_id: u64,
4864 ) -> Option<(u8, u8, u8)> {
4865 let npu = self.npu.as_ref()?;
4867 let npu_lock = npu.lock().ok()?;
4868
4869 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
4872
4873 for (source_id, weight, psp, synapse_type) in incoming {
4875 if source_id == source_neuron_id as u32 {
4876 return Some((weight, psp, synapse_type));
4877 }
4878 }
4879
4880 None
4881 }
4882
4883 pub fn update_synapse_weight(
4896 &mut self,
4897 source_neuron_id: u64,
4898 target_neuron_id: u64,
4899 new_weight: 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 updated = npu_lock.update_synapse_weight(
4913 NeuronId(source_neuron_id as u32),
4914 NeuronId(target_neuron_id as u32),
4915 feagi_npu_neural::types::SynapticWeight(new_weight),
4916 );
4917
4918 if updated {
4919 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
4920 Ok(())
4921 } else {
4922 Err(BduError::InvalidSynapse(format!(
4923 "Synapse {} -> {} not found",
4924 source_neuron_id, target_neuron_id
4925 )))
4926 }
4927 }
4928
4929 pub fn remove_synapse(
4941 &mut self,
4942 source_neuron_id: u64,
4943 target_neuron_id: u64,
4944 ) -> BduResult<bool> {
4945 let npu = self
4947 .npu
4948 .as_ref()
4949 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4950
4951 let mut npu_lock = npu
4952 .lock()
4953 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4954
4955 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
4956 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
4957 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
4958 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
4959
4960 let removed = npu_lock.remove_synapse(
4962 NeuronId(source_neuron_id as u32),
4963 NeuronId(target_neuron_id as u32),
4964 );
4965
4966 if removed {
4967 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
4968
4969 if let Some(state_manager) = StateManager::instance().try_read() {
4971 let core_state = state_manager.get_core_state();
4972 core_state.subtract_synapse_count(1);
4973 if let Some(cortical_id) = source_cortical_id {
4974 state_manager
4975 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
4976 }
4977 if let Some(cortical_id) = target_cortical_id {
4978 state_manager
4979 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
4980 }
4981 }
4982 }
4983
4984 Ok(removed)
4985 }
4986
4987 pub fn batch_create_neurons(
5005 &mut self,
5006 cortical_id: &CorticalID,
5007 neurons: Vec<NeuronData>,
5008 ) -> BduResult<Vec<u64>> {
5009 let npu = self
5011 .npu
5012 .as_ref()
5013 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5014
5015 let mut npu_lock = npu
5016 .lock()
5017 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5018
5019 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5021 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5022 })?;
5023 let cortical_idx = area.cortical_idx;
5024
5025 let count = neurons.len();
5026
5027 let mut x_coords = Vec::with_capacity(count);
5029 let mut y_coords = Vec::with_capacity(count);
5030 let mut z_coords = Vec::with_capacity(count);
5031 let mut firing_thresholds = Vec::with_capacity(count);
5032 let mut threshold_limits = Vec::with_capacity(count);
5033 let mut leak_coeffs = Vec::with_capacity(count);
5034 let mut resting_potentials = Vec::with_capacity(count);
5035 let mut neuron_types = Vec::with_capacity(count);
5036 let mut refractory_periods = Vec::with_capacity(count);
5037 let mut excitabilities = Vec::with_capacity(count);
5038 let mut consec_fire_limits = Vec::with_capacity(count);
5039 let mut snooze_lengths = Vec::with_capacity(count);
5040 let mut mp_accums = Vec::with_capacity(count);
5041 let mut cortical_areas = Vec::with_capacity(count);
5042
5043 for (
5044 x,
5045 y,
5046 z,
5047 threshold,
5048 threshold_limit,
5049 leak,
5050 resting,
5051 ntype,
5052 refract,
5053 excit,
5054 consec_limit,
5055 snooze,
5056 mp_accum,
5057 ) in neurons
5058 {
5059 x_coords.push(x);
5060 y_coords.push(y);
5061 z_coords.push(z);
5062 firing_thresholds.push(threshold);
5063 threshold_limits.push(threshold_limit);
5064 leak_coeffs.push(leak);
5065 resting_potentials.push(resting);
5066 neuron_types.push(ntype);
5067 refractory_periods.push(refract);
5068 excitabilities.push(excit);
5069 consec_fire_limits.push(consec_limit);
5070 snooze_lengths.push(snooze);
5071 mp_accums.push(mp_accum);
5072 cortical_areas.push(cortical_idx);
5073 }
5074
5075 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5077
5078 let firing_thresholds_t = firing_thresholds;
5083 let threshold_limits_t = threshold_limits;
5084 let resting_potentials_t = resting_potentials;
5085 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5086 firing_thresholds_t,
5087 threshold_limits_t,
5088 leak_coeffs,
5089 resting_potentials_t,
5090 neuron_types,
5091 refractory_periods,
5092 excitabilities,
5093 consec_fire_limits,
5094 snooze_lengths,
5095 mp_accums,
5096 cortical_areas,
5097 x_coords,
5098 y_coords,
5099 z_coords,
5100 );
5101
5102 let mut neuron_ids = Vec::with_capacity(count);
5104 for i in 0..neurons_created {
5105 neuron_ids.push((first_neuron_id + i) as u64);
5106 }
5107
5108 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5109
5110 if let Some(state_manager) = StateManager::instance().try_read() {
5112 let core_state = state_manager.get_core_state();
5113 core_state.add_neuron_count(neurons_created);
5114 core_state.add_regular_neuron_count(neurons_created);
5115 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5116 }
5117
5118 {
5120 let mut cache = self.cached_neuron_counts_per_area.write();
5121 cache
5122 .entry(*cortical_id)
5123 .or_insert_with(|| AtomicUsize::new(0))
5124 .fetch_add(count, Ordering::Relaxed);
5125 }
5126
5127 Ok(neuron_ids)
5128 }
5129
5130 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5141 let npu = self
5143 .npu
5144 .as_ref()
5145 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5146
5147 let mut npu_lock = npu
5148 .lock()
5149 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5150
5151 let mut deleted_count = 0;
5152 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5153 std::collections::HashMap::new();
5154
5155 for neuron_id in neuron_ids {
5158 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5159 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
5160
5161 if npu_lock.delete_neuron(neuron_id as u32) {
5162 deleted_count += 1;
5163 if let Some(cortical_id) = cortical_id {
5164 let key = cortical_id.as_base_64();
5165 *per_area_deleted.entry(key).or_insert(0) += 1;
5166 }
5167 }
5168 }
5169
5170 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5171
5172 if deleted_count > 0 {
5174 if let Some(state_manager) = StateManager::instance().try_read() {
5175 let core_state = state_manager.get_core_state();
5176 core_state.subtract_neuron_count(deleted_count as u32);
5177 core_state.subtract_regular_neuron_count(deleted_count as u32);
5178 for (cortical_id, count) in per_area_deleted {
5179 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5180 }
5181 }
5182 }
5183
5184 Ok(deleted_count)
5191 }
5192
5193 pub fn update_neuron_properties(
5212 &mut self,
5213 neuron_id: u64,
5214 firing_threshold: Option<f32>,
5215 leak_coefficient: Option<f32>,
5216 resting_potential: Option<f32>,
5217 excitability: Option<f32>,
5218 ) -> BduResult<()> {
5219 let npu = self
5221 .npu
5222 .as_ref()
5223 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5224
5225 let mut npu_lock = npu
5226 .lock()
5227 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5228
5229 let neuron_id_u32 = neuron_id as u32;
5230
5231 let mut updated = false;
5233
5234 if let Some(threshold) = firing_threshold {
5236 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5237 updated = true;
5238 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5239 } else if !updated {
5240 return Err(BduError::InvalidNeuron(format!(
5241 "Neuron {} not found",
5242 neuron_id
5243 )));
5244 }
5245 }
5246
5247 if let Some(leak) = leak_coefficient {
5248 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5249 updated = true;
5250 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5251 } else if !updated {
5252 return Err(BduError::InvalidNeuron(format!(
5253 "Neuron {} not found",
5254 neuron_id
5255 )));
5256 }
5257 }
5258
5259 if let Some(resting) = resting_potential {
5260 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5261 updated = true;
5262 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5263 } else if !updated {
5264 return Err(BduError::InvalidNeuron(format!(
5265 "Neuron {} not found",
5266 neuron_id
5267 )));
5268 }
5269 }
5270
5271 if let Some(excit) = excitability {
5272 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
5273 updated = true;
5274 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
5275 } else if !updated {
5276 return Err(BduError::InvalidNeuron(format!(
5277 "Neuron {} not found",
5278 neuron_id
5279 )));
5280 }
5281 }
5282
5283 if !updated {
5284 return Err(BduError::Internal(
5285 "No properties provided for update".to_string(),
5286 ));
5287 }
5288
5289 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
5290
5291 Ok(())
5292 }
5293
5294 pub fn set_neuron_firing_threshold(
5306 &mut self,
5307 neuron_id: u64,
5308 new_threshold: f32,
5309 ) -> BduResult<()> {
5310 let npu = self
5312 .npu
5313 .as_ref()
5314 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5315
5316 let mut npu_lock = npu
5317 .lock()
5318 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5319
5320 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
5322 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
5323 Ok(())
5324 } else {
5325 Err(BduError::InvalidNeuron(format!(
5326 "Neuron {} not found",
5327 neuron_id
5328 )))
5329 }
5330 }
5331
5332 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
5347 self.cortical_areas
5348 .values()
5349 .find(|area| area.name == name)
5350 .cloned()
5351 }
5352
5353 pub fn resize_cortical_area(
5370 &mut self,
5371 cortical_id: &CorticalID,
5372 new_dimensions: CorticalAreaDimensions,
5373 ) -> BduResult<()> {
5374 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
5376 return Err(BduError::InvalidArea(format!(
5377 "Invalid dimensions: {:?} (all must be > 0)",
5378 new_dimensions
5379 )));
5380 }
5381
5382 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
5384 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5385 })?;
5386
5387 let old_dimensions = area.dimensions;
5388 area.dimensions = new_dimensions;
5389
5390 info!(target: "feagi-bdu",
5394 "Resized cortical area {} from {:?} to {:?}",
5395 cortical_id,
5396 old_dimensions,
5397 new_dimensions
5398 );
5399
5400 self.refresh_cortical_area_hashes(false, true);
5401
5402 Ok(())
5403 }
5404
5405 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
5416 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
5417 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5418 })?;
5419
5420 Ok(region
5422 .cortical_areas
5423 .iter()
5424 .map(|id| id.as_base_64())
5425 .collect())
5426 }
5427
5428 pub fn update_brain_region(
5441 &mut self,
5442 region_id: &str,
5443 new_name: Option<String>,
5444 new_description: Option<String>,
5445 ) -> BduResult<()> {
5446 let region = self
5447 .brain_regions
5448 .get_region_mut(region_id)
5449 .ok_or_else(|| {
5450 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5451 })?;
5452
5453 if let Some(name) = new_name {
5454 region.name = name;
5455 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
5456 }
5457
5458 if let Some(desc) = new_description {
5459 region
5461 .properties
5462 .insert("description".to_string(), serde_json::json!(desc));
5463 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
5464 }
5465
5466 info!(target: "feagi-bdu","Updated brain region {}", region_id);
5467
5468 self.refresh_brain_regions_hash();
5469
5470 Ok(())
5471 }
5472
5473 pub fn update_brain_region_properties(
5487 &mut self,
5488 region_id: &str,
5489 properties: std::collections::HashMap<String, serde_json::Value>,
5490 ) -> BduResult<()> {
5491 use tracing::{debug, info};
5492
5493 let region = self
5494 .brain_regions
5495 .get_region_mut(region_id)
5496 .ok_or_else(|| {
5497 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5498 })?;
5499
5500 for (key, value) in properties {
5501 match key.as_str() {
5502 "title" | "name" => {
5503 if let Some(name) = value.as_str() {
5504 region.name = name.to_string();
5505 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
5506 }
5507 }
5508 "coordinate_3d" | "coordinates_3d" => {
5509 region
5510 .properties
5511 .insert("coordinate_3d".to_string(), value.clone());
5512 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
5513 }
5514 "coordinate_2d" | "coordinates_2d" => {
5515 region
5516 .properties
5517 .insert("coordinate_2d".to_string(), value.clone());
5518 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
5519 }
5520 "description" => {
5521 region
5522 .properties
5523 .insert("description".to_string(), value.clone());
5524 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
5525 }
5526 "region_type" => {
5527 if let Some(type_str) = value.as_str() {
5528 region.region_type = feagi_structures::genomic::RegionType::Undefined;
5531 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
5532 }
5533 }
5534 _ => {
5536 region.properties.insert(key.clone(), value.clone());
5537 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
5538 }
5539 }
5540 }
5541
5542 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
5543
5544 Ok(())
5545 }
5546
5547 pub fn get_neuron_by_coordinates(
5565 &self,
5566 cortical_id: &CorticalID,
5567 x: u32,
5568 y: u32,
5569 z: u32,
5570 ) -> Option<u64> {
5571 let area = self.get_cortical_area(cortical_id)?;
5573 let cortical_idx = area.cortical_idx;
5574
5575 let npu = self.npu.as_ref()?;
5577 let npu_lock = npu.lock().ok()?;
5578
5579 npu_lock
5580 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
5581 .map(|id| id as u64)
5582 }
5583
5584 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
5595 let npu = self.npu.as_ref()?;
5596 let npu_lock = npu.lock().ok()?;
5597
5598 let neuron_count = npu_lock.get_neuron_count();
5600 if (neuron_id as usize) >= neuron_count {
5601 return None;
5602 }
5603
5604 Some(
5605 npu_lock
5606 .get_neuron_coordinates(neuron_id as u32)
5607 .unwrap_or((0, 0, 0)),
5608 )
5609 }
5610
5611 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
5622 let npu = self.npu.as_ref()?;
5623 let npu_lock = npu.lock().ok()?;
5624
5625 let neuron_count = npu_lock.get_neuron_count();
5627 if (neuron_id as usize) >= neuron_count {
5628 return None;
5629 }
5630
5631 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5632
5633 self.cortical_areas
5635 .values()
5636 .find(|area| area.cortical_idx == cortical_idx)
5637 .map(|area| area.cortical_id)
5638 }
5639
5640 pub fn get_neuron_properties(
5651 &self,
5652 neuron_id: u64,
5653 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5654 let npu = self.npu.as_ref()?;
5655 let npu_lock = npu.lock().ok()?;
5656
5657 let neuron_id_u32 = neuron_id as u32;
5658 let idx = neuron_id as usize;
5659
5660 let neuron_count = npu_lock.get_neuron_count();
5662 if idx >= neuron_count {
5663 return None;
5664 }
5665
5666 let mut properties = std::collections::HashMap::new();
5667
5668 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
5670
5671 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
5673 properties.insert("x".to_string(), serde_json::json!(x));
5674 properties.insert("y".to_string(), serde_json::json!(y));
5675 properties.insert("z".to_string(), serde_json::json!(z));
5676
5677 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32);
5679 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
5680
5681 if let Some((consec_count, consec_limit, snooze, mp, threshold, refract_countdown)) =
5683 npu_lock.get_neuron_state(NeuronId(neuron_id_u32))
5684 {
5685 properties.insert(
5686 "consecutive_fire_count".to_string(),
5687 serde_json::json!(consec_count),
5688 );
5689 properties.insert(
5690 "consecutive_fire_limit".to_string(),
5691 serde_json::json!(consec_limit),
5692 );
5693 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
5694 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
5695 properties.insert("threshold".to_string(), serde_json::json!(threshold));
5696 properties.insert(
5697 "refractory_countdown".to_string(),
5698 serde_json::json!(refract_countdown),
5699 );
5700 }
5701
5702 if let Some(leak) = npu_lock.get_neuron_property_by_index(idx, "leak_coefficient") {
5704 properties.insert("leak_coefficient".to_string(), serde_json::json!(leak));
5705 }
5706 if let Some(resting) = npu_lock.get_neuron_property_by_index(idx, "resting_potential") {
5707 properties.insert("resting_potential".to_string(), serde_json::json!(resting));
5708 }
5709 if let Some(excit) = npu_lock.get_neuron_property_by_index(idx, "excitability") {
5710 properties.insert("excitability".to_string(), serde_json::json!(excit));
5711 }
5712 if let Some(threshold_limit) = npu_lock.get_neuron_property_by_index(idx, "threshold_limit")
5713 {
5714 properties.insert(
5715 "threshold_limit".to_string(),
5716 serde_json::json!(threshold_limit),
5717 );
5718 }
5719
5720 if let Some(refract_period) =
5722 npu_lock.get_neuron_property_u16_by_index(idx, "refractory_period")
5723 {
5724 properties.insert(
5725 "refractory_period".to_string(),
5726 serde_json::json!(refract_period),
5727 );
5728 }
5729
5730 Some(properties)
5731 }
5732
5733 pub fn get_neuron_property(
5745 &self,
5746 neuron_id: u64,
5747 property_name: &str,
5748 ) -> Option<serde_json::Value> {
5749 self.get_neuron_properties(neuron_id)?
5750 .get(property_name)
5751 .cloned()
5752 }
5753
5754 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
5765 self.cortical_areas.keys().copied().collect()
5766 }
5767
5768 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
5775 self.cortical_areas
5776 .values()
5777 .map(|area| area.cortical_idx)
5778 .collect()
5779 }
5780
5781 pub fn get_cortical_area_names(&self) -> Vec<String> {
5788 self.cortical_areas
5789 .values()
5790 .map(|area| area.name.clone())
5791 .collect()
5792 }
5793
5794 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
5801 use crate::models::CorticalAreaExt;
5802 self.cortical_areas
5803 .values()
5804 .filter(|area| area.is_input_area())
5805 .map(|area| area.cortical_id)
5806 .collect()
5807 }
5808
5809 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
5816 use crate::models::CorticalAreaExt;
5817 self.cortical_areas
5818 .values()
5819 .filter(|area| area.is_output_area())
5820 .map(|area| area.cortical_id)
5821 .collect()
5822 }
5823
5824 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
5831 self.cortical_areas
5832 .values()
5833 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
5834 (
5835 max_w.max(area.dimensions.width as usize),
5836 max_h.max(area.dimensions.height as usize),
5837 max_d.max(area.dimensions.depth as usize),
5838 )
5839 })
5840 }
5841
5842 pub fn get_cortical_area_properties(
5853 &self,
5854 cortical_id: &CorticalID,
5855 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5856 let area = self.get_cortical_area(cortical_id)?;
5857
5858 let mut properties = std::collections::HashMap::new();
5859 properties.insert(
5860 "cortical_id".to_string(),
5861 serde_json::json!(area.cortical_id),
5862 );
5863 properties.insert(
5864 "cortical_id_s".to_string(),
5865 serde_json::json!(area.cortical_id.to_string()),
5866 );
5867 properties.insert(
5868 "cortical_idx".to_string(),
5869 serde_json::json!(area.cortical_idx),
5870 );
5871 properties.insert("name".to_string(), serde_json::json!(area.name));
5872 use crate::models::CorticalAreaExt;
5873 properties.insert(
5874 "area_type".to_string(),
5875 serde_json::json!(area.get_cortical_group()),
5876 );
5877 properties.insert(
5878 "dimensions".to_string(),
5879 serde_json::json!({
5880 "width": area.dimensions.width,
5881 "height": area.dimensions.height,
5882 "depth": area.dimensions.depth,
5883 }),
5884 );
5885 properties.insert("position".to_string(), serde_json::json!(area.position));
5886
5887 for (key, value) in &area.properties {
5889 properties.insert(key.clone(), value.clone());
5890 }
5891
5892 properties.extend(area.properties.clone());
5894
5895 Some(properties)
5896 }
5897
5898 pub fn get_all_cortical_area_properties(
5905 &self,
5906 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
5907 self.cortical_areas
5908 .keys()
5909 .filter_map(|id| self.get_cortical_area_properties(id))
5910 .collect()
5911 }
5912
5913 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
5924 self.brain_regions
5925 .get_all_region_ids()
5926 .into_iter()
5927 .cloned()
5928 .collect()
5929 }
5930
5931 pub fn get_brain_region_names(&self) -> Vec<String> {
5938 self.brain_regions
5939 .get_all_region_ids()
5940 .iter()
5941 .filter_map(|id| {
5942 self.brain_regions
5943 .get_region(id)
5944 .map(|region| region.name.clone())
5945 })
5946 .collect()
5947 }
5948
5949 pub fn get_brain_region_properties(
5960 &self,
5961 region_id: &str,
5962 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5963 let region = self.brain_regions.get_region(region_id)?;
5964
5965 let mut properties = std::collections::HashMap::new();
5966 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
5967 properties.insert("name".to_string(), serde_json::json!(region.name));
5968 properties.insert(
5969 "region_type".to_string(),
5970 serde_json::json!(format!("{:?}", region.region_type)),
5971 );
5972 properties.insert(
5973 "cortical_areas".to_string(),
5974 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
5975 );
5976
5977 properties.extend(region.properties.clone());
5979
5980 Some(properties)
5981 }
5982
5983 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
5994 self.cortical_areas.contains_key(cortical_id)
5995 }
5996
5997 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6008 self.brain_regions.get_region(region_id).is_some()
6009 }
6010
6011 pub fn get_brain_region_count(&self) -> usize {
6018 self.brain_regions.region_count()
6019 }
6020
6021 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6032 self.get_neurons_in_area(cortical_id)
6036 }
6037}
6038
6039impl std::fmt::Debug for ConnectomeManager {
6041 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6042 f.debug_struct("ConnectomeManager")
6043 .field("cortical_areas", &self.cortical_areas.len())
6044 .field("next_cortical_idx", &self.next_cortical_idx)
6045 .field("brain_regions", &self.brain_regions)
6046 .field(
6047 "npu",
6048 &if self.npu.is_some() {
6049 "Connected"
6050 } else {
6051 "Not connected"
6052 },
6053 )
6054 .field("initialized", &self.initialized)
6055 .finish()
6056 }
6057}
6058
6059#[cfg(test)]
6060mod tests {
6061 use super::*;
6062 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6063
6064 #[test]
6065 fn test_singleton_instance() {
6066 let instance1 = ConnectomeManager::instance();
6067 let instance2 = ConnectomeManager::instance();
6068
6069 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6071 }
6072
6073 #[test]
6074 fn test_add_cortical_area() {
6075 ConnectomeManager::reset_for_testing();
6076
6077 let instance = ConnectomeManager::instance();
6078 let mut manager = instance.write();
6079
6080 use feagi_structures::genomic::cortical_area::{
6081 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6082 };
6083 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6085 let area = CorticalArea::new(
6086 cortical_id,
6087 0,
6088 "Visual Input".to_string(),
6089 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6090 (0, 0, 0).into(),
6091 cortical_type,
6092 )
6093 .unwrap();
6094
6095 let initial_count = manager.get_cortical_area_count();
6096 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6097
6098 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6099 assert!(manager.has_cortical_area(&cortical_id));
6100 assert!(manager.is_initialized());
6101 }
6102
6103 #[test]
6104 fn test_cortical_area_lookups() {
6105 ConnectomeManager::reset_for_testing();
6106
6107 let instance = ConnectomeManager::instance();
6108 let mut manager = instance.write();
6109
6110 use feagi_structures::genomic::cortical_area::{
6111 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6112 };
6113 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6115 let area = CorticalArea::new(
6116 cortical_id,
6117 0,
6118 "Test Area".to_string(),
6119 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6120 (0, 0, 0).into(),
6121 cortical_type,
6122 )
6123 .unwrap();
6124
6125 let cortical_idx = manager.add_cortical_area(area).unwrap();
6126
6127 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6129
6130 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6132
6133 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6135 assert_eq!(retrieved_area.name, "Test Area");
6136 }
6137
6138 #[test]
6139 fn test_remove_cortical_area() {
6140 ConnectomeManager::reset_for_testing();
6141
6142 let instance = ConnectomeManager::instance();
6143 let mut manager = instance.write();
6144
6145 use feagi_structures::genomic::cortical_area::{
6146 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6147 };
6148 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6149
6150 if manager.has_cortical_area(&cortical_id) {
6152 manager.remove_cortical_area(&cortical_id).unwrap();
6153 }
6154
6155 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6156 let area = CorticalArea::new(
6157 cortical_id,
6158 0,
6159 "Test".to_string(),
6160 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6161 (0, 0, 0).into(),
6162 cortical_type,
6163 )
6164 .unwrap();
6165
6166 let initial_count = manager.get_cortical_area_count();
6167 manager.add_cortical_area(area).unwrap();
6168 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6169
6170 manager.remove_cortical_area(&cortical_id).unwrap();
6171 assert_eq!(manager.get_cortical_area_count(), initial_count);
6172 assert!(!manager.has_cortical_area(&cortical_id));
6173 }
6174
6175 #[test]
6176 fn test_duplicate_area_error() {
6177 ConnectomeManager::reset_for_testing();
6178
6179 let instance = ConnectomeManager::instance();
6180 let mut manager = instance.write();
6181
6182 use feagi_structures::genomic::cortical_area::{
6183 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6184 };
6185 let cortical_id1 = CoreCorticalType::Power.to_cortical_id();
6186 let cortical_type1 = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6187 let area1 = CorticalArea::new(
6188 cortical_id1,
6189 0,
6190 "First".to_string(),
6191 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6192 (0, 0, 0).into(),
6193 cortical_type1,
6194 )
6195 .unwrap();
6196
6197 let cortical_id2 = CoreCorticalType::Power.to_cortical_id();
6198 let cortical_type2 = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6199 let area2 = CorticalArea::new(
6200 cortical_id2, 1,
6202 "Second".to_string(),
6203 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6204 (0, 0, 0).into(),
6205 cortical_type2,
6206 )
6207 .unwrap();
6208
6209 manager.add_cortical_area(area1).unwrap();
6210 let result = manager.add_cortical_area(area2);
6211
6212 assert!(result.is_err());
6213 }
6214
6215 #[test]
6216 fn test_brain_region_management() {
6217 ConnectomeManager::reset_for_testing();
6218
6219 let instance = ConnectomeManager::instance();
6220 let mut manager = instance.write();
6221
6222 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
6223 let region_id_str = region_id.to_string();
6224 let root = BrainRegion::new(
6225 region_id,
6226 "Root".to_string(),
6227 feagi_structures::genomic::brain_regions::RegionType::Undefined,
6228 )
6229 .unwrap();
6230
6231 let initial_count = manager.get_brain_region_ids().len();
6232 manager.add_brain_region(root, None).unwrap();
6233
6234 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
6235 assert!(manager.get_brain_region(®ion_id_str).is_some());
6236 }
6237
6238 #[test]
6239 fn test_genome_loading() {
6240 ConnectomeManager::reset_for_testing();
6241
6242 let test01_id = CorticalID::try_from_bytes(b"cstgen01").unwrap();
6244 let test02_id = CorticalID::try_from_bytes(b"cstgen02").unwrap();
6245
6246 let genome_json = format!(
6247 r#"{{
6248 "genome_id": "test-001",
6249 "genome_title": "Test Genome",
6250 "version": "2.1",
6251 "blueprint": {{
6252 "{}": {{
6253 "cortical_name": "Test Area 1",
6254 "block_boundaries": [10, 10, 10],
6255 "relative_coordinate": [0, 0, 0],
6256 "cortical_type": "IPU",
6257 "firing_threshold": 50.0
6258 }},
6259 "{}": {{
6260 "cortical_name": "Test Area 2",
6261 "block_boundaries": [5, 5, 5],
6262 "relative_coordinate": [10, 0, 0],
6263 "cortical_type": "OPU"
6264 }}
6265 }},
6266 "brain_regions": {{
6267 "root": {{
6268 "title": "Root Region",
6269 "parent_region_id": null,
6270 "areas": ["{}", "{}"]
6271 }}
6272 }}
6273 }}"#,
6274 test01_id.as_base_64(),
6275 test02_id.as_base_64(),
6276 test01_id.as_base_64(),
6277 test02_id.as_base_64()
6278 );
6279
6280 let instance = ConnectomeManager::instance();
6281 let mut manager = instance.write();
6282
6283 manager.load_genome_from_json(&genome_json).unwrap();
6285
6286 assert!(
6291 manager.get_cortical_area_count() >= 2,
6292 "Expected at least the 2 blueprint cortical areas to be loaded"
6293 );
6294
6295 assert!(manager.has_cortical_area(&test01_id));
6296 assert!(manager.has_cortical_area(&test02_id));
6297
6298 let area1 = manager.get_cortical_area(&test01_id).unwrap();
6300 assert_eq!(area1.name, "Test Area 1");
6301 assert_eq!(area1.dimensions.width, 10);
6302 assert!(area1.properties.contains_key("firing_threshold"));
6304
6305 let area2 = manager.get_cortical_area(&test02_id).unwrap();
6306 assert_eq!(area2.name, "Test Area 2");
6307 assert_eq!(area2.dimensions.width, 5);
6308 let brain_region_ids = manager.get_brain_region_ids();
6312 assert_eq!(brain_region_ids.len(), 1);
6313 let root_region_id = &brain_region_ids[0];
6315 let root_region = manager.get_brain_region(root_region_id).unwrap();
6316 assert_eq!(root_region.name, "Root Region");
6317 assert_eq!(root_region.cortical_areas.len(), 2);
6318 assert!(root_region.contains_area(&test01_id));
6320 assert!(root_region.contains_area(&test02_id));
6321
6322 assert!(manager.is_initialized());
6324 }
6325
6326 #[test]
6327 fn test_synapse_operations() {
6328 use feagi_npu_burst_engine::npu::RustNPU;
6329 use feagi_npu_burst_engine::TracingMutex;
6330 use std::sync::Arc;
6331
6332 use feagi_npu_burst_engine::backend::CPUBackend;
6334 use feagi_npu_burst_engine::DynamicNPU;
6335 use feagi_npu_runtime::StdRuntime;
6336
6337 let runtime = StdRuntime;
6338 let backend = CPUBackend::new();
6339 let npu_result =
6340 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
6341 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
6342 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
6343
6344 use feagi_structures::genomic::cortical_area::{
6346 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6347 };
6348 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6350 let area = CorticalArea::new(
6351 cortical_id,
6352 0, "Test Area".to_string(),
6354 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
6355 (0, 0, 0).into(), cortical_type,
6357 )
6358 .unwrap();
6359 let cortical_idx = manager.add_cortical_area(area).unwrap();
6360
6361 if let Some(npu_arc) = manager.get_npu() {
6363 if let Ok(mut npu_guard) = npu_arc.try_lock() {
6364 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
6365 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
6366 }
6367 }
6368 }
6369
6370 let neuron1_id = manager
6372 .add_neuron(
6373 &cortical_id,
6374 0,
6375 0,
6376 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
6388 .unwrap();
6389
6390 let neuron2_id = manager
6391 .add_neuron(
6392 &cortical_id,
6393 1,
6394 0,
6395 0, 100.0,
6397 f32::MAX, 0.1,
6399 -60.0,
6400 0,
6401 2,
6402 1.0,
6403 5,
6404 10,
6405 false,
6406 )
6407 .unwrap();
6408
6409 manager
6411 .create_synapse(
6412 neuron1_id, neuron2_id, 128, 64, 0, )
6416 .unwrap();
6417
6418 println!("✅ Synapse creation test passed");
6421 }
6422
6423 #[test]
6424 fn test_apply_cortical_mapping_missing_rules_is_ok() {
6425 let mut manager = ConnectomeManager::new_for_testing();
6428
6429 use feagi_structures::genomic::cortical_area::{
6430 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6431 };
6432
6433 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
6434 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
6435
6436 let src_area = CorticalArea::new(
6437 src_id,
6438 0,
6439 "src".to_string(),
6440 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6441 (0, 0, 0).into(),
6442 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6443 )
6444 .unwrap();
6445
6446 let dst_area = CorticalArea::new(
6447 dst_id,
6448 1,
6449 "dst".to_string(),
6450 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6451 (0, 0, 0).into(),
6452 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6453 )
6454 .unwrap();
6455
6456 manager.add_cortical_area(src_area).unwrap();
6457 manager.add_cortical_area(dst_area).unwrap();
6458
6459 let count = manager
6461 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6462 .unwrap();
6463 assert_eq!(count, 0);
6464
6465 manager
6467 .update_cortical_mapping(
6468 &src_id,
6469 &dst_id,
6470 vec![serde_json::json!({"morphology_id":"m1"})],
6471 )
6472 .unwrap();
6473 manager
6474 .update_cortical_mapping(&src_id, &dst_id, vec![])
6475 .unwrap();
6476
6477 let count2 = manager
6478 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6479 .unwrap();
6480 assert_eq!(count2, 0);
6481 }
6482
6483 #[test]
6484 fn test_mapping_deletion_prunes_synapses_between_areas() {
6485 use feagi_npu_burst_engine::backend::CPUBackend;
6486 use feagi_npu_burst_engine::RustNPU;
6487 use feagi_npu_burst_engine::TracingMutex;
6488 use feagi_npu_runtime::StdRuntime;
6489 use feagi_structures::genomic::cortical_area::{
6490 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6491 };
6492 use std::sync::Arc;
6493
6494 let runtime = StdRuntime;
6496 let backend = CPUBackend::new();
6497 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6498 let dyn_npu = Arc::new(TracingMutex::new(
6499 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6500 "TestNPU",
6501 ));
6502 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6503
6504 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
6506 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
6507
6508 let src_area = CorticalArea::new(
6509 src_id,
6510 0,
6511 "src".to_string(),
6512 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6513 (0, 0, 0).into(),
6514 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6515 )
6516 .unwrap();
6517 let dst_area = CorticalArea::new(
6518 dst_id,
6519 1,
6520 "dst".to_string(),
6521 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6522 (0, 0, 0).into(),
6523 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6524 )
6525 .unwrap();
6526
6527 manager.add_cortical_area(src_area).unwrap();
6528 manager.add_cortical_area(dst_area).unwrap();
6529
6530 let s0 = manager
6532 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6533 .unwrap();
6534 let s1 = manager
6535 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6536 .unwrap();
6537 let t0 = manager
6538 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6539 .unwrap();
6540 let t1 = manager
6541 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6542 .unwrap();
6543
6544 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6546 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6547
6548 {
6550 let mut npu = dyn_npu.lock().unwrap();
6551 npu.rebuild_synapse_index();
6552 assert_eq!(npu.get_synapse_count(), 2);
6553 }
6554
6555 manager
6557 .update_cortical_mapping(&src_id, &dst_id, vec![])
6558 .unwrap();
6559 let created = manager
6560 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6561 .unwrap();
6562 assert_eq!(created, 0);
6563
6564 {
6566 let mut npu = dyn_npu.lock().unwrap();
6567 npu.rebuild_synapse_index();
6569 assert_eq!(npu.get_synapse_count(), 0);
6570 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6571 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6572 }
6573 }
6574
6575 #[test]
6576 fn test_mapping_update_prunes_synapses_between_areas() {
6577 use feagi_npu_burst_engine::backend::CPUBackend;
6578 use feagi_npu_burst_engine::RustNPU;
6579 use feagi_npu_burst_engine::TracingMutex;
6580 use feagi_npu_runtime::StdRuntime;
6581 use feagi_structures::genomic::cortical_area::{
6582 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6583 };
6584 use std::sync::Arc;
6585
6586 let runtime = StdRuntime;
6588 let backend = CPUBackend::new();
6589 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6590 let dyn_npu = Arc::new(TracingMutex::new(
6591 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6592 "TestNPU",
6593 ));
6594 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6595
6596 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6598
6599 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
6602 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
6603
6604 let src_area = CorticalArea::new(
6605 src_id,
6606 0,
6607 "src".to_string(),
6608 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6609 (0, 0, 0).into(),
6610 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6611 )
6612 .unwrap();
6613 let dst_area = CorticalArea::new(
6614 dst_id,
6615 0,
6616 "dst".to_string(),
6617 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6618 (0, 0, 0).into(),
6619 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6620 )
6621 .unwrap();
6622
6623 manager.add_cortical_area(src_area).unwrap();
6624 manager.add_cortical_area(dst_area).unwrap();
6625
6626 let s0 = manager
6628 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6629 .unwrap();
6630 let s1 = manager
6631 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6632 .unwrap();
6633 let t0 = manager
6634 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6635 .unwrap();
6636 let t1 = manager
6637 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6638 .unwrap();
6639
6640 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6642 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6643
6644 {
6646 let mut npu = dyn_npu.lock().unwrap();
6647 npu.rebuild_synapse_index();
6648 assert_eq!(npu.get_synapse_count(), 2);
6649 }
6650
6651 manager
6657 .update_cortical_mapping(
6658 &src_id,
6659 &dst_id,
6660 vec![serde_json::json!({
6661 "morphology_id": "episodic_memory",
6662 "morphology_scalar": [1],
6663 "postSynapticCurrent_multiplier": 1,
6664 "plasticity_flag": false,
6665 "plasticity_constant": 0,
6666 "ltp_multiplier": 0,
6667 "ltd_multiplier": 0,
6668 "plasticity_window": 0,
6669 })],
6670 )
6671 .unwrap();
6672 let created = manager
6673 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6674 .unwrap();
6675 assert_eq!(created, 0);
6676
6677 {
6679 let mut npu = dyn_npu.lock().unwrap();
6680 npu.rebuild_synapse_index();
6682 assert_eq!(npu.get_synapse_count(), 0);
6683 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6684 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6685 }
6686 }
6687
6688 #[test]
6689 fn test_upstream_area_tracking() {
6690 use crate::models::cortical_area::CorticalArea;
6692 use feagi_npu_burst_engine::backend::CPUBackend;
6693 use feagi_npu_burst_engine::TracingMutex;
6694 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6695 use feagi_npu_runtime::StdRuntime;
6696 use feagi_structures::genomic::cortical_area::{
6697 CorticalAreaDimensions, CorticalAreaType, CorticalID,
6698 };
6699
6700 let runtime = StdRuntime;
6702 let backend = CPUBackend::new();
6703 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6704 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6705 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6706
6707 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6710
6711 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
6713 let src_area = CorticalArea::new(
6714 src_id,
6715 0,
6716 "Source Area".to_string(),
6717 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6718 (0, 0, 0).into(),
6719 CorticalAreaType::Custom(
6720 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6721 ),
6722 )
6723 .unwrap();
6724 let src_idx = manager.add_cortical_area(src_area).unwrap();
6725
6726 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
6728 let dst_area = CorticalArea::new(
6729 dst_id,
6730 0,
6731 "Dest Area".to_string(),
6732 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6733 (0, 0, 0).into(),
6734 CorticalAreaType::Custom(
6735 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6736 ),
6737 )
6738 .unwrap();
6739 manager.add_cortical_area(dst_area).unwrap();
6740
6741 {
6743 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
6744 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
6745 assert!(
6746 upstream.as_array().unwrap().is_empty(),
6747 "Upstream areas should be empty initially"
6748 );
6749 }
6750
6751 let mapping_data = vec![serde_json::json!({
6753 "morphology_id": "episodic_memory",
6754 "morphology_scalar": 1,
6755 "postSynapticCurrent_multiplier": 1.0,
6756 })];
6757 manager
6758 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
6759 .unwrap();
6760 manager
6761 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6762 .unwrap();
6763
6764 {
6766 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6767 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
6768 assert_eq!(
6769 upstream_areas[0], src_idx,
6770 "Upstream area should be src_idx"
6771 );
6772 }
6773
6774 manager
6776 .update_cortical_mapping(&src_id, &dst_id, vec![])
6777 .unwrap();
6778 manager
6779 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6780 .unwrap();
6781
6782 {
6784 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6785 assert_eq!(
6786 upstream_areas.len(),
6787 0,
6788 "Should have 0 upstream areas after deletion"
6789 );
6790 }
6791 }
6792
6793 #[test]
6794 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
6795 use crate::models::cortical_area::CorticalArea;
6796 use feagi_npu_burst_engine::backend::CPUBackend;
6797 use feagi_npu_burst_engine::TracingMutex;
6798 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6799 use feagi_npu_runtime::StdRuntime;
6800 use feagi_structures::genomic::cortical_area::{
6801 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
6802 };
6803 use std::sync::Arc;
6804
6805 let runtime = StdRuntime;
6806 let backend = CPUBackend::new();
6807 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6808 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6809 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6810 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6811
6812 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6813 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
6814 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
6815 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
6816
6817 let a1_area = CorticalArea::new(
6818 a1_id,
6819 0,
6820 "A1".to_string(),
6821 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6822 (0, 0, 0).into(),
6823 CorticalAreaType::Custom(
6824 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6825 ),
6826 )
6827 .unwrap();
6828 let a2_area = CorticalArea::new(
6829 a2_id,
6830 0,
6831 "A2".to_string(),
6832 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6833 (0, 0, 0).into(),
6834 CorticalAreaType::Custom(
6835 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6836 ),
6837 )
6838 .unwrap();
6839
6840 let mut m1_area = CorticalArea::new(
6841 m1_id,
6842 0,
6843 "M1".to_string(),
6844 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6845 (0, 0, 0).into(),
6846 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6847 )
6848 .unwrap();
6849 m1_area
6850 .properties
6851 .insert("is_mem_type".to_string(), serde_json::json!(true));
6852 m1_area
6853 .properties
6854 .insert("temporal_depth".to_string(), serde_json::json!(1));
6855
6856 let mut m2_area = CorticalArea::new(
6857 m2_id,
6858 0,
6859 "M2".to_string(),
6860 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6861 (0, 0, 0).into(),
6862 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6863 )
6864 .unwrap();
6865 m2_area
6866 .properties
6867 .insert("is_mem_type".to_string(), serde_json::json!(true));
6868 m2_area
6869 .properties
6870 .insert("temporal_depth".to_string(), serde_json::json!(1));
6871
6872 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
6873 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
6874 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
6875 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
6876
6877 manager
6878 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6879 .unwrap();
6880 manager
6881 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6882 .unwrap();
6883
6884 let episodic_mapping = vec![serde_json::json!({
6885 "morphology_id": "episodic_memory",
6886 "morphology_scalar": 1,
6887 "postSynapticCurrent_multiplier": 1.0,
6888 })];
6889 manager
6890 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
6891 .unwrap();
6892 manager
6893 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
6894 .unwrap();
6895 manager
6896 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
6897 .unwrap();
6898 manager
6899 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
6900 .unwrap();
6901
6902 let assoc_mapping = vec![serde_json::json!({
6903 "morphology_id": "associative_memory",
6904 "morphology_scalar": 1,
6905 "postSynapticCurrent_multiplier": 1.0,
6906 "plasticity_flag": true,
6907 "plasticity_constant": 1,
6908 "ltp_multiplier": 1,
6909 "ltd_multiplier": 1,
6910 "plasticity_window": 5,
6911 })];
6912 manager
6913 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping)
6914 .unwrap();
6915 manager
6916 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
6917 .unwrap();
6918
6919 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6920 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6921 assert_eq!(
6922 upstream_m1.len(),
6923 1,
6924 "M1 should have only 1 upstream before refresh"
6925 );
6926 assert_eq!(
6927 upstream_m2.len(),
6928 2,
6929 "M2 should have 2 upstreams before refresh"
6930 );
6931
6932 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
6933 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
6934
6935 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6936 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6937 assert_eq!(
6938 upstream_m1.len(),
6939 2,
6940 "M1 should have 2 upstreams after refresh"
6941 );
6942 assert_eq!(
6943 upstream_m2.len(),
6944 2,
6945 "M2 should have 2 upstreams after refresh"
6946 );
6947 assert!(upstream_m1.contains(&a1_idx));
6948 assert!(upstream_m1.contains(&m2_idx));
6949 assert!(upstream_m2.contains(&a2_idx));
6950 assert!(upstream_m2.contains(&m1_idx));
6951
6952 {
6954 let mut npu_lock = dyn_npu.lock().unwrap();
6955 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
6956 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
6957 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
6958 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
6959 npu_lock.process_burst().expect("Burst processing failed");
6960 }
6961
6962 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6963 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6964 assert_eq!(
6965 upstream_m1.len(),
6966 2,
6967 "M1 should keep 2 upstreams after firing"
6968 );
6969 assert_eq!(
6970 upstream_m2.len(),
6971 2,
6972 "M2 should keep 2 upstreams after firing"
6973 );
6974 }
6975
6976 #[test]
6977 fn test_memory_twin_created_for_memory_mapping() {
6978 use crate::models::cortical_area::CorticalArea;
6979 use feagi_npu_burst_engine::backend::CPUBackend;
6980 use feagi_npu_burst_engine::TracingMutex;
6981 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6982 use feagi_npu_runtime::StdRuntime;
6983 use feagi_structures::genomic::cortical_area::{
6984 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
6985 MemoryCorticalType,
6986 };
6987 use std::sync::Arc;
6988
6989 let runtime = StdRuntime;
6990 let backend = CPUBackend::new();
6991 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6992 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6993 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6994 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6995
6996 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
6997 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
6998
6999 let src_area = CorticalArea::new(
7000 src_id,
7001 0,
7002 "Source Area".to_string(),
7003 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7004 (0, 0, 0).into(),
7005 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7006 )
7007 .unwrap();
7008 let mut dst_area = CorticalArea::new(
7009 dst_id,
7010 0,
7011 "Memory Area".to_string(),
7012 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7013 (0, 0, 0).into(),
7014 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7015 )
7016 .unwrap();
7017 dst_area
7018 .properties
7019 .insert("is_mem_type".to_string(), serde_json::json!(true));
7020 dst_area
7021 .properties
7022 .insert("temporal_depth".to_string(), serde_json::json!(1));
7023
7024 manager.add_cortical_area(src_area).unwrap();
7025 manager.add_cortical_area(dst_area).unwrap();
7026
7027 let mapping_data = vec![serde_json::json!({
7028 "morphology_id": "episodic_memory",
7029 "morphology_scalar": 1,
7030 "postSynapticCurrent_multiplier": 1.0,
7031 })];
7032 manager
7033 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7034 .unwrap();
7035 manager
7036 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7037 .unwrap();
7038
7039 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7040 let twin_map = memory_area
7041 .properties
7042 .get("memory_twin_areas")
7043 .and_then(|v| v.as_object())
7044 .expect("memory_twin_areas should be set");
7045 let twin_id_str = twin_map
7046 .get(&src_id.as_base_64())
7047 .and_then(|v| v.as_str())
7048 .expect("Missing twin entry for upstream area");
7049 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7050 let mapping = memory_area
7051 .properties
7052 .get("cortical_mapping_dst")
7053 .and_then(|v| v.as_object())
7054 .and_then(|map| map.get(&twin_id.as_base_64()))
7055 .and_then(|v| v.as_array())
7056 .expect("Missing memory replay mapping for twin area");
7057 let uses_replay = mapping.iter().any(|rule| {
7058 rule.get("morphology_id")
7059 .and_then(|v| v.as_str())
7060 .is_some_and(|id| id == "memory_replay")
7061 });
7062 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7063
7064 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7065 assert!(matches!(
7066 twin_area.cortical_type,
7067 CorticalAreaType::Custom(_)
7068 ));
7069 assert_eq!(
7070 twin_area
7071 .properties
7072 .get("memory_twin_of")
7073 .and_then(|v| v.as_str()),
7074 Some(src_id.as_base_64().as_str())
7075 );
7076 assert_eq!(
7077 twin_area
7078 .properties
7079 .get("memory_twin_for")
7080 .and_then(|v| v.as_str()),
7081 Some(dst_id.as_base_64().as_str())
7082 );
7083 }
7084
7085 #[test]
7086 fn test_associative_memory_between_memory_areas_creates_synapses() {
7087 use crate::models::cortical_area::CorticalArea;
7088 use feagi_npu_burst_engine::backend::CPUBackend;
7089 use feagi_npu_burst_engine::TracingMutex;
7090 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7091 use feagi_npu_runtime::StdRuntime;
7092 use feagi_structures::genomic::cortical_area::{
7093 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7094 };
7095 use std::sync::Arc;
7096
7097 let runtime = StdRuntime;
7098 let backend = CPUBackend::new();
7099 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7100 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7101 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7102 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7103
7104 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
7105 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
7106
7107 let mut m1_area = CorticalArea::new(
7108 m1_id,
7109 0,
7110 "Memory M1".to_string(),
7111 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7112 (0, 0, 0).into(),
7113 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7114 )
7115 .unwrap();
7116 m1_area
7117 .properties
7118 .insert("is_mem_type".to_string(), serde_json::json!(true));
7119 m1_area
7120 .properties
7121 .insert("temporal_depth".to_string(), serde_json::json!(1));
7122
7123 let mut m2_area = CorticalArea::new(
7124 m2_id,
7125 0,
7126 "Memory M2".to_string(),
7127 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7128 (0, 0, 0).into(),
7129 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7130 )
7131 .unwrap();
7132 m2_area
7133 .properties
7134 .insert("is_mem_type".to_string(), serde_json::json!(true));
7135 m2_area
7136 .properties
7137 .insert("temporal_depth".to_string(), serde_json::json!(1));
7138
7139 manager.add_cortical_area(m1_area).unwrap();
7140 manager.add_cortical_area(m2_area).unwrap();
7141
7142 manager
7143 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7144 .unwrap();
7145 manager
7146 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7147 .unwrap();
7148
7149 let mapping_data = vec![serde_json::json!({
7150 "morphology_id": "associative_memory",
7151 "morphology_scalar": 1,
7152 "postSynapticCurrent_multiplier": 1.0,
7153 "plasticity_flag": true,
7154 "plasticity_constant": 1,
7155 "ltp_multiplier": 1,
7156 "ltd_multiplier": 1,
7157 "plasticity_window": 5,
7158 })];
7159 manager
7160 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
7161 .unwrap();
7162 let created = manager
7163 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7164 .unwrap();
7165 assert!(
7166 created > 0,
7167 "Expected associative memory mapping between memory areas to create synapses"
7168 );
7169 }
7170
7171 #[test]
7172 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7173 use crate::models::cortical_area::CorticalArea;
7174 use feagi_npu_burst_engine::backend::CPUBackend;
7175 use feagi_npu_burst_engine::TracingMutex;
7176 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7177 use feagi_npu_runtime::StdRuntime;
7178 use feagi_structures::genomic::cortical_area::{
7179 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7180 MemoryCorticalType,
7181 };
7182 use std::sync::Arc;
7183
7184 let runtime = StdRuntime;
7185 let backend = CPUBackend::new();
7186 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7187 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7188 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7189 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7190
7191 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7192 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7193
7194 let src_area = CorticalArea::new(
7195 src_id,
7196 0,
7197 "Source Area".to_string(),
7198 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7199 (0, 0, 0).into(),
7200 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7201 )
7202 .unwrap();
7203 let mut mem_area = CorticalArea::new(
7204 mem_id,
7205 0,
7206 "Memory Area".to_string(),
7207 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7208 (0, 0, 0).into(),
7209 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7210 )
7211 .unwrap();
7212 mem_area
7213 .properties
7214 .insert("is_mem_type".to_string(), serde_json::json!(true));
7215 mem_area
7216 .properties
7217 .insert("temporal_depth".to_string(), serde_json::json!(1));
7218
7219 manager.add_cortical_area(src_area).unwrap();
7220 manager.add_cortical_area(mem_area).unwrap();
7221
7222 let twin_id = manager
7223 .build_memory_twin_id(&mem_id, &src_id)
7224 .expect("Failed to build twin id");
7225 let twin_area = CorticalArea::new(
7226 twin_id,
7227 0,
7228 "Source Area_twin".to_string(),
7229 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7230 (0, 0, 0).into(),
7231 CorticalAreaType::Custom(
7232 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7233 ),
7234 )
7235 .unwrap();
7236 manager.add_cortical_area(twin_area).unwrap();
7237
7238 let repaired = manager
7239 .ensure_memory_twin_area(&mem_id, &src_id)
7240 .expect("Failed to repair twin");
7241 assert_eq!(repaired, twin_id);
7242
7243 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
7244 let twin_map = mem_area
7245 .properties
7246 .get("memory_twin_areas")
7247 .and_then(|v| v.as_object())
7248 .expect("memory_twin_areas should be set");
7249 let twin_id_str = twin_map
7250 .get(&src_id.as_base_64())
7251 .and_then(|v| v.as_str())
7252 .expect("Missing twin entry for upstream area");
7253 assert_eq!(twin_id_str, twin_id.as_base_64());
7254
7255 let replay_map = mem_area
7256 .properties
7257 .get("cortical_mapping_dst")
7258 .and_then(|v| v.as_object())
7259 .and_then(|map| map.get(&twin_id.as_base_64()))
7260 .and_then(|v| v.as_array())
7261 .expect("Missing memory replay mapping for twin area");
7262 let uses_replay = replay_map.iter().any(|rule| {
7263 rule.get("morphology_id")
7264 .and_then(|v| v.as_str())
7265 .is_some_and(|id| id == "memory_replay")
7266 });
7267 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7268
7269 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7270 assert_eq!(
7271 twin_area
7272 .properties
7273 .get("memory_twin_of")
7274 .and_then(|v| v.as_str()),
7275 Some(src_id.as_base_64().as_str())
7276 );
7277 assert_eq!(
7278 twin_area
7279 .properties
7280 .get("memory_twin_for")
7281 .and_then(|v| v.as_str()),
7282 Some(mem_id.as_base_64().as_str())
7283 );
7284 }
7285}