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 Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1008 warn!(
1009 target: "feagi-bdu",
1010 "Skipping region IO for source area {} (not in any region)",
1011 src_id.as_base_64()
1012 );
1013 continue;
1014 };
1015
1016 for dst_id_str in dstmap.keys() {
1017 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1018 BduError::InvalidArea(format!(
1019 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1020 dst_id_str,
1021 src_id.as_base_64(),
1022 e
1023 ))
1024 })?;
1025
1026 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1027 else {
1028 warn!(
1029 target: "feagi-bdu",
1030 "Skipping region IO for destination area {} (not in any region)",
1031 dst_id.as_base_64()
1032 );
1033 continue;
1034 };
1035
1036 if src_region_id == dst_region_id {
1037 continue;
1038 }
1039
1040 outputs_by_region
1041 .entry(src_region_id.clone())
1042 .or_default()
1043 .insert(src_id.as_base_64());
1044 inputs_by_region
1045 .entry(dst_region_id.clone())
1046 .or_default()
1047 .insert(dst_id.as_base_64());
1048 }
1049 }
1050
1051 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1052 for rid in region_ids {
1053 let mut inputs: Vec<String> = inputs_by_region
1054 .remove(&rid)
1055 .unwrap_or_default()
1056 .into_iter()
1057 .collect();
1058 let mut outputs: Vec<String> = outputs_by_region
1059 .remove(&rid)
1060 .unwrap_or_default()
1061 .into_iter()
1062 .collect();
1063
1064 inputs.sort();
1065 outputs.sort();
1066
1067 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1068 BduError::InvalidArea(format!(
1069 "Unable to recompute region IO: region '{}' not found in hierarchy",
1070 rid
1071 ))
1072 })?;
1073
1074 if inputs.is_empty() {
1075 region.properties.remove("inputs");
1076 } else {
1077 region
1078 .properties
1079 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1080 }
1081
1082 if outputs.is_empty() {
1083 region.properties.remove("outputs");
1084 } else {
1085 region
1086 .properties
1087 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1088 }
1089
1090 computed.insert(rid, (inputs, outputs));
1091 }
1092
1093 self.refresh_brain_regions_hash();
1094
1095 Ok(computed)
1096 }
1097
1098 pub fn get_root_region_id(&self) -> Option<String> {
1104 self.brain_regions.get_root_region_id()
1105 }
1106
1107 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1109 self.cortical_idx_to_id.get(&cortical_idx)
1110 }
1111
1112 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1115 self.cortical_idx_to_id
1116 .iter()
1117 .map(|(idx, id)| (*idx, id.as_base_64()))
1118 .collect()
1119 }
1120
1121 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1126 let mut granularities = ahash::AHashMap::new();
1127 for (cortical_id, area) in &self.cortical_areas {
1128 let cortical_idx = self
1129 .cortical_id_to_idx
1130 .get(cortical_id)
1131 .copied()
1132 .unwrap_or(0);
1133
1134 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1137 if let Some(arr) = granularity_json.as_array() {
1138 if arr.len() == 3 {
1139 let x_opt = arr[0]
1140 .as_u64()
1141 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1142 let y_opt = arr[1]
1143 .as_u64()
1144 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1145 let z_opt = arr[2]
1146 .as_u64()
1147 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1148
1149 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1150 let granularity = (x as u32, y as u32, z as u32);
1151 if granularity != (1, 1, 1) {
1153 granularities.insert(cortical_idx, granularity);
1154 }
1155 }
1156 }
1157 }
1158 }
1159 }
1160 granularities
1161 }
1162
1163 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1165 self.cortical_areas.keys().collect()
1166 }
1167
1168 pub fn get_cortical_area_count(&self) -> usize {
1170 self.cortical_areas.len()
1171 }
1172
1173 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1187 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1188 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1189 if let Some(upstream_array) = upstream_prop.as_array() {
1190 return upstream_array
1191 .iter()
1192 .filter_map(|v| v.as_u64().map(|n| n as u32))
1193 .collect();
1194 }
1195 }
1196
1197 warn!(target: "feagi-bdu",
1199 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1200 target_cortical_id.as_base_64()
1201 );
1202 }
1203
1204 Vec::new()
1205 }
1206
1207 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1209 upstream
1210 .iter()
1211 .filter_map(|idx| {
1212 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1213 let area = self.cortical_areas.get(cortical_id)?;
1214 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1215 None
1216 } else {
1217 Some(*idx)
1218 }
1219 })
1220 .collect()
1221 }
1222
1223 pub fn refresh_upstream_cortical_areas_from_mappings(
1228 &mut self,
1229 target_cortical_id: &CorticalID,
1230 ) -> Vec<u32> {
1231 use std::collections::HashSet;
1232 let target_id_str = target_cortical_id.as_base_64();
1233 let mut upstream_idxs = HashSet::new();
1234 for (src_id, src_area) in &self.cortical_areas {
1235 if src_id == target_cortical_id {
1236 continue;
1237 }
1238 if let Some(mapping) = src_area
1239 .properties
1240 .get("cortical_mapping_dst")
1241 .and_then(|v| v.as_object())
1242 {
1243 if mapping.contains_key(&target_id_str) {
1244 upstream_idxs.insert(src_area.cortical_idx);
1245 }
1246 }
1247 }
1248
1249 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1250 upstream_list.sort_unstable();
1251
1252 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1253 target_area.properties.insert(
1254 "upstream_cortical_areas".to_string(),
1255 serde_json::json!(upstream_list),
1256 );
1257 }
1258
1259 self.get_upstream_cortical_areas(target_cortical_id)
1260 }
1261
1262 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1272 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1273 let upstream_array = area
1274 .properties
1275 .entry("upstream_cortical_areas".to_string())
1276 .or_insert_with(|| serde_json::json!([]));
1277
1278 if let Some(arr) = upstream_array.as_array_mut() {
1279 let src_value = serde_json::json!(src_cortical_idx);
1280 if !arr.contains(&src_value) {
1281 arr.push(src_value);
1282 info!(target: "feagi-bdu",
1283 "✓ Added upstream area idx={} to cortical area '{}'",
1284 src_cortical_idx, target_cortical_id.as_base_64()
1285 );
1286 }
1287 }
1288 }
1289 }
1290
1291 pub fn get_memory_twin_for_upstream_idx(
1293 &self,
1294 memory_area_idx: u32,
1295 upstream_idx: u32,
1296 ) -> Option<CorticalID> {
1297 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1298 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1299 let area = self.cortical_areas.get(memory_id)?;
1300 let mapping = area
1301 .properties
1302 .get("memory_twin_areas")
1303 .and_then(|v| v.as_object())?;
1304 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1305 CorticalID::try_from_base_64(twin_b64).ok()
1306 }
1307
1308 pub fn ensure_memory_twin_area(
1310 &mut self,
1311 memory_area_id: &CorticalID,
1312 upstream_area_id: &CorticalID,
1313 ) -> BduResult<CorticalID> {
1314 use crate::models::CorticalAreaExt;
1315
1316 let register_replay_mapping = |manager: &mut ConnectomeManager,
1317 twin_id: &CorticalID|
1318 -> BduResult<()> {
1319 let Some(npu) = manager.npu.as_ref() else {
1320 return Ok(());
1321 };
1322 let memory_area_idx =
1323 *manager
1324 .cortical_id_to_idx
1325 .get(memory_area_id)
1326 .ok_or_else(|| {
1327 BduError::InvalidArea(format!(
1328 "Memory area idx missing for {}",
1329 memory_area_id.as_base_64()
1330 ))
1331 })?;
1332 let upstream_area_idx = *manager
1333 .cortical_id_to_idx
1334 .get(upstream_area_id)
1335 .ok_or_else(|| {
1336 BduError::InvalidArea(format!(
1337 "Upstream area idx missing for {}",
1338 upstream_area_id.as_base_64()
1339 ))
1340 })?;
1341 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1342 BduError::InvalidArea(format!(
1343 "Twin area idx missing for {}",
1344 twin_id.as_base_64()
1345 ))
1346 })?;
1347 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1348 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1349 })?;
1350 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1351 if let Ok(mut npu_lock) = npu.lock() {
1352 npu_lock.register_memory_twin_mapping(
1353 memory_area_idx,
1354 upstream_area_idx,
1355 twin_area_idx,
1356 potential,
1357 );
1358 }
1359 Ok(())
1360 };
1361
1362 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1363 BduError::InvalidArea(format!(
1364 "Memory area {} not found",
1365 memory_area_id.as_base_64()
1366 ))
1367 })?;
1368 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1369 BduError::InvalidArea(format!(
1370 "Upstream area {} not found",
1371 upstream_area_id.as_base_64()
1372 ))
1373 })?;
1374
1375 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1376 return Err(BduError::InvalidArea(format!(
1377 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1378 upstream_area_id.as_base_64()
1379 )));
1380 }
1381
1382 if let Some(existing) = memory_area
1383 .properties
1384 .get("memory_twin_areas")
1385 .and_then(|v| v.as_object())
1386 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1387 .and_then(|v| v.as_str())
1388 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1389 {
1390 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1391 register_replay_mapping(self, &existing)?;
1392 self.refresh_cortical_mappings_hash();
1393 return Ok(existing);
1394 }
1395
1396 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1397 if self.cortical_areas.contains_key(&twin_id) {
1398 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1399 let expected_source = upstream_area_id.as_base_64();
1400 let expected_target = memory_area_id.as_base_64();
1401 let existing_source = existing
1402 .properties
1403 .get("memory_twin_of")
1404 .and_then(|v| v.as_str());
1405 let existing_target = existing
1406 .properties
1407 .get("memory_twin_for")
1408 .and_then(|v| v.as_str());
1409 if existing_source != Some(expected_source.as_str())
1410 || existing_target != Some(expected_target.as_str())
1411 {
1412 warn!(
1413 target: "feagi-bdu",
1414 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1415 upstream_area_id.as_base_64(),
1416 memory_area_id.as_base_64()
1417 );
1418 existing.properties.insert(
1419 "memory_twin_of".to_string(),
1420 serde_json::json!(expected_source),
1421 );
1422 existing.properties.insert(
1423 "memory_twin_for".to_string(),
1424 serde_json::json!(expected_target),
1425 );
1426 }
1427 }
1428 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1429 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1430 register_replay_mapping(self, &twin_id)?;
1431 self.refresh_cortical_mappings_hash();
1432 return Ok(twin_id);
1433 }
1434
1435 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1436 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1437 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1438 let mut twin_area = CorticalArea::new(
1439 twin_id,
1440 0,
1441 twin_name,
1442 upstream_area.dimensions,
1443 twin_position,
1444 twin_type,
1445 )?;
1446 twin_area.properties = self.build_memory_twin_properties(
1447 memory_area,
1448 upstream_area,
1449 memory_area_id,
1450 upstream_area_id,
1451 );
1452
1453 let _twin_idx = self.add_cortical_area(twin_area)?;
1454 let _ = self.create_neurons_for_area(&twin_id);
1455
1456 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1457 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1458 register_replay_mapping(self, &twin_id)?;
1459 self.refresh_cortical_mappings_hash();
1460 Ok(twin_id)
1461 }
1462
1463 fn build_memory_twin_position(
1464 &self,
1465 memory_area: &CorticalArea,
1466 upstream_area: &CorticalArea,
1467 ) -> GenomeCoordinate3D {
1468 let memory_parent = memory_area
1469 .properties
1470 .get("parent_region_id")
1471 .and_then(|v| v.as_str());
1472 let upstream_parent = upstream_area
1473 .properties
1474 .get("parent_region_id")
1475 .and_then(|v| v.as_str());
1476 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1477
1478 if !same_region {
1479 return GenomeCoordinate3D::new(
1480 memory_area.position.x + 20,
1481 memory_area.position.y,
1482 memory_area.position.z,
1483 );
1484 }
1485
1486 let width = upstream_area.dimensions.width as f32;
1487 let margin = (width * 0.25).ceil() as i32;
1488 let offset = upstream_area.dimensions.width as i32 + margin;
1489 GenomeCoordinate3D::new(
1490 upstream_area.position.x + offset,
1491 upstream_area.position.y,
1492 upstream_area.position.z,
1493 )
1494 }
1495
1496 fn build_memory_twin_id(
1497 &self,
1498 memory_area_id: &CorticalID,
1499 upstream_area_id: &CorticalID,
1500 ) -> BduResult<CorticalID> {
1501 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1502 hasher.write(memory_area_id.as_base_64().as_bytes());
1503 hasher.write(upstream_area_id.as_base_64().as_bytes());
1504 hasher.write(b"memory_twin");
1505 let hash = hasher.finish();
1506 let mut bytes = hash.to_be_bytes();
1507 bytes[0] = b'c';
1508 CorticalID::try_from_bytes(&bytes)
1509 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1510 }
1511
1512 fn build_memory_twin_properties(
1513 &self,
1514 memory_area: &CorticalArea,
1515 upstream_area: &CorticalArea,
1516 memory_area_id: &CorticalID,
1517 upstream_area_id: &CorticalID,
1518 ) -> HashMap<String, serde_json::Value> {
1519 let mut props = upstream_area.properties.clone();
1520 props.remove("cortical_mapping_dst");
1521 props.remove("upstream_cortical_areas");
1522 props.remove("parent_region_id");
1523 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1524 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1525 props.insert(
1526 "memory_twin_of".to_string(),
1527 serde_json::json!(upstream_area_id.as_base_64()),
1528 );
1529 props.insert(
1530 "memory_twin_for".to_string(),
1531 serde_json::json!(memory_area_id.as_base_64()),
1532 );
1533 if let Some(parent_region_id) = memory_area
1534 .properties
1535 .get("parent_region_id")
1536 .and_then(|v| v.as_str())
1537 {
1538 props.insert(
1539 "parent_region_id".to_string(),
1540 serde_json::json!(parent_region_id),
1541 );
1542 }
1543 props
1544 }
1545
1546 fn set_memory_twin_mapping(
1547 &mut self,
1548 memory_area_id: &CorticalID,
1549 upstream_area_id: &CorticalID,
1550 twin_id: &CorticalID,
1551 ) {
1552 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1553 let mapping = memory_area
1554 .properties
1555 .entry("memory_twin_areas".to_string())
1556 .or_insert_with(|| serde_json::json!({}));
1557 if let Some(map) = mapping.as_object_mut() {
1558 map.insert(
1559 upstream_area_id.as_base_64(),
1560 serde_json::json!(twin_id.as_base_64()),
1561 );
1562 }
1563 }
1564 }
1565
1566 fn ensure_memory_replay_mapping(
1567 &mut self,
1568 memory_area_id: &CorticalID,
1569 twin_id: &CorticalID,
1570 ) -> BduResult<()> {
1571 if !self.morphology_registry.contains("memory_replay") {
1572 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1573 }
1574 self.refresh_morphologies_hash();
1575 let mapping_data = vec![serde_json::json!({
1576 "morphology_id": "memory_replay",
1577 "morphology_scalar": [1, 1, 1],
1578 "postSynapticCurrent_multiplier": 1,
1579 "plasticity_flag": false,
1580 "plasticity_constant": 0,
1581 "ltp_multiplier": 0,
1582 "ltd_multiplier": 0,
1583 "plasticity_window": 0,
1584 })];
1585 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1586 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1587 self.refresh_cortical_area_hashes(true, false);
1589 Ok(())
1590 }
1591
1592 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1602 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1603 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1604 if let Some(arr) = upstream_prop.as_array_mut() {
1605 let src_value = serde_json::json!(src_cortical_idx);
1606 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1607 arr.remove(pos);
1608 debug!(target: "feagi-bdu",
1609 "Removed upstream area idx={} from cortical area '{}'",
1610 src_cortical_idx, target_cortical_id.as_base_64()
1611 );
1612 }
1613 }
1614 }
1615 }
1616 }
1617
1618 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1620 self.cortical_areas.contains_key(cortical_id)
1621 }
1622
1623 pub fn is_initialized(&self) -> bool {
1625 self.initialized && !self.cortical_areas.is_empty()
1626 }
1627
1628 pub fn add_brain_region(
1634 &mut self,
1635 region: BrainRegion,
1636 parent_id: Option<String>,
1637 ) -> BduResult<()> {
1638 self.brain_regions.add_region(region, parent_id)?;
1639 self.refresh_brain_regions_hash();
1640 Ok(())
1641 }
1642
1643 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1645 self.brain_regions.remove_region(region_id)?;
1646 self.refresh_brain_regions_hash();
1647 Ok(())
1648 }
1649
1650 pub fn change_brain_region_parent(
1652 &mut self,
1653 region_id: &str,
1654 new_parent_id: &str,
1655 ) -> BduResult<()> {
1656 self.brain_regions.change_parent(region_id, new_parent_id)?;
1657 self.refresh_brain_regions_hash();
1658 Ok(())
1659 }
1660
1661 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1663 self.brain_regions.get_region(region_id)
1664 }
1665
1666 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1668 self.brain_regions.get_region_mut(region_id)
1669 }
1670
1671 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1673 self.brain_regions.get_all_region_ids()
1674 }
1675
1676 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1678 &self.brain_regions
1679 }
1680
1681 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1687 &self.morphology_registry
1688 }
1689
1690 pub fn get_morphology_count(&self) -> usize {
1692 self.morphology_registry.count()
1693 }
1694
1695 pub fn upsert_morphology(
1700 &mut self,
1701 morphology_id: String,
1702 morphology: feagi_evolutionary::Morphology,
1703 ) {
1704 self.morphology_registry
1705 .add_morphology(morphology_id, morphology);
1706 self.refresh_morphologies_hash();
1707 }
1708
1709 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1715 let removed = self.morphology_registry.remove_morphology(morphology_id);
1716 if removed {
1717 self.refresh_morphologies_hash();
1718 }
1719 removed
1720 }
1721
1722 pub fn update_cortical_mapping(
1740 &mut self,
1741 src_area_id: &CorticalID,
1742 dst_area_id: &CorticalID,
1743 mapping_data: Vec<serde_json::Value>,
1744 ) -> BduResult<()> {
1745 use tracing::info;
1746
1747 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1748
1749 let mapping_has_bidirectional_stdp = |rules: &[serde_json::Value]| -> bool {
1751 for rule in rules {
1752 if let Some(obj) = rule.as_object() {
1753 if let Some(morphology_id) = obj.get("morphology_id").and_then(|v| v.as_str()) {
1754 if morphology_id == "associative_memory" {
1755 return true;
1756 }
1757 }
1758 }
1759 }
1760 false
1761 };
1762
1763 let requested_bidirectional = mapping_has_bidirectional_stdp(&mapping_data);
1764 let existing_bidirectional = self
1765 .cortical_areas
1766 .get(src_area_id)
1767 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1768 .and_then(|v| v.as_object())
1769 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1770 .and_then(|v| v.as_array())
1771 .map(|arr| mapping_has_bidirectional_stdp(arr))
1772 .unwrap_or(false);
1773 let should_apply_bidirectional = requested_bidirectional || existing_bidirectional;
1774
1775 {
1776 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1778 crate::types::BduError::InvalidArea(format!(
1779 "Source area not found: {}",
1780 src_area_id
1781 ))
1782 })?;
1783
1784 let cortical_mapping_dst =
1786 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1787 existing.as_object_mut().ok_or_else(|| {
1788 crate::types::BduError::InvalidMorphology(
1789 "cortical_mapping_dst is not an object".to_string(),
1790 )
1791 })?
1792 } else {
1793 src_area
1795 .properties
1796 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1797 src_area
1798 .properties
1799 .get_mut("cortical_mapping_dst")
1800 .unwrap()
1801 .as_object_mut()
1802 .unwrap()
1803 };
1804
1805 if mapping_data.is_empty() {
1807 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1809 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1810 } else {
1811 cortical_mapping_dst.insert(
1812 dst_area_id.as_base_64(),
1813 serde_json::Value::Array(mapping_data.clone()),
1814 );
1815 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1816 src_area_id, dst_area_id, mapping_data.len());
1817 }
1818 }
1819
1820 if should_apply_bidirectional {
1822 let dst_area = self.cortical_areas.get_mut(dst_area_id).ok_or_else(|| {
1823 crate::types::BduError::InvalidArea(format!(
1824 "Destination area not found: {}",
1825 dst_area_id
1826 ))
1827 })?;
1828 let dst_mapping_dst =
1829 if let Some(existing) = dst_area.properties.get_mut("cortical_mapping_dst") {
1830 existing.as_object_mut().ok_or_else(|| {
1831 crate::types::BduError::InvalidMorphology(
1832 "cortical_mapping_dst is not an object".to_string(),
1833 )
1834 })?
1835 } else {
1836 dst_area
1837 .properties
1838 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1839 dst_area
1840 .properties
1841 .get_mut("cortical_mapping_dst")
1842 .unwrap()
1843 .as_object_mut()
1844 .unwrap()
1845 };
1846
1847 if mapping_data.is_empty() {
1848 dst_mapping_dst.remove(&src_area_id.as_base_64());
1849 info!(
1850 target: "feagi-bdu",
1851 "Removed bi-directional STDP mirror mapping from {} to {}",
1852 dst_area_id,
1853 src_area_id
1854 );
1855 } else {
1856 dst_mapping_dst.insert(
1857 src_area_id.as_base_64(),
1858 serde_json::Value::Array(mapping_data.clone()),
1859 );
1860 info!(
1861 target: "feagi-bdu",
1862 "Updated bi-directional STDP mirror mapping from {} to {} with {} connections",
1863 dst_area_id,
1864 src_area_id,
1865 mapping_data.len()
1866 );
1867 }
1868 }
1869
1870 self.refresh_cortical_mappings_hash();
1871
1872 Ok(())
1873 }
1874
1875 pub fn regenerate_synapses_for_mapping(
1888 &mut self,
1889 src_area_id: &CorticalID,
1890 dst_area_id: &CorticalID,
1891 ) -> BduResult<usize> {
1892 use tracing::info;
1893
1894 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1895
1896 let mapping_rules_len = self
1897 .cortical_areas
1898 .get(src_area_id)
1899 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1900 .and_then(|v| v.as_object())
1901 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1902 .and_then(|v| v.as_array())
1903 .map(|arr| arr.len())
1904 .unwrap_or(0);
1905 tracing::debug!(
1906 target: "feagi-bdu",
1907 "Mapping rules for {} -> {}: {}",
1908 src_area_id,
1909 dst_area_id,
1910 mapping_rules_len
1911 );
1912
1913 let Some(npu_arc) = self.npu.clone() else {
1915 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
1916 return Ok(0);
1917 };
1918
1919 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
1929 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
1930 })?;
1931 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
1932 BduError::InvalidArea(format!(
1933 "No cortical idx for destination area {}",
1934 dst_area_id
1935 ))
1936 })?;
1937
1938 let mut pruned_synapse_count: usize = 0;
1941 use std::time::Instant;
1942 let start = Instant::now();
1943
1944 let (sources, targets) = {
1950 let lock_start = std::time::Instant::now();
1951 let npu = npu_arc.lock().unwrap();
1952 let lock_wait = lock_start.elapsed();
1953 tracing::debug!(
1954 target: "feagi-bdu",
1955 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
1956 lock_wait.as_secs_f64() * 1000.0,
1957 src_area_id,
1958 dst_area_id
1959 );
1960 let sources: Vec<NeuronId> = npu
1961 .get_neurons_in_cortical_area(src_idx)
1962 .into_iter()
1963 .map(NeuronId)
1964 .collect();
1965 let targets: Vec<NeuronId> = npu
1966 .get_neurons_in_cortical_area(dst_idx)
1967 .into_iter()
1968 .map(NeuronId)
1969 .collect();
1970 (sources, targets)
1971 };
1972
1973 tracing::debug!(
1974 target: "feagi-bdu",
1975 "Prune synapses: {} sources, {} targets",
1976 sources.len(),
1977 targets.len()
1978 );
1979
1980 if !sources.is_empty() && !targets.is_empty() {
1981 let remove_start = Instant::now();
1982 pruned_synapse_count = {
1983 let lock_start = std::time::Instant::now();
1984 let mut npu = npu_arc.lock().unwrap();
1985 let lock_wait = lock_start.elapsed();
1986 tracing::debug!(
1987 target: "feagi-bdu",
1988 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
1989 lock_wait.as_secs_f64() * 1000.0,
1990 src_area_id,
1991 dst_area_id
1992 );
1993 npu.remove_synapses_from_sources_to_targets(sources, targets)
1994 };
1995 let remove_time = remove_start.elapsed();
1996 let total_time = start.elapsed();
1997
1998 info!(
1999 target: "feagi-bdu",
2000 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2001 pruned_synapse_count,
2002 src_area_id,
2003 dst_area_id,
2004 total_time.as_millis(),
2005 remove_time.as_millis()
2006 );
2007
2008 if pruned_synapse_count > 0 {
2010 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2011 BduError::Internal(format!(
2012 "Pruned synapse count overflow (usize -> u32): {}",
2013 pruned_synapse_count
2014 ))
2015 })?;
2016 if let Some(state_manager) = StateManager::instance().try_read() {
2017 let core_state = state_manager.get_core_state();
2018 core_state.subtract_synapse_count(pruned_u32);
2019 state_manager.subtract_cortical_area_outgoing_synapses(
2020 &src_area_id.as_base_64(),
2021 pruned_synapse_count,
2022 );
2023 state_manager.subtract_cortical_area_incoming_synapses(
2024 &dst_area_id.as_base_64(),
2025 pruned_synapse_count,
2026 );
2027 }
2028
2029 {
2033 let mut cache = self.cached_synapse_counts_per_area.write();
2034 let entry = cache
2035 .entry(*src_area_id)
2036 .or_insert_with(|| AtomicUsize::new(0));
2037 let mut current = entry.load(Ordering::Relaxed);
2038 loop {
2039 let next = current.saturating_sub(pruned_synapse_count);
2040 match entry.compare_exchange(
2041 current,
2042 next,
2043 Ordering::Relaxed,
2044 Ordering::Relaxed,
2045 ) {
2046 Ok(_) => break,
2047 Err(v) => current = v,
2048 }
2049 }
2050 }
2051 }
2052 }
2053
2054 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2061 tracing::debug!(
2062 target: "feagi-bdu",
2063 "Synaptogenesis created {} synapses for {} -> {}",
2064 synapse_count,
2065 src_area_id,
2066 dst_area_id
2067 );
2068
2069 if synapse_count > 0 {
2072 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2073 BduError::Internal(format!(
2074 "Created synapse count overflow (usize -> u32): {}",
2075 synapse_count
2076 ))
2077 })?;
2078
2079 {
2081 let mut cache = self.cached_synapse_counts_per_area.write();
2082 cache
2083 .entry(*src_area_id)
2084 .or_insert_with(|| AtomicUsize::new(0))
2085 .fetch_add(synapse_count, Ordering::Relaxed);
2086 }
2087
2088 if let Some(state_manager) = StateManager::instance().try_read() {
2090 let core_state = state_manager.get_core_state();
2091 core_state.add_synapse_count(created_u32);
2092 state_manager
2093 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2094 state_manager
2095 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2096 }
2097 }
2098
2099 let src_idx_for_upstream = src_idx;
2102
2103 let has_mapping = self
2105 .cortical_areas
2106 .get(src_area_id)
2107 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2108 .and_then(|v| v.as_object())
2109 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2110 .is_some();
2111
2112 info!(target: "feagi-bdu",
2113 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2114 synapse_count,
2115 src_area_id.as_base_64(),
2116 dst_area_id.as_base_64(),
2117 has_mapping,
2118 if has_mapping { "" } else { "NOT " }
2119 );
2120
2121 if has_mapping {
2122 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2124
2125 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2126 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2127 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2128 warn!(
2129 target: "feagi-bdu",
2130 "Failed to ensure memory twin for {} -> {}: {}",
2131 src_area_id.as_base_64(),
2132 dst_area_id.as_base_64(),
2133 e
2134 );
2135 }
2136 }
2137 }
2138
2139 #[cfg(feature = "plasticity")]
2141 if let Some(ref executor) = self.plasticity_executor {
2142 use feagi_evolutionary::extract_memory_properties;
2143
2144 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2145 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2146 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2147 let upstream_non_memory =
2148 self.filter_non_memory_upstream_areas(&upstream_areas);
2149 debug!(
2150 target: "feagi-bdu",
2151 "Registering memory area idx={} id={} upstream={} depth={}",
2152 dst_area.cortical_idx,
2153 dst_area_id.as_base_64(),
2154 upstream_areas.len(),
2155 mem_props.temporal_depth
2156 );
2157
2158 if let Some(ref npu_arc) = self.npu {
2161 if let Ok(mut npu) = npu_arc.lock() {
2162 let existing_configs = npu.get_all_fire_ledger_configs();
2163 for &upstream_idx in &upstream_areas {
2164 let existing = existing_configs
2165 .iter()
2166 .find(|(idx, _)| *idx == upstream_idx)
2167 .map(|(_, w)| *w)
2168 .unwrap_or(0);
2169
2170 let desired = mem_props.temporal_depth as usize;
2171 let resolved = existing.max(desired);
2172 if resolved != existing {
2173 if let Err(e) =
2174 npu.configure_fire_ledger_window(upstream_idx, resolved)
2175 {
2176 warn!(
2177 target: "feagi-bdu",
2178 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2179 upstream_idx,
2180 resolved,
2181 e
2182 );
2183 }
2184 }
2185 }
2186 } else {
2187 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2188 }
2189 }
2190
2191 if let Ok(exec) = executor.lock() {
2192 use feagi_npu_plasticity::{
2193 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2194 };
2195
2196 let lifecycle_config = MemoryNeuronLifecycleConfig {
2197 initial_lifespan: mem_props.init_lifespan,
2198 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2199 longterm_threshold: mem_props.longterm_threshold,
2200 max_reactivations: 1000,
2201 };
2202
2203 exec.register_memory_area(
2204 dst_area.cortical_idx,
2205 dst_area_id.as_base_64(),
2206 mem_props.temporal_depth,
2207 upstream_non_memory,
2208 Some(lifecycle_config),
2209 );
2210 } else {
2211 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2212 }
2213 } else {
2214 debug!(
2215 target: "feagi-bdu",
2216 "Skipping plasticity registration: no memory properties for area {}",
2217 dst_area_id.as_base_64()
2218 );
2219 }
2220 } else {
2221 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2222 }
2223 } else {
2224 warn!(
2225 target: "feagi-bdu",
2226 "PlasticityExecutor not available; memory area {} not registered",
2227 dst_area_id.as_base_64()
2228 );
2229 }
2230
2231 #[cfg(not(feature = "plasticity"))]
2232 {
2233 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2234 }
2235 } else {
2236 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2238
2239 let mut npu = npu_arc.lock().unwrap();
2241 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2242 }
2243
2244 info!(
2245 target: "feagi-bdu",
2246 "Created {} new synapses: {} -> {}",
2247 synapse_count,
2248 src_area_id,
2249 dst_area_id
2250 );
2251
2252 if pruned_synapse_count > 0 || synapse_count == 0 {
2255 let mut npu = npu_arc.lock().unwrap();
2256 npu.rebuild_synapse_index();
2257 info!(
2258 target: "feagi-bdu",
2259 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2260 src_area_id,
2261 dst_area_id,
2262 pruned_synapse_count,
2263 synapse_count
2264 );
2265 } else {
2266 info!(
2267 target: "feagi-bdu",
2268 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2269 src_area_id,
2270 dst_area_id,
2271 synapse_count
2272 );
2273 }
2274
2275 {
2277 let npu = npu_arc.lock().unwrap();
2278 let fresh_count = npu.get_synapse_count();
2279 self.cached_synapse_count
2280 .store(fresh_count, Ordering::Relaxed);
2281 }
2282
2283 Ok(synapse_count)
2284 }
2285
2286 #[allow(clippy::too_many_arguments)]
2288 fn register_stdp_mapping_for_rule(
2289 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2290 src_area_id: &CorticalID,
2291 dst_area_id: &CorticalID,
2292 src_cortical_idx: u32,
2293 dst_cortical_idx: u32,
2294 rule_obj: &serde_json::Map<String, serde_json::Value>,
2295 bidirectional_stdp: bool,
2296 synapse_psp: u8,
2297 synapse_type: feagi_npu_neural::SynapseType,
2298 ) -> BduResult<()> {
2299 let plasticity_window = rule_obj
2300 .get("plasticity_window")
2301 .and_then(|v| v.as_u64())
2302 .ok_or_else(|| {
2303 BduError::Internal(format!(
2304 "Missing plasticity_window in plastic mapping rule {} -> {}",
2305 src_area_id, dst_area_id
2306 ))
2307 })? as usize;
2308 let plasticity_constant = rule_obj
2309 .get("plasticity_constant")
2310 .and_then(|v| v.as_i64())
2311 .ok_or_else(|| {
2312 BduError::Internal(format!(
2313 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2314 src_area_id, dst_area_id
2315 ))
2316 })?;
2317 let ltp_multiplier = rule_obj
2318 .get("ltp_multiplier")
2319 .and_then(|v| v.as_i64())
2320 .ok_or_else(|| {
2321 BduError::Internal(format!(
2322 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2323 src_area_id, dst_area_id
2324 ))
2325 })?;
2326 let ltd_multiplier = rule_obj
2327 .get("ltd_multiplier")
2328 .and_then(|v| v.as_i64())
2329 .ok_or_else(|| {
2330 BduError::Internal(format!(
2331 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2332 src_area_id, dst_area_id
2333 ))
2334 })?;
2335
2336 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2337 plasticity_window,
2338 plasticity_constant,
2339 ltp_multiplier,
2340 ltd_multiplier,
2341 bidirectional_stdp,
2342 synapse_psp,
2343 synapse_type,
2344 };
2345
2346 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2347 let mut npu_lock = npu
2348 .lock()
2349 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2350 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2351
2352 npu_lock
2353 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2354 .map_err(|e| {
2355 BduError::Internal(format!(
2356 "Failed to register STDP mapping {} -> {}: {}",
2357 src_area_id, dst_area_id, e
2358 ))
2359 })?;
2360
2361 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2363 for area_idx in [src_cortical_idx, dst_cortical_idx] {
2364 let existing = existing_configs
2365 .iter()
2366 .find(|(idx, _)| *idx == area_idx)
2367 .map(|(_, w)| *w)
2368 .unwrap_or(0);
2369 let resolved = existing.max(plasticity_window);
2370 if resolved != existing {
2371 npu_lock
2372 .configure_fire_ledger_window(area_idx, resolved)
2373 .map_err(|e| {
2374 BduError::Internal(format!(
2375 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2376 area_idx, resolved, e
2377 ))
2378 })?;
2379 }
2380 }
2381
2382 Ok(())
2383 }
2384
2385 fn resolve_synapse_params_for_rule(
2387 &self,
2388 src_area_id: &CorticalID,
2389 rule: &serde_json::Value,
2390 ) -> BduResult<(u8, u8, feagi_npu_neural::SynapseType)> {
2391 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2393 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2394 })?;
2395
2396 let (weight, synapse_type) = {
2402 let parse_i64 = |v: &serde_json::Value| -> Option<i64> {
2404 if let Some(i) = v.as_i64() {
2405 return Some(i);
2406 }
2407 let f = v.as_f64()?;
2408 if f.fract() == 0.0 {
2409 Some(f as i64)
2410 } else {
2411 None
2412 }
2413 };
2414
2415 let multiplier_i64: i64 = if let Some(obj) = rule.as_object() {
2416 obj.get("postSynapticCurrent_multiplier")
2417 .and_then(parse_i64)
2418 .unwrap_or(1) } else if let Some(arr) = rule.as_array() {
2420 arr.get(2).and_then(parse_i64).unwrap_or(1) } else {
2423 128 };
2425
2426 if multiplier_i64 < 0 {
2427 let abs = if multiplier_i64 == i64::MIN {
2428 i64::MAX
2429 } else {
2430 multiplier_i64.abs()
2431 };
2432 (
2433 abs.clamp(0, 255) as u8,
2434 feagi_npu_neural::SynapseType::Inhibitory,
2435 )
2436 } else {
2437 (
2438 multiplier_i64.clamp(0, 255) as u8,
2439 feagi_npu_neural::SynapseType::Excitatory,
2440 )
2441 }
2442 };
2443
2444 let (psp_f32, psp) = {
2451 use crate::models::cortical_area::CorticalAreaExt;
2452 let psp_f32 = src_area.postsynaptic_current();
2453 (psp_f32, psp_f32.clamp(0.0, 255.0) as u8)
2454 };
2455
2456 tracing::debug!(
2457 target: "feagi-bdu",
2458 "Resolved synapse params src={} weight={} psp={} psp_f32={} type={:?}",
2459 src_area_id.as_base_64(),
2460 weight,
2461 psp,
2462 psp_f32,
2463 synapse_type
2464 );
2465
2466 Ok((weight, psp, synapse_type))
2467 }
2468
2469 fn apply_cortical_mapping_for_pair(
2471 &mut self,
2472 src_area_id: &CorticalID,
2473 dst_area_id: &CorticalID,
2474 ) -> BduResult<usize> {
2475 let rules = {
2481 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2482 crate::types::BduError::InvalidArea(format!(
2483 "Source area not found: {}",
2484 src_area_id
2485 ))
2486 })?;
2487
2488 let Some(mapping_dst) = src_area
2489 .properties
2490 .get("cortical_mapping_dst")
2491 .and_then(|v| v.as_object())
2492 else {
2493 return Ok(0);
2494 };
2495
2496 let Some(rules) = mapping_dst
2497 .get(&dst_area_id.as_base_64())
2498 .and_then(|v| v.as_array())
2499 else {
2500 return Ok(0);
2501 };
2502
2503 rules.clone()
2504 }; if rules.is_empty() {
2507 return Ok(0);
2508 }
2509
2510 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2512 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2513 })?;
2514 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2515 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2516 })?;
2517
2518 let npu_arc = self
2520 .npu
2521 .as_ref()
2522 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2523 .clone();
2524
2525 tracing::debug!(
2526 target: "feagi-bdu",
2527 "Applying {} mapping rule(s) for {} -> {}",
2528 rules.len(),
2529 src_area_id,
2530 dst_area_id
2531 );
2532 let mut total_synapses = 0;
2534 for rule in &rules {
2535 let rule_obj = match rule.as_object() {
2536 Some(obj) => obj,
2537 None => continue,
2538 };
2539 let morphology_id = rule_obj
2540 .get("morphology_id")
2541 .and_then(|v| v.as_str())
2542 .unwrap_or("unknown");
2543
2544 let rule_keys: Vec<String> = rule_obj.keys().cloned().collect();
2545
2546 let mut plasticity_flag = rule_obj
2548 .get("plasticity_flag")
2549 .and_then(|v| v.as_bool())
2550 .unwrap_or(false);
2551 if morphology_id == "associative_memory" {
2552 plasticity_flag = true;
2553 }
2554 if plasticity_flag {
2555 let (_weight, psp, synapse_type) =
2556 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2557 let bidirectional_stdp = morphology_id == "associative_memory";
2558 if let Err(e) = Self::register_stdp_mapping_for_rule(
2559 &npu_arc,
2560 src_area_id,
2561 dst_area_id,
2562 src_cortical_idx,
2563 dst_cortical_idx,
2564 rule_obj,
2565 bidirectional_stdp,
2566 psp,
2567 synapse_type,
2568 ) {
2569 tracing::error!(
2570 target: "feagi-bdu",
2571 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2572 src_area_id,
2573 dst_area_id,
2574 morphology_id,
2575 rule_keys,
2576 e
2577 );
2578 return Err(e);
2579 }
2580 }
2581
2582 let synapse_count = match self.apply_single_morphology_rule(
2584 src_area_id,
2585 dst_area_id,
2586 rule,
2587 ) {
2588 Ok(count) => count,
2589 Err(e) => {
2590 tracing::error!(
2591 target: "feagi-bdu",
2592 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2593 src_area_id,
2594 dst_area_id,
2595 morphology_id,
2596 rule_keys,
2597 e
2598 );
2599 return Err(e);
2600 }
2601 };
2602 total_synapses += synapse_count;
2603 tracing::debug!(
2604 target: "feagi-bdu",
2605 "Rule {} created {} synapses for {} -> {}",
2606 morphology_id,
2607 synapse_count,
2608 src_area_id,
2609 dst_area_id
2610 );
2611 }
2612
2613 Ok(total_synapses)
2614 }
2615
2616 #[allow(clippy::too_many_arguments)]
2630 fn apply_function_morphology(
2631 &self,
2632 morphology_id: &str,
2633 rule: &serde_json::Value,
2634 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2635 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2636 src_area_id: &CorticalID,
2637 dst_area_id: &CorticalID,
2638 src_idx: u32,
2639 dst_idx: u32,
2640 weight: u8,
2641 psp: u8,
2642 synapse_attractivity: u8,
2643 synapse_type: feagi_npu_neural::SynapseType,
2644 ) -> BduResult<usize> {
2645 match morphology_id {
2646 "projector" => {
2647 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2649 crate::types::BduError::InvalidArea(format!(
2650 "Source area not found: {}",
2651 src_area_id
2652 ))
2653 })?;
2654 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2655 crate::types::BduError::InvalidArea(format!(
2656 "Destination area not found: {}",
2657 dst_area_id
2658 ))
2659 })?;
2660
2661 let src_dimensions = (
2662 src_area.dimensions.width as usize,
2663 src_area.dimensions.height as usize,
2664 src_area.dimensions.depth as usize,
2665 );
2666 let dst_dimensions = (
2667 dst_area.dimensions.width as usize,
2668 dst_area.dimensions.height as usize,
2669 dst_area.dimensions.depth as usize,
2670 );
2671
2672 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2673 let count = apply_projector_morphology_with_dimensions(
2674 npu,
2675 src_idx,
2676 dst_idx,
2677 src_dimensions,
2678 dst_dimensions,
2679 None, None, weight,
2682 psp,
2683 synapse_attractivity,
2684 synapse_type,
2685 )?;
2686 npu.rebuild_synapse_index();
2688 Ok(count as usize)
2689 }
2690 "episodic_memory" => {
2691 use tracing::trace;
2694 trace!(
2695 target: "feagi-bdu",
2696 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
2697 src_idx, dst_idx
2698 );
2699 Ok(0)
2700 }
2701 "memory_replay" => {
2702 use tracing::trace;
2704 trace!(
2705 target: "feagi-bdu",
2706 "Memory replay morphology: {} -> {} (no physical synapses)",
2707 src_idx, dst_idx
2708 );
2709 Ok(0)
2710 }
2711 "associative_memory" => {
2712 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2716 crate::types::BduError::InvalidArea(format!(
2717 "Source area not found: {}",
2718 src_area_id
2719 ))
2720 })?;
2721 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2722 crate::types::BduError::InvalidArea(format!(
2723 "Destination area not found: {}",
2724 dst_area_id
2725 ))
2726 })?;
2727
2728 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2729 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2730 {
2731 let src_dimensions = (
2732 src_area.dimensions.width as usize,
2733 src_area.dimensions.height as usize,
2734 src_area.dimensions.depth as usize,
2735 );
2736 let dst_dimensions = (
2737 dst_area.dimensions.width as usize,
2738 dst_area.dimensions.height as usize,
2739 dst_area.dimensions.depth as usize,
2740 );
2741 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2742 let count = apply_projector_morphology_with_dimensions(
2743 npu,
2744 src_idx,
2745 dst_idx,
2746 src_dimensions,
2747 dst_dimensions,
2748 None,
2749 None,
2750 weight,
2751 psp,
2752 synapse_attractivity,
2753 synapse_type,
2754 )?;
2755 npu.rebuild_synapse_index();
2756 Ok(count as usize)
2757 } else {
2758 Ok(0)
2759 }
2760 }
2761 "block_to_block" => {
2762 tracing::warn!(
2763 target: "feagi-bdu",
2764 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
2765 src_idx, dst_idx
2766 );
2767 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2769 crate::types::BduError::InvalidArea(format!(
2770 "Source area not found: {}",
2771 src_area_id
2772 ))
2773 })?;
2774 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2775 crate::types::BduError::InvalidArea(format!(
2776 "Destination area not found: {}",
2777 dst_area_id
2778 ))
2779 })?;
2780
2781 let src_dimensions = (
2782 src_area.dimensions.width as usize,
2783 src_area.dimensions.height as usize,
2784 src_area.dimensions.depth as usize,
2785 );
2786 let dst_dimensions = (
2787 dst_area.dimensions.width as usize,
2788 dst_area.dimensions.height as usize,
2789 dst_area.dimensions.depth as usize,
2790 );
2791
2792 let scalar = if let Some(obj) = rule.as_object() {
2794 if let Some(scalar_arr) =
2796 obj.get("morphology_scalar").and_then(|v| v.as_array())
2797 {
2798 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
2800 } else {
2801 1 }
2803 } else if let Some(arr) = rule.as_array() {
2804 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
2806 } else {
2807 1 };
2809
2810 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
2813 let count = if estimated_neurons > 100_000 {
2814 let _ = npu;
2816
2817 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
2818 npu_arc,
2819 src_idx,
2820 dst_idx,
2821 src_dimensions,
2822 dst_dimensions,
2823 scalar, weight,
2825 psp,
2826 synapse_attractivity,
2827 synapse_type,
2828 )? as usize
2829 } else {
2830 tracing::warn!(
2832 target: "feagi-bdu",
2833 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
2834 src_idx, dst_idx, src_dimensions, dst_dimensions
2835 );
2836 let count =
2837 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
2838 npu,
2839 src_idx,
2840 dst_idx,
2841 src_dimensions,
2842 dst_dimensions,
2843 scalar, weight,
2845 psp,
2846 synapse_attractivity,
2847 synapse_type,
2848 )? as usize;
2849 tracing::warn!(
2850 target: "feagi-bdu",
2851 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
2852 count
2853 );
2854 if count > 0 {
2856 npu.rebuild_synapse_index();
2857 }
2858 count
2859 };
2860
2861 if count > 0 && estimated_neurons > 100_000 {
2863 let mut npu_lock = npu_arc.lock().unwrap();
2864 npu_lock.rebuild_synapse_index();
2865 }
2866
2867 Ok(count)
2868 }
2869 _ => {
2870 use tracing::debug;
2873 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
2874 Ok(0)
2875 }
2876 }
2877 }
2878
2879 fn apply_single_morphology_rule(
2881 &mut self,
2882 src_area_id: &CorticalID,
2883 dst_area_id: &CorticalID,
2884 rule: &serde_json::Value,
2885 ) -> BduResult<usize> {
2886 let morphology_id = if let Some(arr) = rule.as_array() {
2888 arr.first().and_then(|v| v.as_str()).unwrap_or("")
2889 } else if let Some(obj) = rule.as_object() {
2890 obj.get("morphology_id")
2891 .and_then(|v| v.as_str())
2892 .unwrap_or("")
2893 } else {
2894 return Ok(0);
2895 };
2896
2897 if morphology_id.is_empty() {
2898 return Ok(0);
2899 }
2900
2901 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
2903 crate::types::BduError::InvalidMorphology(format!(
2904 "Morphology not found: {}",
2905 morphology_id
2906 ))
2907 })?;
2908
2909 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2911 crate::types::BduError::InvalidArea(format!(
2912 "Source area ID not found: {}",
2913 src_area_id
2914 ))
2915 })?;
2916 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2917 crate::types::BduError::InvalidArea(format!(
2918 "Destination area ID not found: {}",
2919 dst_area_id
2920 ))
2921 })?;
2922
2923 if let Some(ref npu_arc) = self.npu {
2925 let lock_start = std::time::Instant::now();
2926 let mut npu = npu_arc.lock().unwrap();
2927 let lock_wait = lock_start.elapsed();
2928 tracing::debug!(
2929 target: "feagi-bdu",
2930 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
2931 lock_wait.as_secs_f64() * 1000.0,
2932 src_area_id,
2933 dst_area_id,
2934 morphology_id
2935 );
2936
2937 let (weight, psp, synapse_type) =
2938 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2939
2940 let synapse_attractivity = if let Some(obj) = rule.as_object() {
2942 obj.get("synapse_attractivity")
2943 .and_then(|v| v.as_u64())
2944 .unwrap_or(100) as u8
2945 } else {
2946 100 };
2948
2949 match morphology.morphology_type {
2950 feagi_evolutionary::MorphologyType::Functions => {
2951 tracing::warn!(
2952 target: "feagi-bdu",
2953 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
2954 morphology_id
2955 );
2956 self.apply_function_morphology(
2959 morphology_id,
2960 rule,
2961 npu_arc,
2962 &mut npu,
2963 src_area_id,
2964 dst_area_id,
2965 *src_idx,
2966 *dst_idx,
2967 weight,
2968 psp,
2969 synapse_attractivity,
2970 synapse_type,
2971 )
2972 }
2973 feagi_evolutionary::MorphologyType::Vectors => {
2974 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
2975
2976 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2978 crate::types::BduError::InvalidArea(format!(
2979 "Destination area not found: {}",
2980 dst_area_id
2981 ))
2982 })?;
2983
2984 let dst_dimensions = (
2985 dst_area.dimensions.width as usize,
2986 dst_area.dimensions.height as usize,
2987 dst_area.dimensions.depth as usize,
2988 );
2989
2990 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
2991 morphology.parameters
2992 {
2993 let vectors_tuples: Vec<(i32, i32, i32)> =
2995 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
2996
2997 let count = apply_vectors_morphology_with_dimensions(
2998 &mut npu,
2999 *src_idx,
3000 *dst_idx,
3001 vectors_tuples,
3002 dst_dimensions,
3003 weight, psp, synapse_attractivity, synapse_type,
3007 )?;
3008 npu.rebuild_synapse_index();
3011 Ok(count as usize)
3012 } else {
3013 Ok(0)
3014 }
3015 }
3016 feagi_evolutionary::MorphologyType::Patterns => {
3017 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3018 use crate::connectivity::rules::patterns::{
3019 Pattern3D, PatternElement as RulePatternElement,
3020 };
3021 use feagi_evolutionary::PatternElement as EvoPatternElement;
3022
3023 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3024 morphology.parameters
3025 else {
3026 return Ok(0);
3027 };
3028
3029 let convert_element =
3030 |element: &EvoPatternElement|
3031 -> crate::types::BduResult<RulePatternElement> {
3032 match element {
3033 EvoPatternElement::Value(value) => {
3034 if *value < 0 {
3035 return Err(crate::types::BduError::InvalidMorphology(
3036 format!(
3037 "Pattern morphology {} contains negative voxel coordinate {}",
3038 morphology_id, value
3039 ),
3040 ));
3041 }
3042 Ok(RulePatternElement::Exact(*value))
3043 }
3044 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3045 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3046 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3047 }
3048 };
3049
3050 let mut converted_patterns = Vec::with_capacity(patterns.len());
3051 for pattern_pair in patterns {
3052 if pattern_pair.len() != 2 {
3053 return Err(crate::types::BduError::InvalidMorphology(format!(
3054 "Pattern morphology {} must contain [src, dst] pairs",
3055 morphology_id
3056 )));
3057 }
3058
3059 let src_pattern = &pattern_pair[0];
3060 let dst_pattern = &pattern_pair[1];
3061
3062 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3063 return Err(crate::types::BduError::InvalidMorphology(format!(
3064 "Pattern morphology {} requires 3-axis patterns",
3065 morphology_id
3066 )));
3067 }
3068
3069 let src: Pattern3D = (
3070 convert_element(&src_pattern[0])?,
3071 convert_element(&src_pattern[1])?,
3072 convert_element(&src_pattern[2])?,
3073 );
3074 let dst: Pattern3D = (
3075 convert_element(&dst_pattern[0])?,
3076 convert_element(&dst_pattern[1])?,
3077 convert_element(&dst_pattern[2])?,
3078 );
3079
3080 converted_patterns.push((src, dst));
3081 }
3082
3083 let count = apply_patterns_morphology(
3084 &mut npu,
3085 *src_idx,
3086 *dst_idx,
3087 converted_patterns,
3088 weight,
3089 psp,
3090 synapse_attractivity,
3091 synapse_type,
3092 )?;
3093 if count > 0 {
3094 npu.rebuild_synapse_index();
3095 }
3096 Ok(count as usize)
3097 }
3098 _ => {
3099 use tracing::debug;
3100 debug!(target: "feagi-bdu", "Morphology type {:?} not yet fully implemented", morphology.morphology_type);
3101 Ok(0)
3102 }
3103 }
3104 } else {
3105 Ok(0) }
3107 }
3108
3109 pub fn set_npu(
3122 &mut self,
3123 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3124 ) {
3125 self.npu = Some(Arc::clone(&npu));
3126 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3127
3128 #[cfg(not(feature = "wasm"))]
3131 {
3132 use feagi_state_manager::StateManager;
3133 if let Some(state_manager) = StateManager::instance().try_read() {
3134 let core_state = state_manager.get_core_state();
3135 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3137 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3138 info!(
3139 target: "feagi-bdu",
3140 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3141 self.config.max_neurons, self.config.max_synapses
3142 );
3143 }
3144 }
3145
3146 let existing_area_count = self.cortical_id_to_idx.len();
3153 if existing_area_count > 0 {
3154 match npu.lock() {
3155 Ok(mut npu_lock) => {
3156 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3157 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3158 }
3159 info!(
3160 target: "feagi-bdu",
3161 "🔁 Backfilled {} cortical area registrations into NPU",
3162 existing_area_count
3163 );
3164 }
3165 Err(e) => {
3166 warn!(
3167 target: "feagi-bdu",
3168 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3169 e
3170 );
3171 }
3172 }
3173 }
3174
3175 self.update_all_cached_stats();
3177 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3178 self.get_neuron_count(), self.get_synapse_count());
3179 }
3180
3181 pub fn has_npu(&self) -> bool {
3183 self.npu.is_some()
3184 }
3185
3186 pub fn get_npu(
3193 &self,
3194 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3195 {
3196 self.npu.as_ref()
3197 }
3198
3199 #[cfg(feature = "plasticity")]
3202 pub fn set_plasticity_executor(
3203 &mut self,
3204 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3205 ) {
3206 self.plasticity_executor = Some(executor);
3207 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3208 }
3209
3210 #[cfg(feature = "plasticity")]
3212 pub fn get_plasticity_executor(
3213 &self,
3214 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3215 self.plasticity_executor.as_ref()
3216 }
3217
3218 pub fn get_neuron_capacity(&self) -> usize {
3230 self.config.max_neurons
3232 }
3233
3234 pub fn get_synapse_capacity(&self) -> usize {
3246 self.config.max_synapses
3248 }
3249
3250 pub fn update_fatigue_index(&self) -> Option<u8> {
3265 let mut last_calc = match self.last_fatigue_calculation.lock() {
3267 Ok(guard) => guard,
3268 Err(_) => return None, };
3270
3271 let now = std::time::Instant::now();
3272 if now.duration_since(*last_calc).as_secs() < 2 {
3273 return None; }
3275 *last_calc = now;
3276 drop(last_calc);
3277
3278 let regular_neuron_count = self.get_neuron_count();
3280 let regular_neuron_capacity = self.get_neuron_capacity();
3281 let regular_neuron_util = if regular_neuron_capacity > 0 {
3282 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3283 } else {
3284 0
3285 };
3286
3287 let memory_neuron_util = match StateManager::instance().try_read() {
3291 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3292 None => {
3293 return None;
3295 }
3296 };
3297
3298 let synapse_count = self.get_synapse_count();
3300 let synapse_capacity = self.get_synapse_capacity();
3301 let synapse_util = if synapse_capacity > 0 {
3302 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3303 } else {
3304 0
3305 };
3306
3307 let fatigue_index = regular_neuron_util
3309 .max(memory_neuron_util)
3310 .max(synapse_util);
3311
3312 let current_fatigue_active = {
3314 StateManager::instance()
3316 .try_read()
3317 .map(|m| m.get_core_state().is_fatigue_active())
3318 .unwrap_or(false)
3319 };
3320
3321 let new_fatigue_active = if fatigue_index >= 85 {
3322 true
3323 } else if fatigue_index < 80 {
3324 false
3325 } else {
3326 current_fatigue_active };
3328
3329 if let Some(state_manager) = StateManager::instance().try_write() {
3333 let core_state = state_manager.get_core_state();
3334 core_state.set_fatigue_index(fatigue_index);
3335 core_state.set_fatigue_active(new_fatigue_active);
3336 core_state.set_regular_neuron_util(regular_neuron_util);
3337 core_state.set_memory_neuron_util(memory_neuron_util);
3338 core_state.set_synapse_util(synapse_util);
3339 } else {
3340 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3342 }
3343
3344 if let Some(ref npu) = self.npu {
3346 if let Ok(mut npu_lock) = npu.lock() {
3347 npu_lock.set_fatigue_active(new_fatigue_active);
3348 }
3349 }
3350
3351 trace!(
3352 target: "feagi-bdu",
3353 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3354 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3355 );
3356
3357 Some(fatigue_index)
3358 }
3359
3360 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3377 let area = self
3379 .cortical_areas
3380 .get(cortical_id)
3381 .ok_or_else(|| {
3382 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3383 })?
3384 .clone();
3385
3386 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3388 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3389 })?;
3390
3391 let npu = self
3393 .npu
3394 .as_ref()
3395 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3396
3397 use crate::models::CorticalAreaExt;
3400 let per_voxel_cnt = area.neurons_per_voxel();
3401 let firing_threshold = area.firing_threshold();
3402 let firing_threshold_increment_x = area.firing_threshold_increment_x();
3403 let firing_threshold_increment_y = area.firing_threshold_increment_y();
3404 let firing_threshold_increment_z = area.firing_threshold_increment_z();
3405 let firing_threshold_limit_raw = area.firing_threshold_limit();
3407 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
3408 f32::MAX } else {
3410 firing_threshold_limit_raw
3411 };
3412
3413 if firing_threshold_increment_x != 0.0
3415 || firing_threshold_increment_y != 0.0
3416 || firing_threshold_increment_z != 0.0
3417 {
3418 info!(
3419 target: "feagi-bdu",
3420 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
3421 cortical_id.as_base_64(),
3422 firing_threshold_increment_x,
3423 firing_threshold_increment_y,
3424 firing_threshold_increment_z
3425 );
3426 } else {
3427 if area.properties.contains_key("firing_threshold_increment_x")
3429 || area.properties.contains_key("firing_threshold_increment_y")
3430 || area.properties.contains_key("firing_threshold_increment_z")
3431 {
3432 info!(
3433 target: "feagi-bdu",
3434 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
3435 cortical_id.as_base_64(),
3436 area.properties.get("firing_threshold_increment_x"),
3437 area.properties.get("firing_threshold_increment_y"),
3438 area.properties.get("firing_threshold_increment_z")
3439 );
3440 }
3441 }
3442
3443 let leak_coefficient = area.leak_coefficient();
3444 let excitability = area.neuron_excitability();
3445 let refractory_period = area.refractory_period();
3446 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
3448 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
3449 u16::MAX } else {
3451 consecutive_fire_limit_raw
3452 };
3453 let snooze_length = area.snooze_period();
3454 let mp_charge_accumulation = area.mp_charge_accumulation();
3455
3456 let voxels = area.dimensions.width as usize
3458 * area.dimensions.height as usize
3459 * area.dimensions.depth as usize;
3460 let expected_neurons = voxels * per_voxel_cnt as usize;
3461
3462 trace!(
3463 target: "feagi-bdu",
3464 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
3465 cortical_id.as_base_64(),
3466 area.dimensions.width,
3467 area.dimensions.height,
3468 area.dimensions.depth,
3469 per_voxel_cnt,
3470 expected_neurons
3471 );
3472
3473 let mut npu_lock = npu
3476 .lock()
3477 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3478
3479 let neuron_count = npu_lock
3480 .create_cortical_area_neurons(
3481 *cortical_idx,
3482 area.dimensions.width,
3483 area.dimensions.height,
3484 area.dimensions.depth,
3485 per_voxel_cnt,
3486 firing_threshold,
3487 firing_threshold_increment_x,
3488 firing_threshold_increment_y,
3489 firing_threshold_increment_z,
3490 firing_threshold_limit,
3491 leak_coefficient,
3492 0.0, 0, refractory_period,
3495 excitability,
3496 consecutive_fire_limit,
3497 snooze_length,
3498 mp_charge_accumulation,
3499 )
3500 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?;
3501
3502 trace!(
3503 target: "feagi-bdu",
3504 "Created {} neurons for area {} via NPU",
3505 neuron_count,
3506 cortical_id.as_base_64()
3507 );
3508
3509 {
3512 let mut cache = self.cached_neuron_counts_per_area.write();
3513 cache
3514 .entry(*cortical_id)
3515 .or_insert_with(|| AtomicUsize::new(0))
3516 .store(neuron_count as usize, Ordering::Relaxed);
3517 }
3518
3519 if let Some(state_manager) = StateManager::instance().try_read() {
3521 state_manager
3522 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
3523 }
3524
3525 self.cached_neuron_count
3527 .fetch_add(neuron_count as usize, Ordering::Relaxed);
3528
3529 if let Some(state_manager) = StateManager::instance().try_read() {
3531 let core_state = state_manager.get_core_state();
3532 core_state.add_neuron_count(neuron_count);
3533 core_state.add_regular_neuron_count(neuron_count);
3534 }
3535
3536 Ok(neuron_count)
3544 }
3545
3546 #[allow(clippy::too_many_arguments)]
3570 pub fn add_neuron(
3571 &mut self,
3572 cortical_id: &CorticalID,
3573 x: u32,
3574 y: u32,
3575 z: u32,
3576 firing_threshold: f32,
3577 firing_threshold_limit: f32,
3578 leak_coefficient: f32,
3579 resting_potential: f32,
3580 neuron_type: u8,
3581 refractory_period: u16,
3582 excitability: f32,
3583 consecutive_fire_limit: u16,
3584 snooze_length: u16,
3585 mp_charge_accumulation: bool,
3586 ) -> BduResult<u64> {
3587 if !self.cortical_areas.contains_key(cortical_id) {
3589 return Err(BduError::InvalidArea(format!(
3590 "Cortical area {} not found",
3591 cortical_id
3592 )));
3593 }
3594
3595 let cortical_idx = *self
3596 .cortical_id_to_idx
3597 .get(cortical_id)
3598 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
3599
3600 let npu = self
3602 .npu
3603 .as_ref()
3604 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3605
3606 let mut npu_lock = npu
3607 .lock()
3608 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3609
3610 let neuron_id = npu_lock
3612 .add_neuron(
3613 firing_threshold,
3614 firing_threshold_limit,
3615 leak_coefficient,
3616 resting_potential,
3617 neuron_type as i32,
3618 refractory_period,
3619 excitability,
3620 consecutive_fire_limit,
3621 snooze_length,
3622 mp_charge_accumulation,
3623 cortical_idx,
3624 x,
3625 y,
3626 z,
3627 )
3628 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
3629
3630 trace!(
3631 target: "feagi-bdu",
3632 "Created neuron {} in area {} at ({}, {}, {})",
3633 neuron_id.0,
3634 cortical_id,
3635 x,
3636 y,
3637 z
3638 );
3639
3640 if let Some(state_manager) = StateManager::instance().try_read() {
3642 let core_state = state_manager.get_core_state();
3643 core_state.add_neuron_count(1);
3644 core_state.add_regular_neuron_count(1);
3645 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3646 }
3647
3648 Ok(neuron_id.0 as u64)
3649 }
3650
3651 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
3662 let npu = self
3664 .npu
3665 .as_ref()
3666 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3667
3668 let mut npu_lock = npu
3669 .lock()
3670 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3671
3672 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
3673 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
3674
3675 let deleted = npu_lock.delete_neuron(neuron_id as u32);
3676
3677 if deleted {
3678 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
3679
3680 if let Some(state_manager) = StateManager::instance().try_read() {
3682 let core_state = state_manager.get_core_state();
3683 core_state.subtract_neuron_count(1);
3684 core_state.subtract_regular_neuron_count(1);
3685 if let Some(cortical_id) = cortical_id {
3686 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3687 }
3688 }
3689
3690 }
3694
3695 Ok(deleted)
3696 }
3697
3698 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
3712 let src_area = self
3714 .cortical_areas
3715 .get(src_cortical_id)
3716 .ok_or_else(|| {
3717 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
3718 })?
3719 .clone();
3720
3721 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
3723 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
3724 _ => return Ok(0), };
3726
3727 let src_cortical_idx = *self
3728 .cortical_id_to_idx
3729 .get(src_cortical_id)
3730 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
3731
3732 let mut total_synapses = 0u32;
3733 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
3737 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
3739 Ok(id) => id,
3740 Err(_) => {
3741 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
3742 continue;
3743 }
3744 };
3745
3746 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
3748 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
3749 continue;
3750 }
3751
3752 let synapse_count =
3754 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
3755 total_synapses += synapse_count as u32;
3756
3757 upstream_updates.push((dst_cortical_id, src_cortical_idx));
3760 }
3761
3762 for (dst_id, src_idx) in upstream_updates {
3764 self.add_upstream_area(&dst_id, src_idx);
3765 }
3766
3767 trace!(
3768 target: "feagi-bdu",
3769 "Created {} synapses for area {} via NPU",
3770 total_synapses,
3771 src_cortical_id
3772 );
3773
3774 if total_synapses > 0 {
3777 let mut cache = self.cached_synapse_counts_per_area.write();
3778 cache
3779 .entry(*src_cortical_id)
3780 .or_insert_with(|| AtomicUsize::new(0))
3781 .fetch_add(total_synapses as usize, Ordering::Relaxed);
3782 }
3783
3784 self.cached_synapse_count
3786 .fetch_add(total_synapses as usize, Ordering::Relaxed);
3787
3788 if total_synapses > 0 {
3790 if let Some(state_manager) = StateManager::instance().try_read() {
3791 let core_state = state_manager.get_core_state();
3792 core_state.add_synapse_count(total_synapses);
3793 }
3794 }
3795
3796 Ok(total_synapses)
3797 }
3798
3799 pub fn has_neuron(&self, neuron_id: u64) -> bool {
3818 if let Some(ref npu) = self.npu {
3819 if let Ok(npu_lock) = npu.lock() {
3820 npu_lock.is_neuron_valid(neuron_id as u32)
3822 } else {
3823 false
3824 }
3825 } else {
3826 false
3827 }
3828 }
3829
3830 pub fn get_neuron_count(&self) -> usize {
3842 if let Some(ref npu) = self.npu {
3844 if let Ok(npu_lock) = npu.try_lock() {
3845 let fresh_count = npu_lock.get_neuron_count();
3846 self.cached_neuron_count
3847 .store(fresh_count, Ordering::Relaxed);
3848 }
3849 }
3851
3852 self.cached_neuron_count.load(Ordering::Relaxed)
3854 }
3855
3856 pub fn update_cached_neuron_count(&self) {
3862 if let Some(ref npu) = self.npu {
3863 if let Ok(npu_lock) = npu.try_lock() {
3864 let count = npu_lock.get_neuron_count();
3865 self.cached_neuron_count.store(count, Ordering::Relaxed);
3866 }
3867 }
3868 }
3869
3870 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
3874 let npu = self.npu.as_ref()?;
3875 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
3876 let npu_lock = npu.lock().ok()?;
3877 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
3878 drop(npu_lock);
3879
3880 let mut cache = self.cached_neuron_counts_per_area.write();
3881 cache
3882 .entry(*cortical_id)
3883 .or_insert_with(|| AtomicUsize::new(0))
3884 .store(count, Ordering::Relaxed);
3885
3886 if let Some(state_manager) = StateManager::instance().try_read() {
3888 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
3889 }
3890
3891 self.update_cached_neuron_count();
3892
3893 Some(count)
3894 }
3895
3896 pub fn get_synapse_count(&self) -> usize {
3908 if let Some(ref npu) = self.npu {
3910 if let Ok(npu_lock) = npu.try_lock() {
3911 let fresh_count = npu_lock.get_synapse_count();
3912 self.cached_synapse_count
3913 .store(fresh_count, Ordering::Relaxed);
3914 }
3915 }
3917
3918 self.cached_synapse_count.load(Ordering::Relaxed)
3920 }
3921
3922 pub fn update_cached_synapse_count(&self) {
3928 if let Some(ref npu) = self.npu {
3929 if let Ok(npu_lock) = npu.try_lock() {
3930 let count = npu_lock.get_synapse_count();
3931 self.cached_synapse_count.store(count, Ordering::Relaxed);
3932 }
3933 }
3934 }
3935
3936 pub fn update_all_cached_stats(&self) {
3942 self.update_cached_neuron_count();
3943 self.update_cached_synapse_count();
3944 }
3945
3946 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
3957 if let Some(ref npu) = self.npu {
3958 if let Ok(npu_lock) = npu.lock() {
3959 npu_lock
3960 .get_neuron_coordinates(neuron_id as u32)
3961 .unwrap_or((0, 0, 0))
3962 } else {
3963 (0, 0, 0)
3964 }
3965 } else {
3966 (0, 0, 0)
3967 }
3968 }
3969
3970 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
3981 if let Some(ref npu) = self.npu {
3982 if let Ok(npu_lock) = npu.lock() {
3983 npu_lock.get_neuron_cortical_area(neuron_id as u32)
3984 } else {
3985 0
3986 }
3987 } else {
3988 0
3989 }
3990 }
3991
3992 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4003 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4005 Some(idx) => *idx,
4006 None => return Vec::new(),
4007 };
4008
4009 if let Some(ref npu) = self.npu {
4010 if let Ok(npu_lock) = npu.lock() {
4011 npu_lock
4013 .get_neurons_in_cortical_area(cortical_idx)
4014 .into_iter()
4015 .map(|id| id as u64)
4016 .collect()
4017 } else {
4018 Vec::new()
4019 }
4020 } else {
4021 Vec::new()
4022 }
4023 }
4024
4025 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4036 if let Some(ref npu) = self.npu {
4037 if let Ok(npu_lock) = npu.lock() {
4038 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4039 } else {
4040 Vec::new()
4041 }
4042 } else {
4043 Vec::new()
4044 }
4045 }
4046
4047 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, u8, u8, u8)> {
4058 if let Some(ref npu) = self.npu {
4059 if let Ok(npu_lock) = npu.lock() {
4060 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4061 } else {
4062 Vec::new()
4063 }
4064 } else {
4065 Vec::new()
4066 }
4067 }
4068
4069 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4095 let cache = self.cached_neuron_counts_per_area.read();
4097 let base_count = cache
4098 .get(cortical_id)
4099 .map(|count| count.load(Ordering::Relaxed))
4100 .unwrap_or(0);
4101
4102 let memory_count = self
4104 .cortical_areas
4105 .get(cortical_id)
4106 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4107 .and_then(|_| {
4108 StateManager::instance()
4109 .try_read()
4110 .and_then(|state_manager| {
4111 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4112 })
4113 })
4114 .map(|stats| stats.neuron_count)
4115 .unwrap_or(0);
4116
4117 base_count.saturating_add(memory_count)
4118 }
4119
4120 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4127 let mut result = Vec::new();
4128
4129 for cortical_id in self.cortical_areas.keys() {
4130 let count = self.get_neuron_count_in_area(cortical_id);
4131 if count > 0 {
4132 result.push((cortical_id.to_string(), count));
4133 }
4134 }
4135
4136 result
4137 }
4138
4139 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4150 self.get_neuron_count_in_area(cortical_id) > 0
4151 }
4152
4153 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4169 let cache = self.cached_synapse_counts_per_area.read();
4171 cache
4172 .get(cortical_id)
4173 .map(|count| count.load(Ordering::Relaxed))
4174 .unwrap_or(0)
4175 }
4176
4177 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4187 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4188 return 0;
4189 }
4190
4191 if let Some(state_manager) = StateManager::instance().try_read() {
4192 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4193 return stats.incoming_synapse_count;
4194 }
4195 }
4196
4197 0
4198 }
4199
4200 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4210 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4211 return 0;
4212 }
4213
4214 if let Some(state_manager) = StateManager::instance().try_read() {
4215 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4216 return stats.outgoing_synapse_count;
4217 }
4218 }
4219
4220 0
4221 }
4222
4223 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4235 let synapses = self.get_outgoing_synapses(source_neuron_id);
4236 synapses
4237 .iter()
4238 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4239 }
4240
4241 pub fn get_connection_weight(
4253 &self,
4254 source_neuron_id: u64,
4255 target_neuron_id: u64,
4256 ) -> Option<u8> {
4257 let synapses = self.get_outgoing_synapses(source_neuron_id);
4258 synapses
4259 .iter()
4260 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4261 .map(|(_, weight, _, _)| *weight)
4262 }
4263
4264 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4275 let neurons = self.get_neurons_in_area(cortical_id);
4276 let neuron_count = neurons.len();
4277
4278 if neuron_count == 0 {
4279 return (0, 0, 0.0);
4280 }
4281
4282 let mut total_synapses = 0;
4283 for neuron_id in neurons {
4284 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4285 }
4286
4287 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4288
4289 (neuron_count, total_synapses, avg_synapses)
4290 }
4291
4292 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4303 let cortical_idx = self.get_neuron_cortical_idx(neuron_id);
4304 self.cortical_idx_to_id.get(&cortical_idx).copied()
4305 }
4306
4307 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4318 let area = match self.cortical_areas.get(cortical_id) {
4319 Some(a) => a,
4320 None => return 0.0,
4321 };
4322
4323 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4324 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4325
4326 if volume == 0 {
4327 return 0.0;
4328 }
4329
4330 neuron_count as f32 / volume as f32
4331 }
4332
4333 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
4340 let mut stats = Vec::new();
4341
4342 for cortical_id in self.cortical_areas.keys() {
4343 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4344 let synapse_count = self.get_synapse_count_in_area(cortical_id);
4345 let density = self.get_neuron_density(cortical_id);
4346
4347 stats.push((
4348 cortical_id.to_string(),
4349 neuron_count,
4350 synapse_count,
4351 density,
4352 ));
4353 }
4354
4355 stats
4356 }
4357
4358 pub fn get_config(&self) -> &ConnectomeConfig {
4364 &self.config
4365 }
4366
4367 pub fn set_config(&mut self, config: ConnectomeConfig) {
4369 self.config = config;
4370 }
4371
4372 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
4391 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
4392
4393 use feagi_structures::genomic::cortical_area::{
4394 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
4395 };
4396
4397 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
4399 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
4400 })?;
4401
4402 let core_position = (0, 0, 0).into();
4404
4405 let death_id = CoreCorticalType::Death.to_cortical_id();
4407 if !self.cortical_areas.contains_key(&death_id) {
4408 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
4409 let death_area = CorticalArea::new(
4410 death_id,
4411 0, "_death".to_string(),
4413 core_dimensions,
4414 core_position,
4415 CorticalAreaType::Core(CoreCorticalType::Death),
4416 )
4417 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
4418 match self.add_cortical_area(death_area) {
4419 Ok(idx) => {
4420 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
4421 }
4422 Err(e) => {
4423 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
4424 return Err(e);
4425 }
4426 }
4427 } else {
4428 info!(target: "feagi-bdu", " ✓ _death area already exists");
4429 }
4430
4431 let power_id = CoreCorticalType::Power.to_cortical_id();
4433 if !self.cortical_areas.contains_key(&power_id) {
4434 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
4435 let power_area = CorticalArea::new(
4436 power_id,
4437 1, "_power".to_string(),
4439 core_dimensions,
4440 core_position,
4441 CorticalAreaType::Core(CoreCorticalType::Power),
4442 )
4443 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
4444 match self.add_cortical_area(power_area) {
4445 Ok(idx) => {
4446 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
4447 }
4448 Err(e) => {
4449 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
4450 return Err(e);
4451 }
4452 }
4453 } else {
4454 info!(target: "feagi-bdu", " ✓ _power area already exists");
4455 }
4456
4457 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
4459 if !self.cortical_areas.contains_key(&fatigue_id) {
4460 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
4461 let fatigue_area = CorticalArea::new(
4462 fatigue_id,
4463 2, "_fatigue".to_string(),
4465 core_dimensions,
4466 core_position,
4467 CorticalAreaType::Core(CoreCorticalType::Fatigue),
4468 )
4469 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
4470 match self.add_cortical_area(fatigue_area) {
4471 Ok(idx) => {
4472 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
4473 }
4474 Err(e) => {
4475 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
4476 return Err(e);
4477 }
4478 }
4479 } else {
4480 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
4481 }
4482
4483 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
4484 Ok(())
4485 }
4486
4487 #[deprecated(
4504 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
4505 )]
4506 #[allow(deprecated)]
4507 pub fn save_genome_to_json(
4508 &self,
4509 genome_id: Option<String>,
4510 genome_title: Option<String>,
4511 ) -> BduResult<String> {
4512 let mut brain_regions_with_parents = std::collections::HashMap::new();
4514
4515 for region_id in self.brain_regions.get_all_region_ids() {
4516 if let Some(region) = self.brain_regions.get_region(region_id) {
4517 let parent_id = self
4518 .brain_regions
4519 .get_parent(region_id)
4520 .map(|s| s.to_string());
4521 brain_regions_with_parents
4522 .insert(region_id.to_string(), (region.clone(), parent_id));
4523 }
4524 }
4525
4526 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
4528 &self.cortical_areas,
4529 &brain_regions_with_parents,
4530 genome_id,
4531 genome_title,
4532 )?)
4533 }
4534
4535 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
4566 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
4567
4568 self.cortical_areas.clear();
4570 self.cortical_id_to_idx.clear();
4571 self.cortical_idx_to_id.clear();
4572 self.next_cortical_idx = 3;
4574 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4575
4576 self.brain_regions = BrainRegionHierarchy::new();
4578
4579 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
4590 Ok(())
4591 }
4592
4593 pub fn resize_for_genome(
4603 &mut self,
4604 genome: &feagi_evolutionary::RuntimeGenome,
4605 ) -> BduResult<()> {
4606 self.morphology_registry = genome.morphologies.clone();
4608 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
4609
4610 let required_neurons = genome.stats.innate_neuron_count;
4612 let required_synapses = genome.stats.innate_synapse_count;
4613
4614 info!(target: "feagi-bdu",
4615 "Genome requires: {} neurons, {} synapses",
4616 required_neurons,
4617 required_synapses
4618 );
4619
4620 let mut total_voxels = 0;
4622 for area in genome.cortical_areas.values() {
4623 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4624 }
4625
4626 info!(target: "feagi-bdu",
4627 "Genome has {} cortical areas with {} total voxels",
4628 genome.cortical_areas.len(),
4629 total_voxels
4630 );
4631
4632 Ok(())
4637 }
4638
4639 pub fn create_synapse(
4658 &mut self,
4659 source_neuron_id: u64,
4660 target_neuron_id: u64,
4661 weight: u8,
4662 psp: u8,
4663 synapse_type: u8,
4664 ) -> BduResult<()> {
4665 let npu = self
4667 .npu
4668 .as_ref()
4669 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4670
4671 let mut npu_lock = npu
4672 .lock()
4673 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4674
4675 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4677 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4678
4679 if !source_exists {
4680 return Err(BduError::InvalidNeuron(format!(
4681 "Source neuron {} not found",
4682 source_neuron_id
4683 )));
4684 }
4685 if !target_exists {
4686 return Err(BduError::InvalidNeuron(format!(
4687 "Target neuron {} not found",
4688 target_neuron_id
4689 )));
4690 }
4691
4692 let syn_type = if synapse_type == 0 {
4694 feagi_npu_neural::synapse::SynapseType::Excitatory
4695 } else {
4696 feagi_npu_neural::synapse::SynapseType::Inhibitory
4697 };
4698
4699 let synapse_idx = npu_lock
4700 .add_synapse(
4701 NeuronId(source_neuron_id as u32),
4702 NeuronId(target_neuron_id as u32),
4703 feagi_npu_neural::types::SynapticWeight(weight),
4704 feagi_npu_neural::types::SynapticPsp(psp),
4705 syn_type,
4706 )
4707 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
4708
4709 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
4710 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
4711
4712 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
4713 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
4714 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
4715 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
4716
4717 if let Some(state_manager) = StateManager::instance().try_read() {
4718 let core_state = state_manager.get_core_state();
4719 core_state.add_synapse_count(1);
4720 if let Some(cortical_id) = source_cortical_id {
4721 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
4722 }
4723 if let Some(cortical_id) = target_cortical_id {
4724 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
4725 }
4726 }
4727
4728 Ok(())
4733 }
4734
4735 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
4738 if let Some(ref npu) = self.npu {
4739 if let Ok(mut npu_lock) = npu.lock() {
4740 let mut psp_uniform_flags = ahash::AHashMap::new();
4742 let mut mp_driven_psp_flags = ahash::AHashMap::new();
4743
4744 for (cortical_id, area) in &self.cortical_areas {
4745 let psp_uniform = area
4747 .get_property("psp_uniform_distribution")
4748 .and_then(|v| v.as_bool())
4749 .unwrap_or(false);
4750 psp_uniform_flags.insert(*cortical_id, psp_uniform);
4751
4752 let mp_driven_psp = area
4754 .get_property("mp_driven_psp")
4755 .and_then(|v| v.as_bool())
4756 .unwrap_or(false);
4757 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
4758 }
4759
4760 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
4762 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
4763
4764 trace!(
4765 target: "feagi-bdu",
4766 "Synchronized cortical area flags to NPU ({} areas)",
4767 self.cortical_areas.len()
4768 );
4769 }
4770 }
4771
4772 Ok(())
4773 }
4774
4775 pub fn get_synapse(
4787 &self,
4788 source_neuron_id: u64,
4789 target_neuron_id: u64,
4790 ) -> Option<(u8, u8, u8)> {
4791 let npu = self.npu.as_ref()?;
4793 let npu_lock = npu.lock().ok()?;
4794
4795 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
4798
4799 for (source_id, weight, psp, synapse_type) in incoming {
4801 if source_id == source_neuron_id as u32 {
4802 return Some((weight, psp, synapse_type));
4803 }
4804 }
4805
4806 None
4807 }
4808
4809 pub fn update_synapse_weight(
4822 &mut self,
4823 source_neuron_id: u64,
4824 target_neuron_id: u64,
4825 new_weight: u8,
4826 ) -> BduResult<()> {
4827 let npu = self
4829 .npu
4830 .as_ref()
4831 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4832
4833 let mut npu_lock = npu
4834 .lock()
4835 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4836
4837 let updated = npu_lock.update_synapse_weight(
4839 NeuronId(source_neuron_id as u32),
4840 NeuronId(target_neuron_id as u32),
4841 feagi_npu_neural::types::SynapticWeight(new_weight),
4842 );
4843
4844 if updated {
4845 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
4846 Ok(())
4847 } else {
4848 Err(BduError::InvalidSynapse(format!(
4849 "Synapse {} -> {} not found",
4850 source_neuron_id, target_neuron_id
4851 )))
4852 }
4853 }
4854
4855 pub fn remove_synapse(
4867 &mut self,
4868 source_neuron_id: u64,
4869 target_neuron_id: u64,
4870 ) -> BduResult<bool> {
4871 let npu = self
4873 .npu
4874 .as_ref()
4875 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4876
4877 let mut npu_lock = npu
4878 .lock()
4879 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4880
4881 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
4882 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
4883 let source_cortical_id = self.cortical_idx_to_id.get(&source_cortical_idx).cloned();
4884 let target_cortical_id = self.cortical_idx_to_id.get(&target_cortical_idx).cloned();
4885
4886 let removed = npu_lock.remove_synapse(
4888 NeuronId(source_neuron_id as u32),
4889 NeuronId(target_neuron_id as u32),
4890 );
4891
4892 if removed {
4893 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
4894
4895 if let Some(state_manager) = StateManager::instance().try_read() {
4897 let core_state = state_manager.get_core_state();
4898 core_state.subtract_synapse_count(1);
4899 if let Some(cortical_id) = source_cortical_id {
4900 state_manager
4901 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
4902 }
4903 if let Some(cortical_id) = target_cortical_id {
4904 state_manager
4905 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
4906 }
4907 }
4908 }
4909
4910 Ok(removed)
4911 }
4912
4913 pub fn batch_create_neurons(
4931 &mut self,
4932 cortical_id: &CorticalID,
4933 neurons: Vec<NeuronData>,
4934 ) -> BduResult<Vec<u64>> {
4935 let npu = self
4937 .npu
4938 .as_ref()
4939 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4940
4941 let mut npu_lock = npu
4942 .lock()
4943 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4944
4945 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
4947 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
4948 })?;
4949 let cortical_idx = area.cortical_idx;
4950
4951 let count = neurons.len();
4952
4953 let mut x_coords = Vec::with_capacity(count);
4955 let mut y_coords = Vec::with_capacity(count);
4956 let mut z_coords = Vec::with_capacity(count);
4957 let mut firing_thresholds = Vec::with_capacity(count);
4958 let mut threshold_limits = Vec::with_capacity(count);
4959 let mut leak_coeffs = Vec::with_capacity(count);
4960 let mut resting_potentials = Vec::with_capacity(count);
4961 let mut neuron_types = Vec::with_capacity(count);
4962 let mut refractory_periods = Vec::with_capacity(count);
4963 let mut excitabilities = Vec::with_capacity(count);
4964 let mut consec_fire_limits = Vec::with_capacity(count);
4965 let mut snooze_lengths = Vec::with_capacity(count);
4966 let mut mp_accums = Vec::with_capacity(count);
4967 let mut cortical_areas = Vec::with_capacity(count);
4968
4969 for (
4970 x,
4971 y,
4972 z,
4973 threshold,
4974 threshold_limit,
4975 leak,
4976 resting,
4977 ntype,
4978 refract,
4979 excit,
4980 consec_limit,
4981 snooze,
4982 mp_accum,
4983 ) in neurons
4984 {
4985 x_coords.push(x);
4986 y_coords.push(y);
4987 z_coords.push(z);
4988 firing_thresholds.push(threshold);
4989 threshold_limits.push(threshold_limit);
4990 leak_coeffs.push(leak);
4991 resting_potentials.push(resting);
4992 neuron_types.push(ntype);
4993 refractory_periods.push(refract);
4994 excitabilities.push(excit);
4995 consec_fire_limits.push(consec_limit);
4996 snooze_lengths.push(snooze);
4997 mp_accums.push(mp_accum);
4998 cortical_areas.push(cortical_idx);
4999 }
5000
5001 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5003
5004 let firing_thresholds_t = firing_thresholds;
5009 let threshold_limits_t = threshold_limits;
5010 let resting_potentials_t = resting_potentials;
5011 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5012 firing_thresholds_t,
5013 threshold_limits_t,
5014 leak_coeffs,
5015 resting_potentials_t,
5016 neuron_types,
5017 refractory_periods,
5018 excitabilities,
5019 consec_fire_limits,
5020 snooze_lengths,
5021 mp_accums,
5022 cortical_areas,
5023 x_coords,
5024 y_coords,
5025 z_coords,
5026 );
5027
5028 let mut neuron_ids = Vec::with_capacity(count);
5030 for i in 0..neurons_created {
5031 neuron_ids.push((first_neuron_id + i) as u64);
5032 }
5033
5034 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5035
5036 if let Some(state_manager) = StateManager::instance().try_read() {
5038 let core_state = state_manager.get_core_state();
5039 core_state.add_neuron_count(neurons_created);
5040 core_state.add_regular_neuron_count(neurons_created);
5041 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5042 }
5043
5044 {
5046 let mut cache = self.cached_neuron_counts_per_area.write();
5047 cache
5048 .entry(*cortical_id)
5049 .or_insert_with(|| AtomicUsize::new(0))
5050 .fetch_add(count, Ordering::Relaxed);
5051 }
5052
5053 Ok(neuron_ids)
5054 }
5055
5056 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5067 let npu = self
5069 .npu
5070 .as_ref()
5071 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5072
5073 let mut npu_lock = npu
5074 .lock()
5075 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5076
5077 let mut deleted_count = 0;
5078 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5079 std::collections::HashMap::new();
5080
5081 for neuron_id in neuron_ids {
5084 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5085 let cortical_id = self.cortical_idx_to_id.get(&cortical_idx).cloned();
5086
5087 if npu_lock.delete_neuron(neuron_id as u32) {
5088 deleted_count += 1;
5089 if let Some(cortical_id) = cortical_id {
5090 let key = cortical_id.as_base_64();
5091 *per_area_deleted.entry(key).or_insert(0) += 1;
5092 }
5093 }
5094 }
5095
5096 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5097
5098 if deleted_count > 0 {
5100 if let Some(state_manager) = StateManager::instance().try_read() {
5101 let core_state = state_manager.get_core_state();
5102 core_state.subtract_neuron_count(deleted_count as u32);
5103 core_state.subtract_regular_neuron_count(deleted_count as u32);
5104 for (cortical_id, count) in per_area_deleted {
5105 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5106 }
5107 }
5108 }
5109
5110 Ok(deleted_count)
5117 }
5118
5119 pub fn update_neuron_properties(
5138 &mut self,
5139 neuron_id: u64,
5140 firing_threshold: Option<f32>,
5141 leak_coefficient: Option<f32>,
5142 resting_potential: Option<f32>,
5143 excitability: Option<f32>,
5144 ) -> BduResult<()> {
5145 let npu = self
5147 .npu
5148 .as_ref()
5149 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5150
5151 let mut npu_lock = npu
5152 .lock()
5153 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5154
5155 let neuron_id_u32 = neuron_id as u32;
5156
5157 let mut updated = false;
5159
5160 if let Some(threshold) = firing_threshold {
5162 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5163 updated = true;
5164 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5165 } else if !updated {
5166 return Err(BduError::InvalidNeuron(format!(
5167 "Neuron {} not found",
5168 neuron_id
5169 )));
5170 }
5171 }
5172
5173 if let Some(leak) = leak_coefficient {
5174 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5175 updated = true;
5176 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5177 } else if !updated {
5178 return Err(BduError::InvalidNeuron(format!(
5179 "Neuron {} not found",
5180 neuron_id
5181 )));
5182 }
5183 }
5184
5185 if let Some(resting) = resting_potential {
5186 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5187 updated = true;
5188 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5189 } else if !updated {
5190 return Err(BduError::InvalidNeuron(format!(
5191 "Neuron {} not found",
5192 neuron_id
5193 )));
5194 }
5195 }
5196
5197 if let Some(excit) = excitability {
5198 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
5199 updated = true;
5200 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
5201 } else if !updated {
5202 return Err(BduError::InvalidNeuron(format!(
5203 "Neuron {} not found",
5204 neuron_id
5205 )));
5206 }
5207 }
5208
5209 if !updated {
5210 return Err(BduError::Internal(
5211 "No properties provided for update".to_string(),
5212 ));
5213 }
5214
5215 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
5216
5217 Ok(())
5218 }
5219
5220 pub fn set_neuron_firing_threshold(
5232 &mut self,
5233 neuron_id: u64,
5234 new_threshold: f32,
5235 ) -> BduResult<()> {
5236 let npu = self
5238 .npu
5239 .as_ref()
5240 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5241
5242 let mut npu_lock = npu
5243 .lock()
5244 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5245
5246 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
5248 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
5249 Ok(())
5250 } else {
5251 Err(BduError::InvalidNeuron(format!(
5252 "Neuron {} not found",
5253 neuron_id
5254 )))
5255 }
5256 }
5257
5258 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
5273 self.cortical_areas
5274 .values()
5275 .find(|area| area.name == name)
5276 .cloned()
5277 }
5278
5279 pub fn resize_cortical_area(
5296 &mut self,
5297 cortical_id: &CorticalID,
5298 new_dimensions: CorticalAreaDimensions,
5299 ) -> BduResult<()> {
5300 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
5302 return Err(BduError::InvalidArea(format!(
5303 "Invalid dimensions: {:?} (all must be > 0)",
5304 new_dimensions
5305 )));
5306 }
5307
5308 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
5310 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5311 })?;
5312
5313 let old_dimensions = area.dimensions;
5314 area.dimensions = new_dimensions;
5315
5316 info!(target: "feagi-bdu",
5320 "Resized cortical area {} from {:?} to {:?}",
5321 cortical_id,
5322 old_dimensions,
5323 new_dimensions
5324 );
5325
5326 self.refresh_cortical_area_hashes(false, true);
5327
5328 Ok(())
5329 }
5330
5331 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
5342 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
5343 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5344 })?;
5345
5346 Ok(region
5348 .cortical_areas
5349 .iter()
5350 .map(|id| id.as_base_64())
5351 .collect())
5352 }
5353
5354 pub fn update_brain_region(
5367 &mut self,
5368 region_id: &str,
5369 new_name: Option<String>,
5370 new_description: Option<String>,
5371 ) -> BduResult<()> {
5372 let region = self
5373 .brain_regions
5374 .get_region_mut(region_id)
5375 .ok_or_else(|| {
5376 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5377 })?;
5378
5379 if let Some(name) = new_name {
5380 region.name = name;
5381 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
5382 }
5383
5384 if let Some(desc) = new_description {
5385 region
5387 .properties
5388 .insert("description".to_string(), serde_json::json!(desc));
5389 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
5390 }
5391
5392 info!(target: "feagi-bdu","Updated brain region {}", region_id);
5393
5394 self.refresh_brain_regions_hash();
5395
5396 Ok(())
5397 }
5398
5399 pub fn update_brain_region_properties(
5413 &mut self,
5414 region_id: &str,
5415 properties: std::collections::HashMap<String, serde_json::Value>,
5416 ) -> BduResult<()> {
5417 use tracing::{debug, info};
5418
5419 let region = self
5420 .brain_regions
5421 .get_region_mut(region_id)
5422 .ok_or_else(|| {
5423 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5424 })?;
5425
5426 for (key, value) in properties {
5427 match key.as_str() {
5428 "title" | "name" => {
5429 if let Some(name) = value.as_str() {
5430 region.name = name.to_string();
5431 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
5432 }
5433 }
5434 "coordinate_3d" | "coordinates_3d" => {
5435 region
5436 .properties
5437 .insert("coordinate_3d".to_string(), value.clone());
5438 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
5439 }
5440 "coordinate_2d" | "coordinates_2d" => {
5441 region
5442 .properties
5443 .insert("coordinate_2d".to_string(), value.clone());
5444 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
5445 }
5446 "description" => {
5447 region
5448 .properties
5449 .insert("description".to_string(), value.clone());
5450 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
5451 }
5452 "region_type" => {
5453 if let Some(type_str) = value.as_str() {
5454 region.region_type = feagi_structures::genomic::RegionType::Undefined;
5457 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
5458 }
5459 }
5460 _ => {
5462 region.properties.insert(key.clone(), value.clone());
5463 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
5464 }
5465 }
5466 }
5467
5468 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
5469
5470 Ok(())
5471 }
5472
5473 pub fn get_neuron_by_coordinates(
5491 &self,
5492 cortical_id: &CorticalID,
5493 x: u32,
5494 y: u32,
5495 z: u32,
5496 ) -> Option<u64> {
5497 let area = self.get_cortical_area(cortical_id)?;
5499 let cortical_idx = area.cortical_idx;
5500
5501 let npu = self.npu.as_ref()?;
5503 let npu_lock = npu.lock().ok()?;
5504
5505 npu_lock
5506 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
5507 .map(|id| id as u64)
5508 }
5509
5510 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
5521 let npu = self.npu.as_ref()?;
5522 let npu_lock = npu.lock().ok()?;
5523
5524 let neuron_count = npu_lock.get_neuron_count();
5526 if (neuron_id as usize) >= neuron_count {
5527 return None;
5528 }
5529
5530 Some(
5531 npu_lock
5532 .get_neuron_coordinates(neuron_id as u32)
5533 .unwrap_or((0, 0, 0)),
5534 )
5535 }
5536
5537 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
5548 let npu = self.npu.as_ref()?;
5549 let npu_lock = npu.lock().ok()?;
5550
5551 let neuron_count = npu_lock.get_neuron_count();
5553 if (neuron_id as usize) >= neuron_count {
5554 return None;
5555 }
5556
5557 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5558
5559 self.cortical_areas
5561 .values()
5562 .find(|area| area.cortical_idx == cortical_idx)
5563 .map(|area| area.cortical_id)
5564 }
5565
5566 pub fn get_neuron_properties(
5577 &self,
5578 neuron_id: u64,
5579 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5580 let npu = self.npu.as_ref()?;
5581 let npu_lock = npu.lock().ok()?;
5582
5583 let neuron_id_u32 = neuron_id as u32;
5584 let idx = neuron_id as usize;
5585
5586 let neuron_count = npu_lock.get_neuron_count();
5588 if idx >= neuron_count {
5589 return None;
5590 }
5591
5592 let mut properties = std::collections::HashMap::new();
5593
5594 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
5596
5597 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
5599 properties.insert("x".to_string(), serde_json::json!(x));
5600 properties.insert("y".to_string(), serde_json::json!(y));
5601 properties.insert("z".to_string(), serde_json::json!(z));
5602
5603 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32);
5605 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
5606
5607 if let Some((consec_count, consec_limit, snooze, mp, threshold, refract_countdown)) =
5609 npu_lock.get_neuron_state(NeuronId(neuron_id_u32))
5610 {
5611 properties.insert(
5612 "consecutive_fire_count".to_string(),
5613 serde_json::json!(consec_count),
5614 );
5615 properties.insert(
5616 "consecutive_fire_limit".to_string(),
5617 serde_json::json!(consec_limit),
5618 );
5619 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
5620 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
5621 properties.insert("threshold".to_string(), serde_json::json!(threshold));
5622 properties.insert(
5623 "refractory_countdown".to_string(),
5624 serde_json::json!(refract_countdown),
5625 );
5626 }
5627
5628 if let Some(leak) = npu_lock.get_neuron_property_by_index(idx, "leak_coefficient") {
5630 properties.insert("leak_coefficient".to_string(), serde_json::json!(leak));
5631 }
5632 if let Some(resting) = npu_lock.get_neuron_property_by_index(idx, "resting_potential") {
5633 properties.insert("resting_potential".to_string(), serde_json::json!(resting));
5634 }
5635 if let Some(excit) = npu_lock.get_neuron_property_by_index(idx, "excitability") {
5636 properties.insert("excitability".to_string(), serde_json::json!(excit));
5637 }
5638 if let Some(threshold_limit) = npu_lock.get_neuron_property_by_index(idx, "threshold_limit")
5639 {
5640 properties.insert(
5641 "threshold_limit".to_string(),
5642 serde_json::json!(threshold_limit),
5643 );
5644 }
5645
5646 if let Some(refract_period) =
5648 npu_lock.get_neuron_property_u16_by_index(idx, "refractory_period")
5649 {
5650 properties.insert(
5651 "refractory_period".to_string(),
5652 serde_json::json!(refract_period),
5653 );
5654 }
5655
5656 Some(properties)
5657 }
5658
5659 pub fn get_neuron_property(
5671 &self,
5672 neuron_id: u64,
5673 property_name: &str,
5674 ) -> Option<serde_json::Value> {
5675 self.get_neuron_properties(neuron_id)?
5676 .get(property_name)
5677 .cloned()
5678 }
5679
5680 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
5691 self.cortical_areas.keys().copied().collect()
5692 }
5693
5694 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
5701 self.cortical_areas
5702 .values()
5703 .map(|area| area.cortical_idx)
5704 .collect()
5705 }
5706
5707 pub fn get_cortical_area_names(&self) -> Vec<String> {
5714 self.cortical_areas
5715 .values()
5716 .map(|area| area.name.clone())
5717 .collect()
5718 }
5719
5720 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
5727 use crate::models::CorticalAreaExt;
5728 self.cortical_areas
5729 .values()
5730 .filter(|area| area.is_input_area())
5731 .map(|area| area.cortical_id)
5732 .collect()
5733 }
5734
5735 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
5742 use crate::models::CorticalAreaExt;
5743 self.cortical_areas
5744 .values()
5745 .filter(|area| area.is_output_area())
5746 .map(|area| area.cortical_id)
5747 .collect()
5748 }
5749
5750 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
5757 self.cortical_areas
5758 .values()
5759 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
5760 (
5761 max_w.max(area.dimensions.width as usize),
5762 max_h.max(area.dimensions.height as usize),
5763 max_d.max(area.dimensions.depth as usize),
5764 )
5765 })
5766 }
5767
5768 pub fn get_cortical_area_properties(
5779 &self,
5780 cortical_id: &CorticalID,
5781 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5782 let area = self.get_cortical_area(cortical_id)?;
5783
5784 let mut properties = std::collections::HashMap::new();
5785 properties.insert(
5786 "cortical_id".to_string(),
5787 serde_json::json!(area.cortical_id),
5788 );
5789 properties.insert(
5790 "cortical_id_s".to_string(),
5791 serde_json::json!(area.cortical_id.to_string()),
5792 );
5793 properties.insert(
5794 "cortical_idx".to_string(),
5795 serde_json::json!(area.cortical_idx),
5796 );
5797 properties.insert("name".to_string(), serde_json::json!(area.name));
5798 use crate::models::CorticalAreaExt;
5799 properties.insert(
5800 "area_type".to_string(),
5801 serde_json::json!(area.get_cortical_group()),
5802 );
5803 properties.insert(
5804 "dimensions".to_string(),
5805 serde_json::json!({
5806 "width": area.dimensions.width,
5807 "height": area.dimensions.height,
5808 "depth": area.dimensions.depth,
5809 }),
5810 );
5811 properties.insert("position".to_string(), serde_json::json!(area.position));
5812
5813 for (key, value) in &area.properties {
5815 properties.insert(key.clone(), value.clone());
5816 }
5817
5818 properties.extend(area.properties.clone());
5820
5821 Some(properties)
5822 }
5823
5824 pub fn get_all_cortical_area_properties(
5831 &self,
5832 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
5833 self.cortical_areas
5834 .keys()
5835 .filter_map(|id| self.get_cortical_area_properties(id))
5836 .collect()
5837 }
5838
5839 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
5850 self.brain_regions
5851 .get_all_region_ids()
5852 .into_iter()
5853 .cloned()
5854 .collect()
5855 }
5856
5857 pub fn get_brain_region_names(&self) -> Vec<String> {
5864 self.brain_regions
5865 .get_all_region_ids()
5866 .iter()
5867 .filter_map(|id| {
5868 self.brain_regions
5869 .get_region(id)
5870 .map(|region| region.name.clone())
5871 })
5872 .collect()
5873 }
5874
5875 pub fn get_brain_region_properties(
5886 &self,
5887 region_id: &str,
5888 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5889 let region = self.brain_regions.get_region(region_id)?;
5890
5891 let mut properties = std::collections::HashMap::new();
5892 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
5893 properties.insert("name".to_string(), serde_json::json!(region.name));
5894 properties.insert(
5895 "region_type".to_string(),
5896 serde_json::json!(format!("{:?}", region.region_type)),
5897 );
5898 properties.insert(
5899 "cortical_areas".to_string(),
5900 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
5901 );
5902
5903 properties.extend(region.properties.clone());
5905
5906 Some(properties)
5907 }
5908
5909 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
5920 self.cortical_areas.contains_key(cortical_id)
5921 }
5922
5923 pub fn brain_region_exists(&self, region_id: &str) -> bool {
5934 self.brain_regions.get_region(region_id).is_some()
5935 }
5936
5937 pub fn get_brain_region_count(&self) -> usize {
5944 self.brain_regions.region_count()
5945 }
5946
5947 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
5958 self.get_neurons_in_area(cortical_id)
5962 }
5963}
5964
5965impl std::fmt::Debug for ConnectomeManager {
5967 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5968 f.debug_struct("ConnectomeManager")
5969 .field("cortical_areas", &self.cortical_areas.len())
5970 .field("next_cortical_idx", &self.next_cortical_idx)
5971 .field("brain_regions", &self.brain_regions)
5972 .field(
5973 "npu",
5974 &if self.npu.is_some() {
5975 "Connected"
5976 } else {
5977 "Not connected"
5978 },
5979 )
5980 .field("initialized", &self.initialized)
5981 .finish()
5982 }
5983}
5984
5985#[cfg(test)]
5986mod tests {
5987 use super::*;
5988 use feagi_structures::genomic::cortical_area::CoreCorticalType;
5989
5990 #[test]
5991 fn test_singleton_instance() {
5992 let instance1 = ConnectomeManager::instance();
5993 let instance2 = ConnectomeManager::instance();
5994
5995 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
5997 }
5998
5999 #[test]
6000 fn test_add_cortical_area() {
6001 ConnectomeManager::reset_for_testing();
6002
6003 let instance = ConnectomeManager::instance();
6004 let mut manager = instance.write();
6005
6006 use feagi_structures::genomic::cortical_area::{
6007 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6008 };
6009 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6011 let area = CorticalArea::new(
6012 cortical_id,
6013 0,
6014 "Visual Input".to_string(),
6015 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6016 (0, 0, 0).into(),
6017 cortical_type,
6018 )
6019 .unwrap();
6020
6021 let initial_count = manager.get_cortical_area_count();
6022 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6023
6024 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6025 assert!(manager.has_cortical_area(&cortical_id));
6026 assert!(manager.is_initialized());
6027 }
6028
6029 #[test]
6030 fn test_cortical_area_lookups() {
6031 ConnectomeManager::reset_for_testing();
6032
6033 let instance = ConnectomeManager::instance();
6034 let mut manager = instance.write();
6035
6036 use feagi_structures::genomic::cortical_area::{
6037 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6038 };
6039 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6041 let area = CorticalArea::new(
6042 cortical_id,
6043 0,
6044 "Test Area".to_string(),
6045 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6046 (0, 0, 0).into(),
6047 cortical_type,
6048 )
6049 .unwrap();
6050
6051 let cortical_idx = manager.add_cortical_area(area).unwrap();
6052
6053 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6055
6056 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6058
6059 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6061 assert_eq!(retrieved_area.name, "Test Area");
6062 }
6063
6064 #[test]
6065 fn test_remove_cortical_area() {
6066 ConnectomeManager::reset_for_testing();
6067
6068 let instance = ConnectomeManager::instance();
6069 let mut manager = instance.write();
6070
6071 use feagi_structures::genomic::cortical_area::{
6072 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6073 };
6074 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6075
6076 if manager.has_cortical_area(&cortical_id) {
6078 manager.remove_cortical_area(&cortical_id).unwrap();
6079 }
6080
6081 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6082 let area = CorticalArea::new(
6083 cortical_id,
6084 0,
6085 "Test".to_string(),
6086 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6087 (0, 0, 0).into(),
6088 cortical_type,
6089 )
6090 .unwrap();
6091
6092 let initial_count = manager.get_cortical_area_count();
6093 manager.add_cortical_area(area).unwrap();
6094 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6095
6096 manager.remove_cortical_area(&cortical_id).unwrap();
6097 assert_eq!(manager.get_cortical_area_count(), initial_count);
6098 assert!(!manager.has_cortical_area(&cortical_id));
6099 }
6100
6101 #[test]
6102 fn test_duplicate_area_error() {
6103 ConnectomeManager::reset_for_testing();
6104
6105 let instance = ConnectomeManager::instance();
6106 let mut manager = instance.write();
6107
6108 use feagi_structures::genomic::cortical_area::{
6109 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6110 };
6111 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
6114 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6115 let area1 = CorticalArea::new(
6116 cortical_id,
6117 0,
6118 "First".to_string(),
6119 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6120 (0, 0, 0).into(),
6121 cortical_type,
6122 )
6123 .unwrap();
6124
6125 let area2 = CorticalArea::new(
6126 cortical_id, 1,
6128 "Second".to_string(),
6129 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6130 (0, 0, 0).into(),
6131 cortical_type,
6132 )
6133 .unwrap();
6134
6135 manager.add_cortical_area(area1).unwrap();
6136 let result = manager.add_cortical_area(area2);
6137
6138 assert!(result.is_err());
6139 }
6140
6141 #[test]
6142 fn test_brain_region_management() {
6143 ConnectomeManager::reset_for_testing();
6144
6145 let instance = ConnectomeManager::instance();
6146 let mut manager = instance.write();
6147
6148 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
6149 let region_id_str = region_id.to_string();
6150 let root = BrainRegion::new(
6151 region_id,
6152 "Root".to_string(),
6153 feagi_structures::genomic::brain_regions::RegionType::Undefined,
6154 )
6155 .unwrap();
6156
6157 let initial_count = manager.get_brain_region_ids().len();
6158 manager.add_brain_region(root, None).unwrap();
6159
6160 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
6161 assert!(manager.get_brain_region(®ion_id_str).is_some());
6162 }
6163
6164 #[test]
6165 fn test_synapse_operations() {
6166 use feagi_npu_burst_engine::npu::RustNPU;
6167 use feagi_npu_burst_engine::TracingMutex;
6168 use std::sync::Arc;
6169
6170 use feagi_npu_burst_engine::backend::CPUBackend;
6172 use feagi_npu_burst_engine::DynamicNPU;
6173 use feagi_npu_runtime::StdRuntime;
6174
6175 let runtime = StdRuntime;
6176 let backend = CPUBackend::new();
6177 let npu_result =
6178 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
6179 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
6180 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
6181
6182 use feagi_structures::genomic::cortical_area::{
6184 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6185 };
6186 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6188 let area = CorticalArea::new(
6189 cortical_id,
6190 0, "Test Area".to_string(),
6192 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
6193 (0, 0, 0).into(), cortical_type,
6195 )
6196 .unwrap();
6197 let cortical_idx = manager.add_cortical_area(area).unwrap();
6198
6199 if let Some(npu_arc) = manager.get_npu() {
6201 if let Ok(mut npu_guard) = npu_arc.try_lock() {
6202 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
6203 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
6204 }
6205 }
6206 }
6207
6208 let neuron1_id = manager
6210 .add_neuron(
6211 &cortical_id,
6212 0,
6213 0,
6214 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
6226 .unwrap();
6227
6228 let neuron2_id = manager
6229 .add_neuron(
6230 &cortical_id,
6231 1,
6232 0,
6233 0, 100.0,
6235 f32::MAX, 0.1,
6237 -60.0,
6238 0,
6239 2,
6240 1.0,
6241 5,
6242 10,
6243 false,
6244 )
6245 .unwrap();
6246
6247 manager
6249 .create_synapse(
6250 neuron1_id, neuron2_id, 128, 64, 0, )
6254 .unwrap();
6255
6256 println!("✅ Synapse creation test passed");
6259 }
6260
6261 #[test]
6262 fn test_apply_cortical_mapping_missing_rules_is_ok() {
6263 let mut manager = ConnectomeManager::new_for_testing();
6266
6267 use feagi_structures::genomic::cortical_area::{
6268 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6269 };
6270
6271 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
6272 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
6273
6274 let src_area = CorticalArea::new(
6275 src_id,
6276 0,
6277 "src".to_string(),
6278 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6279 (0, 0, 0).into(),
6280 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6281 )
6282 .unwrap();
6283
6284 let dst_area = CorticalArea::new(
6285 dst_id,
6286 1,
6287 "dst".to_string(),
6288 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6289 (0, 0, 0).into(),
6290 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6291 )
6292 .unwrap();
6293
6294 manager.add_cortical_area(src_area).unwrap();
6295 manager.add_cortical_area(dst_area).unwrap();
6296
6297 let count = manager
6299 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6300 .unwrap();
6301 assert_eq!(count, 0);
6302
6303 manager
6305 .update_cortical_mapping(
6306 &src_id,
6307 &dst_id,
6308 vec![serde_json::json!({"morphology_id":"m1"})],
6309 )
6310 .unwrap();
6311 manager
6312 .update_cortical_mapping(&src_id, &dst_id, vec![])
6313 .unwrap();
6314
6315 let count2 = manager
6316 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6317 .unwrap();
6318 assert_eq!(count2, 0);
6319 }
6320
6321 #[test]
6322 fn test_mapping_deletion_prunes_synapses_between_areas() {
6323 use feagi_npu_burst_engine::backend::CPUBackend;
6324 use feagi_npu_burst_engine::RustNPU;
6325 use feagi_npu_burst_engine::TracingMutex;
6326 use feagi_npu_runtime::StdRuntime;
6327 use feagi_structures::genomic::cortical_area::{
6328 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6329 };
6330 use std::sync::Arc;
6331
6332 let runtime = StdRuntime;
6334 let backend = CPUBackend::new();
6335 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6336 let dyn_npu = Arc::new(TracingMutex::new(
6337 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6338 "TestNPU",
6339 ));
6340 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6341
6342 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
6344 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
6345
6346 let src_area = CorticalArea::new(
6347 src_id,
6348 0,
6349 "src".to_string(),
6350 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6351 (0, 0, 0).into(),
6352 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6353 )
6354 .unwrap();
6355 let dst_area = CorticalArea::new(
6356 dst_id,
6357 1,
6358 "dst".to_string(),
6359 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6360 (0, 0, 0).into(),
6361 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6362 )
6363 .unwrap();
6364
6365 manager.add_cortical_area(src_area).unwrap();
6366 manager.add_cortical_area(dst_area).unwrap();
6367
6368 let s0 = manager
6370 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6371 .unwrap();
6372 let s1 = manager
6373 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6374 .unwrap();
6375 let t0 = manager
6376 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6377 .unwrap();
6378 let t1 = manager
6379 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6380 .unwrap();
6381
6382 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6384 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6385
6386 {
6388 let mut npu = dyn_npu.lock().unwrap();
6389 npu.rebuild_synapse_index();
6390 assert_eq!(npu.get_synapse_count(), 2);
6391 }
6392
6393 manager
6395 .update_cortical_mapping(&src_id, &dst_id, vec![])
6396 .unwrap();
6397 let created = manager
6398 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6399 .unwrap();
6400 assert_eq!(created, 0);
6401
6402 {
6404 let mut npu = dyn_npu.lock().unwrap();
6405 npu.rebuild_synapse_index();
6407 assert_eq!(npu.get_synapse_count(), 0);
6408 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6409 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6410 }
6411 }
6412
6413 #[test]
6414 fn test_mapping_update_prunes_synapses_between_areas() {
6415 use feagi_npu_burst_engine::backend::CPUBackend;
6416 use feagi_npu_burst_engine::RustNPU;
6417 use feagi_npu_burst_engine::TracingMutex;
6418 use feagi_npu_runtime::StdRuntime;
6419 use feagi_structures::genomic::cortical_area::{
6420 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6421 };
6422 use std::sync::Arc;
6423
6424 let runtime = StdRuntime;
6426 let backend = CPUBackend::new();
6427 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6428 let dyn_npu = Arc::new(TracingMutex::new(
6429 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6430 "TestNPU",
6431 ));
6432 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6433
6434 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6436
6437 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
6440 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
6441
6442 let src_area = CorticalArea::new(
6443 src_id,
6444 0,
6445 "src".to_string(),
6446 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6447 (0, 0, 0).into(),
6448 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6449 )
6450 .unwrap();
6451 let dst_area = CorticalArea::new(
6452 dst_id,
6453 0,
6454 "dst".to_string(),
6455 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6456 (0, 0, 0).into(),
6457 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6458 )
6459 .unwrap();
6460
6461 manager.add_cortical_area(src_area).unwrap();
6462 manager.add_cortical_area(dst_area).unwrap();
6463
6464 let s0 = manager
6466 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6467 .unwrap();
6468 let s1 = manager
6469 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6470 .unwrap();
6471 let t0 = manager
6472 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6473 .unwrap();
6474 let t1 = manager
6475 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6476 .unwrap();
6477
6478 manager.create_synapse(s0, t0, 128, 200, 0).unwrap();
6480 manager.create_synapse(s1, t1, 128, 200, 0).unwrap();
6481
6482 {
6484 let mut npu = dyn_npu.lock().unwrap();
6485 npu.rebuild_synapse_index();
6486 assert_eq!(npu.get_synapse_count(), 2);
6487 }
6488
6489 manager
6495 .update_cortical_mapping(
6496 &src_id,
6497 &dst_id,
6498 vec![serde_json::json!({
6499 "morphology_id": "episodic_memory",
6500 "morphology_scalar": [1],
6501 "postSynapticCurrent_multiplier": 1,
6502 "plasticity_flag": false,
6503 "plasticity_constant": 0,
6504 "ltp_multiplier": 0,
6505 "ltd_multiplier": 0,
6506 "plasticity_window": 0,
6507 })],
6508 )
6509 .unwrap();
6510 let created = manager
6511 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6512 .unwrap();
6513 assert_eq!(created, 0);
6514
6515 {
6517 let mut npu = dyn_npu.lock().unwrap();
6518 npu.rebuild_synapse_index();
6520 assert_eq!(npu.get_synapse_count(), 0);
6521 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6522 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6523 }
6524 }
6525
6526 #[test]
6527 fn test_upstream_area_tracking() {
6528 use crate::models::cortical_area::CorticalArea;
6530 use feagi_npu_burst_engine::backend::CPUBackend;
6531 use feagi_npu_burst_engine::TracingMutex;
6532 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6533 use feagi_npu_runtime::StdRuntime;
6534 use feagi_structures::genomic::cortical_area::{
6535 CorticalAreaDimensions, CorticalAreaType, CorticalID,
6536 };
6537
6538 let runtime = StdRuntime;
6540 let backend = CPUBackend::new();
6541 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6542 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6543 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6544
6545 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6548
6549 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
6551 let src_area = CorticalArea::new(
6552 src_id,
6553 0,
6554 "Source Area".to_string(),
6555 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6556 (0, 0, 0).into(),
6557 CorticalAreaType::Custom(
6558 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6559 ),
6560 )
6561 .unwrap();
6562 let src_idx = manager.add_cortical_area(src_area).unwrap();
6563
6564 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
6566 let dst_area = CorticalArea::new(
6567 dst_id,
6568 0,
6569 "Dest Area".to_string(),
6570 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6571 (0, 0, 0).into(),
6572 CorticalAreaType::Custom(
6573 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6574 ),
6575 )
6576 .unwrap();
6577 manager.add_cortical_area(dst_area).unwrap();
6578
6579 {
6581 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
6582 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
6583 assert!(
6584 upstream.as_array().unwrap().is_empty(),
6585 "Upstream areas should be empty initially"
6586 );
6587 }
6588
6589 let mapping_data = vec![serde_json::json!({
6591 "morphology_id": "episodic_memory",
6592 "morphology_scalar": 1,
6593 "postSynapticCurrent_multiplier": 1.0,
6594 })];
6595 manager
6596 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
6597 .unwrap();
6598 manager
6599 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6600 .unwrap();
6601
6602 {
6604 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6605 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
6606 assert_eq!(
6607 upstream_areas[0], src_idx,
6608 "Upstream area should be src_idx"
6609 );
6610 }
6611
6612 manager
6614 .update_cortical_mapping(&src_id, &dst_id, vec![])
6615 .unwrap();
6616 manager
6617 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6618 .unwrap();
6619
6620 {
6622 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6623 assert_eq!(
6624 upstream_areas.len(),
6625 0,
6626 "Should have 0 upstream areas after deletion"
6627 );
6628 }
6629 }
6630
6631 #[test]
6632 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
6633 use crate::models::cortical_area::CorticalArea;
6634 use feagi_npu_burst_engine::backend::CPUBackend;
6635 use feagi_npu_burst_engine::TracingMutex;
6636 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6637 use feagi_npu_runtime::StdRuntime;
6638 use feagi_structures::genomic::cortical_area::{
6639 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
6640 };
6641 use std::sync::Arc;
6642
6643 let runtime = StdRuntime;
6644 let backend = CPUBackend::new();
6645 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6646 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6647 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6648 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6649
6650 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6651 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
6652 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
6653 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
6654
6655 let a1_area = CorticalArea::new(
6656 a1_id,
6657 0,
6658 "A1".to_string(),
6659 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6660 (0, 0, 0).into(),
6661 CorticalAreaType::Custom(
6662 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6663 ),
6664 )
6665 .unwrap();
6666 let a2_area = CorticalArea::new(
6667 a2_id,
6668 0,
6669 "A2".to_string(),
6670 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6671 (0, 0, 0).into(),
6672 CorticalAreaType::Custom(
6673 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6674 ),
6675 )
6676 .unwrap();
6677
6678 let mut m1_area = CorticalArea::new(
6679 m1_id,
6680 0,
6681 "M1".to_string(),
6682 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6683 (0, 0, 0).into(),
6684 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6685 )
6686 .unwrap();
6687 m1_area
6688 .properties
6689 .insert("is_mem_type".to_string(), serde_json::json!(true));
6690 m1_area
6691 .properties
6692 .insert("temporal_depth".to_string(), serde_json::json!(1));
6693
6694 let mut m2_area = CorticalArea::new(
6695 m2_id,
6696 0,
6697 "M2".to_string(),
6698 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6699 (0, 0, 0).into(),
6700 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6701 )
6702 .unwrap();
6703 m2_area
6704 .properties
6705 .insert("is_mem_type".to_string(), serde_json::json!(true));
6706 m2_area
6707 .properties
6708 .insert("temporal_depth".to_string(), serde_json::json!(1));
6709
6710 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
6711 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
6712 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
6713 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
6714
6715 manager
6716 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6717 .unwrap();
6718 manager
6719 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6720 .unwrap();
6721
6722 let episodic_mapping = vec![serde_json::json!({
6723 "morphology_id": "episodic_memory",
6724 "morphology_scalar": 1,
6725 "postSynapticCurrent_multiplier": 1.0,
6726 })];
6727 manager
6728 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
6729 .unwrap();
6730 manager
6731 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
6732 .unwrap();
6733 manager
6734 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
6735 .unwrap();
6736 manager
6737 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
6738 .unwrap();
6739
6740 let assoc_mapping = vec![serde_json::json!({
6741 "morphology_id": "associative_memory",
6742 "morphology_scalar": 1,
6743 "postSynapticCurrent_multiplier": 1.0,
6744 "plasticity_flag": true,
6745 "plasticity_constant": 1,
6746 "ltp_multiplier": 1,
6747 "ltd_multiplier": 1,
6748 "plasticity_window": 5,
6749 })];
6750 manager
6751 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping)
6752 .unwrap();
6753 manager
6754 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
6755 .unwrap();
6756
6757 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6758 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6759 assert_eq!(
6760 upstream_m1.len(),
6761 1,
6762 "M1 should have only 1 upstream before refresh"
6763 );
6764 assert_eq!(
6765 upstream_m2.len(),
6766 2,
6767 "M2 should have 2 upstreams before refresh"
6768 );
6769
6770 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
6771 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
6772
6773 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6774 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6775 assert_eq!(
6776 upstream_m1.len(),
6777 2,
6778 "M1 should have 2 upstreams after refresh"
6779 );
6780 assert_eq!(
6781 upstream_m2.len(),
6782 2,
6783 "M2 should have 2 upstreams after refresh"
6784 );
6785 assert!(upstream_m1.contains(&a1_idx));
6786 assert!(upstream_m1.contains(&m2_idx));
6787 assert!(upstream_m2.contains(&a2_idx));
6788 assert!(upstream_m2.contains(&m1_idx));
6789
6790 {
6792 let mut npu_lock = dyn_npu.lock().unwrap();
6793 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
6794 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
6795 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
6796 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
6797 npu_lock.process_burst().expect("Burst processing failed");
6798 }
6799
6800 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
6801 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
6802 assert_eq!(
6803 upstream_m1.len(),
6804 2,
6805 "M1 should keep 2 upstreams after firing"
6806 );
6807 assert_eq!(
6808 upstream_m2.len(),
6809 2,
6810 "M2 should keep 2 upstreams after firing"
6811 );
6812 }
6813
6814 #[test]
6815 fn test_memory_twin_created_for_memory_mapping() {
6816 use crate::models::cortical_area::CorticalArea;
6817 use feagi_npu_burst_engine::backend::CPUBackend;
6818 use feagi_npu_burst_engine::TracingMutex;
6819 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6820 use feagi_npu_runtime::StdRuntime;
6821 use feagi_structures::genomic::cortical_area::{
6822 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
6823 MemoryCorticalType,
6824 };
6825 use std::sync::Arc;
6826
6827 let runtime = StdRuntime;
6828 let backend = CPUBackend::new();
6829 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6830 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6831 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6832 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6833
6834 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
6835 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
6836
6837 let src_area = CorticalArea::new(
6838 src_id,
6839 0,
6840 "Source Area".to_string(),
6841 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6842 (0, 0, 0).into(),
6843 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6844 )
6845 .unwrap();
6846 let mut dst_area = CorticalArea::new(
6847 dst_id,
6848 0,
6849 "Memory Area".to_string(),
6850 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6851 (0, 0, 0).into(),
6852 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6853 )
6854 .unwrap();
6855 dst_area
6856 .properties
6857 .insert("is_mem_type".to_string(), serde_json::json!(true));
6858 dst_area
6859 .properties
6860 .insert("temporal_depth".to_string(), serde_json::json!(1));
6861
6862 manager.add_cortical_area(src_area).unwrap();
6863 manager.add_cortical_area(dst_area).unwrap();
6864
6865 let mapping_data = vec![serde_json::json!({
6866 "morphology_id": "episodic_memory",
6867 "morphology_scalar": 1,
6868 "postSynapticCurrent_multiplier": 1.0,
6869 })];
6870 manager
6871 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
6872 .unwrap();
6873 manager
6874 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6875 .unwrap();
6876
6877 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
6878 let twin_map = memory_area
6879 .properties
6880 .get("memory_twin_areas")
6881 .and_then(|v| v.as_object())
6882 .expect("memory_twin_areas should be set");
6883 let twin_id_str = twin_map
6884 .get(&src_id.as_base_64())
6885 .and_then(|v| v.as_str())
6886 .expect("Missing twin entry for upstream area");
6887 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
6888 let mapping = memory_area
6889 .properties
6890 .get("cortical_mapping_dst")
6891 .and_then(|v| v.as_object())
6892 .and_then(|map| map.get(&twin_id.as_base_64()))
6893 .and_then(|v| v.as_array())
6894 .expect("Missing memory replay mapping for twin area");
6895 let uses_replay = mapping.iter().any(|rule| {
6896 rule.get("morphology_id")
6897 .and_then(|v| v.as_str())
6898 .is_some_and(|id| id == "memory_replay")
6899 });
6900 assert!(uses_replay, "Expected memory_replay mapping for twin area");
6901
6902 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
6903 assert!(matches!(
6904 twin_area.cortical_type,
6905 CorticalAreaType::Custom(_)
6906 ));
6907 assert_eq!(
6908 twin_area
6909 .properties
6910 .get("memory_twin_of")
6911 .and_then(|v| v.as_str()),
6912 Some(src_id.as_base_64().as_str())
6913 );
6914 assert_eq!(
6915 twin_area
6916 .properties
6917 .get("memory_twin_for")
6918 .and_then(|v| v.as_str()),
6919 Some(dst_id.as_base_64().as_str())
6920 );
6921 }
6922
6923 #[test]
6924 fn test_associative_memory_between_memory_areas_creates_synapses() {
6925 use crate::models::cortical_area::CorticalArea;
6926 use feagi_npu_burst_engine::backend::CPUBackend;
6927 use feagi_npu_burst_engine::TracingMutex;
6928 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6929 use feagi_npu_runtime::StdRuntime;
6930 use feagi_structures::genomic::cortical_area::{
6931 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
6932 };
6933 use std::sync::Arc;
6934
6935 let runtime = StdRuntime;
6936 let backend = CPUBackend::new();
6937 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6938 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6939 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6940 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6941
6942 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
6943 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
6944
6945 let mut m1_area = CorticalArea::new(
6946 m1_id,
6947 0,
6948 "Memory M1".to_string(),
6949 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6950 (0, 0, 0).into(),
6951 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6952 )
6953 .unwrap();
6954 m1_area
6955 .properties
6956 .insert("is_mem_type".to_string(), serde_json::json!(true));
6957 m1_area
6958 .properties
6959 .insert("temporal_depth".to_string(), serde_json::json!(1));
6960
6961 let mut m2_area = CorticalArea::new(
6962 m2_id,
6963 0,
6964 "Memory M2".to_string(),
6965 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6966 (0, 0, 0).into(),
6967 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6968 )
6969 .unwrap();
6970 m2_area
6971 .properties
6972 .insert("is_mem_type".to_string(), serde_json::json!(true));
6973 m2_area
6974 .properties
6975 .insert("temporal_depth".to_string(), serde_json::json!(1));
6976
6977 manager.add_cortical_area(m1_area).unwrap();
6978 manager.add_cortical_area(m2_area).unwrap();
6979
6980 manager
6981 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6982 .unwrap();
6983 manager
6984 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6985 .unwrap();
6986
6987 let mapping_data = vec![serde_json::json!({
6988 "morphology_id": "associative_memory",
6989 "morphology_scalar": 1,
6990 "postSynapticCurrent_multiplier": 1.0,
6991 "plasticity_flag": true,
6992 "plasticity_constant": 1,
6993 "ltp_multiplier": 1,
6994 "ltd_multiplier": 1,
6995 "plasticity_window": 5,
6996 })];
6997 manager
6998 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
6999 .unwrap();
7000 let created = manager
7001 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7002 .unwrap();
7003 assert!(
7004 created > 0,
7005 "Expected associative memory mapping between memory areas to create synapses"
7006 );
7007 }
7008
7009 #[test]
7010 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7011 use crate::models::cortical_area::CorticalArea;
7012 use feagi_npu_burst_engine::backend::CPUBackend;
7013 use feagi_npu_burst_engine::TracingMutex;
7014 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7015 use feagi_npu_runtime::StdRuntime;
7016 use feagi_structures::genomic::cortical_area::{
7017 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7018 MemoryCorticalType,
7019 };
7020 use std::sync::Arc;
7021
7022 let runtime = StdRuntime;
7023 let backend = CPUBackend::new();
7024 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7025 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7026 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7027 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7028
7029 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7030 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7031
7032 let src_area = CorticalArea::new(
7033 src_id,
7034 0,
7035 "Source Area".to_string(),
7036 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7037 (0, 0, 0).into(),
7038 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7039 )
7040 .unwrap();
7041 let mut mem_area = CorticalArea::new(
7042 mem_id,
7043 0,
7044 "Memory Area".to_string(),
7045 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7046 (0, 0, 0).into(),
7047 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7048 )
7049 .unwrap();
7050 mem_area
7051 .properties
7052 .insert("is_mem_type".to_string(), serde_json::json!(true));
7053 mem_area
7054 .properties
7055 .insert("temporal_depth".to_string(), serde_json::json!(1));
7056
7057 manager.add_cortical_area(src_area).unwrap();
7058 manager.add_cortical_area(mem_area).unwrap();
7059
7060 let twin_id = manager
7061 .build_memory_twin_id(&mem_id, &src_id)
7062 .expect("Failed to build twin id");
7063 let twin_area = CorticalArea::new(
7064 twin_id,
7065 0,
7066 "Source Area_twin".to_string(),
7067 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7068 (0, 0, 0).into(),
7069 CorticalAreaType::Custom(
7070 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7071 ),
7072 )
7073 .unwrap();
7074 manager.add_cortical_area(twin_area).unwrap();
7075
7076 let repaired = manager
7077 .ensure_memory_twin_area(&mem_id, &src_id)
7078 .expect("Failed to repair twin");
7079 assert_eq!(repaired, twin_id);
7080
7081 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
7082 let twin_map = mem_area
7083 .properties
7084 .get("memory_twin_areas")
7085 .and_then(|v| v.as_object())
7086 .expect("memory_twin_areas should be set");
7087 let twin_id_str = twin_map
7088 .get(&src_id.as_base_64())
7089 .and_then(|v| v.as_str())
7090 .expect("Missing twin entry for upstream area");
7091 assert_eq!(twin_id_str, twin_id.as_base_64());
7092
7093 let replay_map = mem_area
7094 .properties
7095 .get("cortical_mapping_dst")
7096 .and_then(|v| v.as_object())
7097 .and_then(|map| map.get(&twin_id.as_base_64()))
7098 .and_then(|v| v.as_array())
7099 .expect("Missing memory replay mapping for twin area");
7100 let uses_replay = replay_map.iter().any(|rule| {
7101 rule.get("morphology_id")
7102 .and_then(|v| v.as_str())
7103 .is_some_and(|id| id == "memory_replay")
7104 });
7105 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7106
7107 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7108 assert_eq!(
7109 twin_area
7110 .properties
7111 .get("memory_twin_of")
7112 .and_then(|v| v.as_str()),
7113 Some(src_id.as_base_64().as_str())
7114 );
7115 assert_eq!(
7116 twin_area
7117 .properties
7118 .get("memory_twin_for")
7119 .and_then(|v| v.as_str()),
7120 Some(mem_id.as_base_64().as_str())
7121 );
7122 }
7123}