1use once_cell::sync::Lazy;
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::hash::Hasher;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::sync::{Arc, Mutex};
38use tracing::{debug, error, info, trace, warn};
39use xxhash_rust::xxh64::Xxh64;
40
41pub type BrainRegionIoRegistry = HashMap<String, (Vec<String>, Vec<String>)>;
43
44use crate::models::{BrainRegion, BrainRegionHierarchy, CorticalArea, CorticalAreaDimensions};
45use crate::types::{BduError, BduResult};
46use feagi_npu_neural::synapse::SYNAPSE_EDGE_ASSOCIATIVE_MEMORY;
47use feagi_npu_neural::types::NeuronId;
48use feagi_structures::genomic::cortical_area::{
49 CoreCorticalType, CorticalAreaType, CorticalID, CustomCorticalType,
50};
51use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
52
53use feagi_state_manager::StateManager;
56
57const DATA_HASH_SEED: u64 = 0;
58const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
60
61static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
66 Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ConnectomeConfig {
71 pub max_neurons: usize,
73
74 pub max_synapses: usize,
76
77 pub backend: String,
79}
80
81impl Default for ConnectomeConfig {
82 fn default() -> Self {
83 Self {
84 max_neurons: 10_000_000,
85 max_synapses: 100_000_000,
86 backend: "cpu".to_string(),
87 }
88 }
89}
90
91pub struct ConnectomeManager {
113 cortical_areas: HashMap<CorticalID, CorticalArea>,
115
116 cortical_id_to_idx: HashMap<CorticalID, u32>,
118
119 cortical_idx_to_id: HashMap<u32, CorticalID>,
121
122 next_cortical_idx: u32,
124
125 brain_regions: BrainRegionHierarchy,
127
128 morphology_registry: feagi_evolutionary::MorphologyRegistry,
130
131 config: ConnectomeConfig,
133
134 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
140
141 #[cfg(feature = "plasticity")]
143 plasticity_executor:
144 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
145
146 cached_neuron_count: Arc<AtomicUsize>,
149
150 cached_synapse_count: Arc<AtomicUsize>,
153
154 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
157
158 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
161
162 initialized: bool,
164
165 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
167}
168
169type NeuronData = (
171 u32,
172 u32,
173 u32,
174 f32,
175 f32,
176 f32,
177 f32,
178 i32,
179 u16,
180 f32,
181 u16,
182 u16,
183 bool,
184);
185
186impl ConnectomeManager {
187 fn get_mapping_rules_for_destination<'a>(
188 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
189 dst_area_id: &CorticalID,
190 ) -> Option<&'a Vec<serde_json::Value>> {
191 if let Some(rules) = mapping_dst
192 .get(&dst_area_id.as_base_64())
193 .and_then(|value| value.as_array())
194 {
195 return Some(rules);
196 }
197
198 for (raw_dst_key, rules_value) in mapping_dst {
201 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
202 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
203 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
204 continue;
205 }
206 if let Some(rules) = rules_value.as_array() {
207 return Some(rules);
208 }
209 }
210
211 None
212 }
213
214 fn new() -> Self {
216 Self {
217 cortical_areas: HashMap::new(),
218 cortical_id_to_idx: HashMap::new(),
219 cortical_idx_to_id: HashMap::new(),
220 next_cortical_idx: 3, brain_regions: BrainRegionHierarchy::new(),
223 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
224 config: ConnectomeConfig::default(),
225 npu: None,
226 #[cfg(feature = "plasticity")]
227 plasticity_executor: None,
228 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
229 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
230 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
231 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
232 initialized: false,
233 last_fatigue_calculation: Arc::new(Mutex::new(
234 std::time::Instant::now() - std::time::Duration::from_secs(10),
235 )), }
237 }
238
239 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
256 Arc::clone(&*INSTANCE)
259 }
260
261 pub fn new_for_testing() -> Self {
289 Self {
290 cortical_areas: HashMap::new(),
291 cortical_id_to_idx: HashMap::new(),
292 cortical_idx_to_id: HashMap::new(),
293 next_cortical_idx: 0,
294 brain_regions: BrainRegionHierarchy::new(),
295 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
296 config: ConnectomeConfig::default(),
297 npu: None,
298 #[cfg(feature = "plasticity")]
299 plasticity_executor: None,
300 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
301 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
302 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
303 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
304 initialized: false,
305 last_fatigue_calculation: Arc::new(Mutex::new(
306 std::time::Instant::now() - std::time::Duration::from_secs(10),
307 )),
308 }
309 }
310
311 pub fn new_for_testing_with_npu(
327 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
328 ) -> Self {
329 Self {
330 cortical_areas: HashMap::new(),
331 cortical_id_to_idx: HashMap::new(),
332 cortical_idx_to_id: HashMap::new(),
333 next_cortical_idx: 3,
334 brain_regions: BrainRegionHierarchy::new(),
335 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
336 config: ConnectomeConfig::default(),
337 npu: Some(npu),
338 #[cfg(feature = "plasticity")]
339 plasticity_executor: None,
340 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
341 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
342 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
343 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
344 initialized: false,
345 last_fatigue_calculation: Arc::new(Mutex::new(
346 std::time::Instant::now() - std::time::Duration::from_secs(10),
347 )),
348 }
349 }
350
351 pub fn setup_core_morphologies_for_testing(&mut self) {
361 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
362 }
363
364 #[cfg(test)]
373 pub fn reset_for_testing() {
374 let mut instance = INSTANCE.write();
375 *instance = Self::new();
376 }
377
378 fn update_state_hashes(
384 &self,
385 brain_regions: Option<u64>,
386 cortical_areas: Option<u64>,
387 brain_geometry: Option<u64>,
388 morphologies: Option<u64>,
389 cortical_mappings: Option<u64>,
390 ) {
391 let state_manager = StateManager::instance();
392 let state_manager = state_manager.read();
393 if let Some(value) = brain_regions {
394 state_manager.set_brain_regions_hash(value);
395 }
396 if let Some(value) = cortical_areas {
397 state_manager.set_cortical_areas_hash(value);
398 }
399 if let Some(value) = brain_geometry {
400 state_manager.set_brain_geometry_hash(value);
401 }
402 if let Some(value) = morphologies {
403 state_manager.set_morphologies_hash(value);
404 }
405 if let Some(value) = cortical_mappings {
406 state_manager.set_cortical_mappings_hash(value);
407 }
408 }
409
410 fn refresh_brain_regions_hash(&self) {
412 let hash = self.compute_brain_regions_hash();
413 self.update_state_hashes(Some(hash), None, None, None, None);
414 }
415
416 #[allow(dead_code)]
417 fn refresh_cortical_areas_hash(&self) {
419 let hash = self.compute_cortical_areas_hash();
420 self.update_state_hashes(None, Some(hash), None, None, None);
421 }
422
423 #[allow(dead_code)]
424 fn refresh_brain_geometry_hash(&self) {
426 let hash = self.compute_brain_geometry_hash();
427 self.update_state_hashes(None, None, Some(hash), None, None);
428 }
429
430 fn refresh_morphologies_hash(&self) {
432 let hash = self.compute_morphologies_hash();
433 self.update_state_hashes(None, None, None, Some(hash), None);
434 }
435
436 fn refresh_cortical_mappings_hash(&self) {
438 let hash = self.compute_cortical_mappings_hash();
439 self.update_state_hashes(None, None, None, None, Some(hash));
440 }
441
442 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
444 let cortical_hash = if properties_changed {
445 Some(self.compute_cortical_areas_hash())
446 } else {
447 None
448 };
449 let geometry_hash = if geometry_changed {
450 Some(self.compute_brain_geometry_hash())
451 } else {
452 None
453 };
454 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
455 }
456
457 fn compute_brain_regions_hash(&self) -> u64 {
459 let mut hasher = Xxh64::new(DATA_HASH_SEED);
460 let mut region_ids: Vec<String> = self
461 .brain_regions
462 .get_all_region_ids()
463 .into_iter()
464 .cloned()
465 .collect();
466 region_ids.sort();
467
468 for region_id in region_ids {
469 let Some(region) = self.brain_regions.get_region(®ion_id) else {
470 continue;
471 };
472 Self::hash_str(&mut hasher, ®ion_id);
473 Self::hash_str(&mut hasher, ®ion.name);
474 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
475 let parent_id = self.brain_regions.get_parent(®ion_id);
476 match parent_id {
477 Some(parent) => Self::hash_str(&mut hasher, parent),
478 None => Self::hash_str(&mut hasher, "null"),
479 }
480
481 let mut cortical_ids: Vec<String> = region
482 .cortical_areas
483 .iter()
484 .map(|id| id.as_base_64())
485 .collect();
486 cortical_ids.sort();
487 for cortical_id in cortical_ids {
488 Self::hash_str(&mut hasher, &cortical_id);
489 }
490
491 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
492 }
493
494 hasher.finish() & HASH_SAFE_MASK
495 }
496
497 fn compute_cortical_areas_hash(&self) -> u64 {
499 let mut hasher = Xxh64::new(DATA_HASH_SEED);
500 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
501 areas.sort_by_key(|area| area.cortical_id.as_base_64());
502
503 for area in areas {
504 let cortical_id = area.cortical_id.as_base_64();
505 Self::hash_str(&mut hasher, &cortical_id);
506 hasher.write_u32(area.cortical_idx);
507 Self::hash_str(&mut hasher, &area.name);
508 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
509
510 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
511 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
512 }
513
514 hasher.finish() & HASH_SAFE_MASK
515 }
516
517 fn compute_brain_geometry_hash(&self) -> u64 {
519 let mut hasher = Xxh64::new(DATA_HASH_SEED);
520 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
521 areas.sort_by_key(|area| area.cortical_id.as_base_64());
522
523 for area in areas {
524 let cortical_id = area.cortical_id.as_base_64();
525 Self::hash_str(&mut hasher, &cortical_id);
526
527 Self::hash_i32(&mut hasher, area.position.x);
528 Self::hash_i32(&mut hasher, area.position.y);
529 Self::hash_i32(&mut hasher, area.position.z);
530
531 Self::hash_u32(&mut hasher, area.dimensions.width);
532 Self::hash_u32(&mut hasher, area.dimensions.height);
533 Self::hash_u32(&mut hasher, area.dimensions.depth);
534
535 let coord_2d = area
536 .properties
537 .get("coordinate_2d")
538 .or_else(|| area.properties.get("coordinates_2d"));
539 match coord_2d {
540 Some(value) => Self::hash_json_value(&mut hasher, value),
541 None => Self::hash_str(&mut hasher, "null"),
542 }
543 }
544
545 hasher.finish() & HASH_SAFE_MASK
546 }
547
548 fn compute_morphologies_hash(&self) -> u64 {
550 let mut hasher = Xxh64::new(DATA_HASH_SEED);
551 let mut morphology_ids = self.morphology_registry.morphology_ids();
552 morphology_ids.sort();
553
554 for morphology_id in morphology_ids {
555 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
556 Self::hash_str(&mut hasher, &morphology_id);
557 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
558 Self::hash_str(&mut hasher, &morphology.class);
559 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
560 Self::hash_json_value(&mut hasher, &value);
561 }
562 }
563 }
564
565 hasher.finish() & HASH_SAFE_MASK
566 }
567
568 fn compute_cortical_mappings_hash(&self) -> u64 {
570 let mut hasher = Xxh64::new(DATA_HASH_SEED);
571 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
572 areas.sort_by_key(|area| area.cortical_id.as_base_64());
573
574 for area in areas {
575 let cortical_id = area.cortical_id.as_base_64();
576 Self::hash_str(&mut hasher, &cortical_id);
577 if let Some(serde_json::Value::Object(map)) =
578 area.properties.get("cortical_mapping_dst")
579 {
580 let mut dest_ids: Vec<&String> = map.keys().collect();
581 dest_ids.sort();
582 for dest_id in dest_ids {
583 Self::hash_str(&mut hasher, dest_id);
584 if let Some(value) = map.get(dest_id) {
585 Self::hash_json_value(&mut hasher, value);
586 }
587 }
588 } else {
589 Self::hash_str(&mut hasher, "null");
590 }
591 }
592
593 hasher.finish() & HASH_SAFE_MASK
594 }
595
596 fn hash_str(hasher: &mut Xxh64, value: &str) {
598 hasher.write(value.as_bytes());
599 hasher.write_u8(0);
600 }
601
602 fn hash_i32(hasher: &mut Xxh64, value: i32) {
604 hasher.write(&value.to_le_bytes());
605 }
606
607 fn hash_u32(hasher: &mut Xxh64, value: u32) {
609 hasher.write(&value.to_le_bytes());
610 }
611
612 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
614 match value {
615 serde_json::Value::Null => {
616 hasher.write_u8(0);
617 }
618 serde_json::Value::Bool(val) => {
619 hasher.write_u8(1);
620 hasher.write_u8(*val as u8);
621 }
622 serde_json::Value::Number(num) => {
623 hasher.write_u8(2);
624 Self::hash_str(hasher, &num.to_string());
625 }
626 serde_json::Value::String(val) => {
627 hasher.write_u8(3);
628 Self::hash_str(hasher, val);
629 }
630 serde_json::Value::Array(items) => {
631 hasher.write_u8(4);
632 for item in items {
633 Self::hash_json_value(hasher, item);
634 }
635 }
636 serde_json::Value::Object(map) => {
637 hasher.write_u8(5);
638 let mut keys: Vec<&String> = map.keys().collect();
639 keys.sort();
640 for key in keys {
641 Self::hash_str(hasher, key);
642 if let Some(val) = map.get(key) {
643 Self::hash_json_value(hasher, val);
644 }
645 }
646 }
647 }
648 }
649
650 fn hash_properties_filtered(
652 hasher: &mut Xxh64,
653 properties: &HashMap<String, serde_json::Value>,
654 excluded_keys: &[&str],
655 ) {
656 let mut keys: Vec<&String> = properties.keys().collect();
657 keys.sort();
658 for key in keys {
659 if excluded_keys.contains(&key.as_str()) {
660 continue;
661 }
662 Self::hash_str(hasher, key);
663 if let Some(value) = properties.get(key) {
664 Self::hash_json_value(hasher, value);
665 }
666 }
667 }
668
669 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
690 if self.cortical_areas.contains_key(&area.cortical_id) {
692 return Err(BduError::InvalidArea(format!(
693 "Cortical area {} already exists",
694 area.cortical_id
695 )));
696 }
697
698 use feagi_structures::genomic::cortical_area::CoreCorticalType;
701
702 let death_id = CoreCorticalType::Death.to_cortical_id();
703 let power_id = CoreCorticalType::Power.to_cortical_id();
704 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
705
706 let is_death_area = area.cortical_id == death_id;
707 let is_power_area = area.cortical_id == power_id;
708 let is_fatigue_area = area.cortical_id == fatigue_id;
709
710 if is_death_area {
711 trace!(
712 target: "feagi-bdu",
713 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
714 area.cortical_id
715 );
716 area.cortical_idx = 0;
717 } else if is_power_area {
718 trace!(
719 target: "feagi-bdu",
720 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
721 area.cortical_id
722 );
723 area.cortical_idx = 1;
724 } else if is_fatigue_area {
725 trace!(
726 target: "feagi-bdu",
727 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
728 area.cortical_id
729 );
730 area.cortical_idx = 2;
731 } else {
732 if area.cortical_idx == 0 {
734 area.cortical_idx = self.next_cortical_idx;
735 self.next_cortical_idx += 1;
736 trace!(
737 target: "feagi-bdu",
738 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be ≥3)",
739 area.cortical_idx,
740 area.cortical_id.as_base_64()
741 );
742 } else {
743 if area.cortical_idx == 0 || area.cortical_idx == 1 || area.cortical_idx == 2 {
745 warn!(
746 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
747 area.cortical_id, area.cortical_idx);
748 area.cortical_idx = self.next_cortical_idx;
749 self.next_cortical_idx += 1;
750 info!(
751 " Reassigned '{}' to cortical_idx={}",
752 area.cortical_id, area.cortical_idx
753 );
754 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
755 return Err(BduError::InvalidArea(format!(
756 "Cortical index {} is already in use",
757 area.cortical_idx
758 )));
759 }
760
761 if area.cortical_idx >= self.next_cortical_idx {
763 self.next_cortical_idx = area.cortical_idx + 1;
764 }
765 }
766 }
767
768 let cortical_id = area.cortical_id;
769 let cortical_idx = area.cortical_idx;
770
771 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
773 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
774
775 area.properties
777 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
778
779 let parent_region_id = area
788 .properties
789 .get("parent_region_id")
790 .and_then(|v| v.as_str())
791 .map(|s| s.to_string());
792
793 self.cortical_areas.insert(cortical_id, area);
795
796 if let Some(region_id) = parent_region_id {
798 let region = self
799 .brain_regions
800 .get_region_mut(®ion_id)
801 .ok_or_else(|| {
802 BduError::InvalidArea(format!(
803 "Unknown parent_region_id '{}' for cortical area {}",
804 region_id,
805 cortical_id.as_base_64()
806 ))
807 })?;
808 region.add_area(cortical_id);
809 }
810
811 {
814 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
815 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
816 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
817 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
818 }
819 let state_manager = StateManager::instance();
821 let state_manager = state_manager.read();
822 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
823
824 if let Some(ref npu) = self.npu {
828 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
829 if let Ok(mut npu_lock) = npu.lock() {
830 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
831 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
832 trace!(
833 target: "feagi-bdu",
834 "Registered cortical area idx={} -> '{}' in NPU",
835 cortical_idx,
836 cortical_id.as_base_64()
837 );
838 }
839 }
840
841 self.sync_cortical_area_flags_to_npu()?;
843
844 self.initialized = true;
845
846 self.refresh_cortical_area_hashes(true, true);
847 self.refresh_brain_regions_hash();
848
849 Ok(cortical_idx)
850 }
851
852 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
867 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
868 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
869 })?;
870
871 self.cortical_id_to_idx.remove(cortical_id);
873 self.cortical_idx_to_id.remove(&area.cortical_idx);
874
875 self.refresh_cortical_area_hashes(true, true);
876 Ok(())
877 }
878
879 pub fn rename_cortical_area_id(
883 &mut self,
884 old_id: &CorticalID,
885 new_id: CorticalID,
886 new_cortical_type: CorticalAreaType,
887 ) -> BduResult<()> {
888 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
889 }
890
891 pub fn rename_cortical_area_id_with_options(
893 &mut self,
894 old_id: &CorticalID,
895 new_id: CorticalID,
896 new_cortical_type: CorticalAreaType,
897 update_npu_registry: bool,
898 ) -> BduResult<()> {
899 if !self.cortical_areas.contains_key(old_id) {
900 return Err(BduError::InvalidArea(format!(
901 "Cortical area {} does not exist",
902 old_id
903 )));
904 }
905 if self.cortical_areas.contains_key(&new_id) {
906 return Err(BduError::InvalidArea(format!(
907 "Cortical area {} already exists",
908 new_id
909 )));
910 }
911
912 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
913 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
914 })?;
915 let cortical_idx = area.cortical_idx;
916 area.cortical_id = new_id;
917 area.cortical_type = new_cortical_type;
918
919 self.cortical_areas.insert(new_id, area);
920 self.cortical_id_to_idx.remove(old_id);
921 self.cortical_id_to_idx.insert(new_id, cortical_idx);
922 self.cortical_idx_to_id.insert(cortical_idx, new_id);
923
924 {
926 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
927 if let Some(value) = neuron_cache.remove(old_id) {
928 neuron_cache.insert(new_id, value);
929 }
930 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
931 if let Some(value) = synapse_cache.remove(old_id) {
932 synapse_cache.insert(new_id, value);
933 }
934 }
935
936 self.brain_regions.rename_cortical_area_id(old_id, new_id);
938
939 let old_id_str = old_id.as_base_64();
941 let new_id_str = new_id.as_base_64();
942 for area in self.cortical_areas.values_mut() {
943 if let Some(mapping) = area
944 .properties
945 .get_mut("cortical_mapping_dst")
946 .and_then(|v| v.as_object_mut())
947 {
948 if let Some(value) = mapping.remove(&old_id_str) {
949 mapping.insert(new_id_str.clone(), value);
950 }
951 }
952 }
953
954 if update_npu_registry {
956 if let Some(ref npu) = self.npu {
957 if let Ok(mut npu_lock) = npu.lock() {
958 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
959 }
960 }
961 }
962
963 self.refresh_cortical_area_hashes(true, true);
964 self.refresh_brain_regions_hash();
965 self.refresh_cortical_mappings_hash();
966
967 Ok(())
968 }
969
970 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
972 self.cortical_areas.get(cortical_id)
973 }
974
975 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
977 self.cortical_areas.get_mut(cortical_id)
978 }
979
980 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
982 self.cortical_id_to_idx.get(cortical_id).copied()
983 }
984
985 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
997 self.brain_regions.find_region_containing_area(cortical_id)
998 }
999
1000 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1003 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1004 return false;
1005 };
1006 let Some(src_area) = self.cortical_areas.get(area) else {
1007 return false;
1008 };
1009 let Some(dst_obj) = src_area
1010 .properties
1011 .get("cortical_mapping_dst")
1012 .and_then(|v| v.as_object())
1013 else {
1014 return false;
1015 };
1016 for dst_key in dst_obj.keys() {
1017 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1018 continue;
1019 };
1020 match self.brain_regions.find_region_containing_area(&dst_id) {
1021 None => return true,
1022 Some(rid) if rid != my_region => return true,
1023 _ => {}
1024 }
1025 }
1026 false
1027 }
1028
1029 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1031 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1032 return false;
1033 };
1034 let my_b64 = area.as_base_64();
1035 for (src_id, src_area) in &self.cortical_areas {
1036 if src_id == area {
1037 continue;
1038 }
1039 let Some(dst_map) = src_area
1040 .properties
1041 .get("cortical_mapping_dst")
1042 .and_then(|v| v.as_object())
1043 else {
1044 continue;
1045 };
1046 if !dst_map.contains_key(&my_b64) {
1047 continue;
1048 }
1049 match self.brain_regions.find_region_containing_area(src_id) {
1050 None => return true,
1051 Some(rid) if rid != my_region => return true,
1052 _ => {}
1053 }
1054 }
1055 false
1056 }
1057
1058 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1069 use std::collections::HashSet;
1070
1071 let region_ids: Vec<String> = self
1072 .brain_regions
1073 .get_all_region_ids()
1074 .into_iter()
1075 .cloned()
1076 .collect();
1077
1078 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1079 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1080
1081 for rid in ®ion_ids {
1083 inputs_by_region.insert(rid.clone(), HashSet::new());
1084 outputs_by_region.insert(rid.clone(), HashSet::new());
1085 }
1086
1087 for (src_id, src_area) in &self.cortical_areas {
1088 let Some(dstmap) = src_area
1089 .properties
1090 .get("cortical_mapping_dst")
1091 .and_then(|v| v.as_object())
1092 else {
1093 continue;
1094 };
1095
1096 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1097 warn!(
1098 target: "feagi-bdu",
1099 "Skipping region IO for source area {} (not in any region)",
1100 src_id.as_base_64()
1101 );
1102 continue;
1103 };
1104
1105 for dst_id_str in dstmap.keys() {
1106 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1107 BduError::InvalidArea(format!(
1108 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1109 dst_id_str,
1110 src_id.as_base_64(),
1111 e
1112 ))
1113 })?;
1114
1115 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1116 else {
1117 warn!(
1118 target: "feagi-bdu",
1119 "Skipping region IO for destination area {} (not in any region)",
1120 dst_id.as_base_64()
1121 );
1122 continue;
1123 };
1124
1125 if src_region_id == dst_region_id {
1126 continue;
1127 }
1128
1129 outputs_by_region
1130 .entry(src_region_id.clone())
1131 .or_default()
1132 .insert(src_id.as_base_64());
1133 inputs_by_region
1134 .entry(dst_region_id.clone())
1135 .or_default()
1136 .insert(dst_id.as_base_64());
1137 }
1138 }
1139
1140 for rid in ®ion_ids {
1142 let Some(region) = self.brain_regions.get_region(rid) else {
1143 continue;
1144 };
1145 let in_ids = crate::region_io_designation::parse_designated_id_list(
1146 region
1147 .properties
1148 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1149 )?;
1150 let out_ids = crate::region_io_designation::parse_designated_id_list(
1151 region
1152 .properties
1153 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1154 )?;
1155 for id in in_ids {
1156 inputs_by_region
1157 .entry(rid.clone())
1158 .or_default()
1159 .insert(id.as_base_64());
1160 }
1161 for id in out_ids {
1162 outputs_by_region
1163 .entry(rid.clone())
1164 .or_default()
1165 .insert(id.as_base_64());
1166 }
1167 }
1168
1169 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1170 for rid in region_ids {
1171 let mut inputs: Vec<String> = inputs_by_region
1172 .remove(&rid)
1173 .unwrap_or_default()
1174 .into_iter()
1175 .collect();
1176 let mut outputs: Vec<String> = outputs_by_region
1177 .remove(&rid)
1178 .unwrap_or_default()
1179 .into_iter()
1180 .collect();
1181
1182 inputs.sort();
1183 outputs.sort();
1184
1185 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1186 BduError::InvalidArea(format!(
1187 "Unable to recompute region IO: region '{}' not found in hierarchy",
1188 rid
1189 ))
1190 })?;
1191
1192 if inputs.is_empty() {
1193 region.properties.remove("inputs");
1194 } else {
1195 region
1196 .properties
1197 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1198 }
1199
1200 if outputs.is_empty() {
1201 region.properties.remove("outputs");
1202 } else {
1203 region
1204 .properties
1205 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1206 }
1207
1208 computed.insert(rid, (inputs, outputs));
1209 }
1210
1211 self.refresh_brain_regions_hash();
1212
1213 Ok(computed)
1214 }
1215
1216 pub fn get_root_region_id(&self) -> Option<String> {
1222 self.brain_regions.get_root_region_id()
1223 }
1224
1225 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1227 self.cortical_idx_to_id.get(&cortical_idx)
1228 }
1229
1230 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1233 self.cortical_idx_to_id
1234 .iter()
1235 .map(|(idx, id)| (*idx, id.as_base_64()))
1236 .collect()
1237 }
1238
1239 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1244 let mut granularities = ahash::AHashMap::new();
1245 for (cortical_id, area) in &self.cortical_areas {
1246 let cortical_idx = self
1247 .cortical_id_to_idx
1248 .get(cortical_id)
1249 .copied()
1250 .unwrap_or(0);
1251
1252 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1255 if let Some(arr) = granularity_json.as_array() {
1256 if arr.len() == 3 {
1257 let x_opt = arr[0]
1258 .as_u64()
1259 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1260 let y_opt = arr[1]
1261 .as_u64()
1262 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1263 let z_opt = arr[2]
1264 .as_u64()
1265 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1266
1267 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1268 let granularity = (x as u32, y as u32, z as u32);
1269 if granularity != (1, 1, 1) {
1271 granularities.insert(cortical_idx, granularity);
1272 }
1273 }
1274 }
1275 }
1276 }
1277 }
1278 granularities
1279 }
1280
1281 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1283 self.cortical_areas.keys().collect()
1284 }
1285
1286 pub fn get_cortical_area_count(&self) -> usize {
1288 self.cortical_areas.len()
1289 }
1290
1291 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1305 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1306 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1307 if let Some(upstream_array) = upstream_prop.as_array() {
1308 return upstream_array
1309 .iter()
1310 .filter_map(|v| v.as_u64().map(|n| n as u32))
1311 .collect();
1312 }
1313 }
1314
1315 warn!(target: "feagi-bdu",
1317 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1318 target_cortical_id.as_base_64()
1319 );
1320 }
1321
1322 Vec::new()
1323 }
1324
1325 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1327 upstream
1328 .iter()
1329 .filter_map(|idx| {
1330 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1331 let area = self.cortical_areas.get(cortical_id)?;
1332 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1333 None
1334 } else {
1335 Some(*idx)
1336 }
1337 })
1338 .collect()
1339 }
1340
1341 pub fn refresh_upstream_cortical_areas_from_mappings(
1345 &mut self,
1346 target_cortical_id: &CorticalID,
1347 ) -> Vec<u32> {
1348 use std::collections::HashSet;
1349 let target_id_str = target_cortical_id.as_base_64();
1350 let mut upstream_idxs = HashSet::new();
1351 for (src_id, src_area) in &self.cortical_areas {
1352 if src_id == target_cortical_id {
1353 continue;
1354 }
1355 if let Some(mapping) = src_area
1356 .properties
1357 .get("cortical_mapping_dst")
1358 .and_then(|v| v.as_object())
1359 {
1360 if mapping.contains_key(&target_id_str) {
1361 upstream_idxs.insert(src_area.cortical_idx);
1362 }
1363 }
1364 }
1365
1366 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1367 upstream_list.sort_unstable();
1368
1369 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1370 target_area.properties.insert(
1371 "upstream_cortical_areas".to_string(),
1372 serde_json::json!(upstream_list),
1373 );
1374 }
1375
1376 self.get_upstream_cortical_areas(target_cortical_id)
1377 }
1378
1379 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1389 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1390 let upstream_array = area
1391 .properties
1392 .entry("upstream_cortical_areas".to_string())
1393 .or_insert_with(|| serde_json::json!([]));
1394
1395 if let Some(arr) = upstream_array.as_array_mut() {
1396 let src_value = serde_json::json!(src_cortical_idx);
1397 if !arr.contains(&src_value) {
1398 arr.push(src_value);
1399 info!(target: "feagi-bdu",
1400 "✓ Added upstream area idx={} to cortical area '{}'",
1401 src_cortical_idx, target_cortical_id.as_base_64()
1402 );
1403 }
1404 }
1405 }
1406 }
1407
1408 pub fn get_memory_twin_for_upstream_idx(
1410 &self,
1411 memory_area_idx: u32,
1412 upstream_idx: u32,
1413 ) -> Option<CorticalID> {
1414 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1415 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1416 let area = self.cortical_areas.get(memory_id)?;
1417 let mapping = area
1418 .properties
1419 .get("memory_twin_areas")
1420 .and_then(|v| v.as_object())?;
1421 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1422 CorticalID::try_from_base_64(twin_b64).ok()
1423 }
1424
1425 pub fn ensure_memory_twin_area(
1427 &mut self,
1428 memory_area_id: &CorticalID,
1429 upstream_area_id: &CorticalID,
1430 ) -> BduResult<CorticalID> {
1431 use crate::models::CorticalAreaExt;
1432
1433 let register_replay_mapping = |manager: &mut ConnectomeManager,
1434 twin_id: &CorticalID|
1435 -> BduResult<()> {
1436 let Some(npu) = manager.npu.as_ref() else {
1437 return Ok(());
1438 };
1439 let memory_area_idx =
1440 *manager
1441 .cortical_id_to_idx
1442 .get(memory_area_id)
1443 .ok_or_else(|| {
1444 BduError::InvalidArea(format!(
1445 "Memory area idx missing for {}",
1446 memory_area_id.as_base_64()
1447 ))
1448 })?;
1449 let upstream_area_idx = *manager
1450 .cortical_id_to_idx
1451 .get(upstream_area_id)
1452 .ok_or_else(|| {
1453 BduError::InvalidArea(format!(
1454 "Upstream area idx missing for {}",
1455 upstream_area_id.as_base_64()
1456 ))
1457 })?;
1458 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1459 BduError::InvalidArea(format!(
1460 "Twin area idx missing for {}",
1461 twin_id.as_base_64()
1462 ))
1463 })?;
1464 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1465 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1466 })?;
1467 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1468 if let Ok(mut npu_lock) = npu.lock() {
1469 npu_lock.register_memory_twin_mapping(
1470 memory_area_idx,
1471 upstream_area_idx,
1472 twin_area_idx,
1473 potential,
1474 );
1475 }
1476 Ok(())
1477 };
1478
1479 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1480 BduError::InvalidArea(format!(
1481 "Memory area {} not found",
1482 memory_area_id.as_base_64()
1483 ))
1484 })?;
1485 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1486 BduError::InvalidArea(format!(
1487 "Upstream area {} not found",
1488 upstream_area_id.as_base_64()
1489 ))
1490 })?;
1491
1492 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1493 return Err(BduError::InvalidArea(format!(
1494 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1495 upstream_area_id.as_base_64()
1496 )));
1497 }
1498
1499 if let Some(existing) = memory_area
1500 .properties
1501 .get("memory_twin_areas")
1502 .and_then(|v| v.as_object())
1503 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1504 .and_then(|v| v.as_str())
1505 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1506 {
1507 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1508 register_replay_mapping(self, &existing)?;
1509 self.refresh_cortical_mappings_hash();
1510 return Ok(existing);
1511 }
1512
1513 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1514 if self.cortical_areas.contains_key(&twin_id) {
1515 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1516 let expected_source = upstream_area_id.as_base_64();
1517 let expected_target = memory_area_id.as_base_64();
1518 let existing_source = existing
1519 .properties
1520 .get("memory_twin_of")
1521 .and_then(|v| v.as_str());
1522 let existing_target = existing
1523 .properties
1524 .get("memory_twin_for")
1525 .and_then(|v| v.as_str());
1526 if existing_source != Some(expected_source.as_str())
1527 || existing_target != Some(expected_target.as_str())
1528 {
1529 warn!(
1530 target: "feagi-bdu",
1531 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1532 upstream_area_id.as_base_64(),
1533 memory_area_id.as_base_64()
1534 );
1535 existing.properties.insert(
1536 "memory_twin_of".to_string(),
1537 serde_json::json!(expected_source),
1538 );
1539 existing.properties.insert(
1540 "memory_twin_for".to_string(),
1541 serde_json::json!(expected_target),
1542 );
1543 }
1544 }
1545 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1546 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1547 register_replay_mapping(self, &twin_id)?;
1548 self.refresh_cortical_mappings_hash();
1549 return Ok(twin_id);
1550 }
1551
1552 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1553 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1554 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1555 let mut twin_area = CorticalArea::new(
1556 twin_id,
1557 0,
1558 twin_name,
1559 upstream_area.dimensions,
1560 twin_position,
1561 twin_type,
1562 )?;
1563 twin_area.properties = self.build_memory_twin_properties(
1564 memory_area,
1565 upstream_area,
1566 memory_area_id,
1567 upstream_area_id,
1568 );
1569
1570 let _twin_idx = self.add_cortical_area(twin_area)?;
1571 let _ = self.create_neurons_for_area(&twin_id);
1572
1573 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1574 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1575 register_replay_mapping(self, &twin_id)?;
1576 self.refresh_cortical_mappings_hash();
1577 Ok(twin_id)
1578 }
1579
1580 fn build_memory_twin_position(
1581 &self,
1582 memory_area: &CorticalArea,
1583 upstream_area: &CorticalArea,
1584 ) -> GenomeCoordinate3D {
1585 let memory_parent = memory_area
1586 .properties
1587 .get("parent_region_id")
1588 .and_then(|v| v.as_str());
1589 let upstream_parent = upstream_area
1590 .properties
1591 .get("parent_region_id")
1592 .and_then(|v| v.as_str());
1593 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1594
1595 if !same_region {
1596 return GenomeCoordinate3D::new(
1597 memory_area.position.x + 20,
1598 memory_area.position.y,
1599 memory_area.position.z,
1600 );
1601 }
1602
1603 let width = upstream_area.dimensions.width as f32;
1604 let margin = (width * 0.25).ceil() as i32;
1605 let offset = upstream_area.dimensions.width as i32 + margin;
1606 GenomeCoordinate3D::new(
1607 upstream_area.position.x + offset,
1608 upstream_area.position.y,
1609 upstream_area.position.z,
1610 )
1611 }
1612
1613 fn build_memory_twin_id(
1614 &self,
1615 memory_area_id: &CorticalID,
1616 upstream_area_id: &CorticalID,
1617 ) -> BduResult<CorticalID> {
1618 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1619 hasher.write(memory_area_id.as_base_64().as_bytes());
1620 hasher.write(upstream_area_id.as_base_64().as_bytes());
1621 hasher.write(b"memory_twin");
1622 let hash = hasher.finish();
1623 let mut bytes = hash.to_be_bytes();
1624 bytes[0] = b'c';
1625 CorticalID::try_from_bytes(&bytes)
1626 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1627 }
1628
1629 fn build_memory_twin_properties(
1630 &self,
1631 memory_area: &CorticalArea,
1632 upstream_area: &CorticalArea,
1633 memory_area_id: &CorticalID,
1634 upstream_area_id: &CorticalID,
1635 ) -> HashMap<String, serde_json::Value> {
1636 let mut props = upstream_area.properties.clone();
1637 props.remove("cortical_mapping_dst");
1638 props.remove("upstream_cortical_areas");
1639 props.remove("parent_region_id");
1640 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1641 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1642 props.insert(
1643 "memory_twin_of".to_string(),
1644 serde_json::json!(upstream_area_id.as_base_64()),
1645 );
1646 props.insert(
1647 "memory_twin_for".to_string(),
1648 serde_json::json!(memory_area_id.as_base_64()),
1649 );
1650 if let Some(parent_region_id) = memory_area
1651 .properties
1652 .get("parent_region_id")
1653 .and_then(|v| v.as_str())
1654 {
1655 props.insert(
1656 "parent_region_id".to_string(),
1657 serde_json::json!(parent_region_id),
1658 );
1659 }
1660 props
1661 }
1662
1663 fn set_memory_twin_mapping(
1664 &mut self,
1665 memory_area_id: &CorticalID,
1666 upstream_area_id: &CorticalID,
1667 twin_id: &CorticalID,
1668 ) {
1669 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1670 let mapping = memory_area
1671 .properties
1672 .entry("memory_twin_areas".to_string())
1673 .or_insert_with(|| serde_json::json!({}));
1674 if let Some(map) = mapping.as_object_mut() {
1675 map.insert(
1676 upstream_area_id.as_base_64(),
1677 serde_json::json!(twin_id.as_base_64()),
1678 );
1679 }
1680 }
1681 }
1682
1683 fn ensure_memory_replay_mapping(
1684 &mut self,
1685 memory_area_id: &CorticalID,
1686 twin_id: &CorticalID,
1687 ) -> BduResult<()> {
1688 if !self.morphology_registry.contains("memory_replay") {
1689 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1690 }
1691 self.refresh_morphologies_hash();
1692 let mapping_data = vec![serde_json::json!({
1693 "morphology_id": "memory_replay",
1694 "morphology_scalar": [1, 1, 1],
1695 "postSynapticCurrent_multiplier": 1,
1696 "plasticity_flag": false,
1697 "plasticity_constant": 0,
1698 "ltp_multiplier": 0,
1699 "ltd_multiplier": 0,
1700 "plasticity_window": 0,
1701 })];
1702 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1703 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1704 self.refresh_cortical_area_hashes(true, false);
1706 Ok(())
1707 }
1708
1709 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1719 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1720 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1721 if let Some(arr) = upstream_prop.as_array_mut() {
1722 let src_value = serde_json::json!(src_cortical_idx);
1723 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1724 arr.remove(pos);
1725 debug!(target: "feagi-bdu",
1726 "Removed upstream area idx={} from cortical area '{}'",
1727 src_cortical_idx, target_cortical_id.as_base_64()
1728 );
1729 }
1730 }
1731 }
1732 }
1733 }
1734
1735 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1737 self.cortical_areas.contains_key(cortical_id)
1738 }
1739
1740 pub fn is_initialized(&self) -> bool {
1742 self.initialized && !self.cortical_areas.is_empty()
1743 }
1744
1745 pub fn add_brain_region(
1751 &mut self,
1752 region: BrainRegion,
1753 parent_id: Option<String>,
1754 ) -> BduResult<()> {
1755 self.brain_regions.add_region(region, parent_id)?;
1756 self.refresh_brain_regions_hash();
1757 Ok(())
1758 }
1759
1760 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1762 self.brain_regions.remove_region(region_id)?;
1763 self.refresh_brain_regions_hash();
1764 Ok(())
1765 }
1766
1767 pub fn change_brain_region_parent(
1769 &mut self,
1770 region_id: &str,
1771 new_parent_id: &str,
1772 ) -> BduResult<()> {
1773 self.brain_regions.change_parent(region_id, new_parent_id)?;
1774 self.refresh_brain_regions_hash();
1775 Ok(())
1776 }
1777
1778 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1780 self.brain_regions.get_region(region_id)
1781 }
1782
1783 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1785 self.brain_regions.get_region_mut(region_id)
1786 }
1787
1788 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1790 self.brain_regions.get_all_region_ids()
1791 }
1792
1793 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1795 &self.brain_regions
1796 }
1797
1798 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1804 &self.morphology_registry
1805 }
1806
1807 pub fn get_morphology_count(&self) -> usize {
1809 self.morphology_registry.count()
1810 }
1811
1812 pub fn upsert_morphology(
1817 &mut self,
1818 morphology_id: String,
1819 morphology: feagi_evolutionary::Morphology,
1820 ) {
1821 self.morphology_registry
1822 .add_morphology(morphology_id, morphology);
1823 self.refresh_morphologies_hash();
1824 }
1825
1826 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1832 let removed = self.morphology_registry.remove_morphology(morphology_id);
1833 if removed {
1834 self.refresh_morphologies_hash();
1835 }
1836 removed
1837 }
1838
1839 pub fn update_cortical_mapping(
1857 &mut self,
1858 src_area_id: &CorticalID,
1859 dst_area_id: &CorticalID,
1860 mapping_data: Vec<serde_json::Value>,
1861 ) -> BduResult<()> {
1862 use tracing::info;
1863
1864 crate::region_io_designation::validate_cross_region_mapping_proposal(
1865 self,
1866 src_area_id,
1867 dst_area_id,
1868 &mapping_data,
1869 )?;
1870
1871 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1872
1873 {
1874 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1876 crate::types::BduError::InvalidArea(format!(
1877 "Source area not found: {}",
1878 src_area_id
1879 ))
1880 })?;
1881
1882 let cortical_mapping_dst =
1884 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1885 existing.as_object_mut().ok_or_else(|| {
1886 crate::types::BduError::InvalidMorphology(
1887 "cortical_mapping_dst is not an object".to_string(),
1888 )
1889 })?
1890 } else {
1891 src_area
1893 .properties
1894 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1895 src_area
1896 .properties
1897 .get_mut("cortical_mapping_dst")
1898 .unwrap()
1899 .as_object_mut()
1900 .unwrap()
1901 };
1902
1903 if mapping_data.is_empty() {
1905 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1907 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1908 } else {
1909 cortical_mapping_dst.insert(
1910 dst_area_id.as_base_64(),
1911 serde_json::Value::Array(mapping_data.clone()),
1912 );
1913 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1914 src_area_id, dst_area_id, mapping_data.len());
1915 }
1916 }
1917
1918 self.refresh_cortical_mappings_hash();
1919
1920 Ok(())
1921 }
1922
1923 pub fn regenerate_synapses_for_mapping(
1936 &mut self,
1937 src_area_id: &CorticalID,
1938 dst_area_id: &CorticalID,
1939 ) -> BduResult<usize> {
1940 use tracing::info;
1941
1942 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1943
1944 let mapping_rules_len = self
1945 .cortical_areas
1946 .get(src_area_id)
1947 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1948 .and_then(|v| v.as_object())
1949 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1950 .and_then(|v| v.as_array())
1951 .map(|arr| arr.len())
1952 .unwrap_or(0);
1953 tracing::debug!(
1954 target: "feagi-bdu",
1955 "Mapping rules for {} -> {}: {}",
1956 src_area_id,
1957 dst_area_id,
1958 mapping_rules_len
1959 );
1960
1961 let Some(npu_arc) = self.npu.clone() else {
1963 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
1964 return Ok(0);
1965 };
1966
1967 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
1977 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
1978 })?;
1979 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
1980 BduError::InvalidArea(format!(
1981 "No cortical idx for destination area {}",
1982 dst_area_id
1983 ))
1984 })?;
1985
1986 let mut pruned_synapse_count: usize = 0;
1989 use std::time::Instant;
1990 let start = Instant::now();
1991
1992 let (sources, targets) = {
1998 let lock_start = std::time::Instant::now();
1999 let npu = npu_arc.lock().unwrap();
2000 let lock_wait = lock_start.elapsed();
2001 tracing::debug!(
2002 target: "feagi-bdu",
2003 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
2004 lock_wait.as_secs_f64() * 1000.0,
2005 src_area_id,
2006 dst_area_id
2007 );
2008 let sources: Vec<NeuronId> = npu
2009 .get_neurons_in_cortical_area(src_idx)
2010 .into_iter()
2011 .map(NeuronId)
2012 .collect();
2013 let targets: Vec<NeuronId> = npu
2014 .get_neurons_in_cortical_area(dst_idx)
2015 .into_iter()
2016 .map(NeuronId)
2017 .collect();
2018 (sources, targets)
2019 };
2020
2021 tracing::debug!(
2022 target: "feagi-bdu",
2023 "Prune synapses: {} sources, {} targets",
2024 sources.len(),
2025 targets.len()
2026 );
2027
2028 if !sources.is_empty() && !targets.is_empty() {
2029 let remove_start = Instant::now();
2030 pruned_synapse_count = {
2031 let lock_start = std::time::Instant::now();
2032 let mut npu = npu_arc.lock().unwrap();
2033 let lock_wait = lock_start.elapsed();
2034 tracing::debug!(
2035 target: "feagi-bdu",
2036 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
2037 lock_wait.as_secs_f64() * 1000.0,
2038 src_area_id,
2039 dst_area_id
2040 );
2041 npu.remove_synapses_between(sources, targets)
2045 };
2046 let remove_time = remove_start.elapsed();
2047 let total_time = start.elapsed();
2048
2049 info!(
2050 target: "feagi-bdu",
2051 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2052 pruned_synapse_count,
2053 src_area_id,
2054 dst_area_id,
2055 total_time.as_millis(),
2056 remove_time.as_millis()
2057 );
2058
2059 if pruned_synapse_count > 0 {
2061 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2062 BduError::Internal(format!(
2063 "Pruned synapse count overflow (usize -> u32): {}",
2064 pruned_synapse_count
2065 ))
2066 })?;
2067 let state_manager = StateManager::instance();
2068 let state_manager = state_manager.read();
2069 let core_state = state_manager.get_core_state();
2070 core_state.subtract_synapse_count(pruned_u32);
2071 state_manager.subtract_cortical_area_outgoing_synapses(
2072 &src_area_id.as_base_64(),
2073 pruned_synapse_count,
2074 );
2075 state_manager.subtract_cortical_area_incoming_synapses(
2076 &dst_area_id.as_base_64(),
2077 pruned_synapse_count,
2078 );
2079
2080 {
2084 let mut cache = self.cached_synapse_counts_per_area.write();
2085 let entry = cache
2086 .entry(*src_area_id)
2087 .or_insert_with(|| AtomicUsize::new(0));
2088 let mut current = entry.load(Ordering::Relaxed);
2089 loop {
2090 let next = current.saturating_sub(pruned_synapse_count);
2091 match entry.compare_exchange(
2092 current,
2093 next,
2094 Ordering::Relaxed,
2095 Ordering::Relaxed,
2096 ) {
2097 Ok(_) => break,
2098 Err(v) => current = v,
2099 }
2100 }
2101 }
2102 }
2103 }
2104
2105 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2112 tracing::debug!(
2113 target: "feagi-bdu",
2114 "Synaptogenesis created {} synapses for {} -> {}",
2115 synapse_count,
2116 src_area_id,
2117 dst_area_id
2118 );
2119
2120 if synapse_count > 0 {
2123 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2124 BduError::Internal(format!(
2125 "Created synapse count overflow (usize -> u32): {}",
2126 synapse_count
2127 ))
2128 })?;
2129
2130 {
2132 let mut cache = self.cached_synapse_counts_per_area.write();
2133 cache
2134 .entry(*src_area_id)
2135 .or_insert_with(|| AtomicUsize::new(0))
2136 .fetch_add(synapse_count, Ordering::Relaxed);
2137 }
2138
2139 let state_manager = StateManager::instance();
2141 let state_manager = state_manager.read();
2142 let core_state = state_manager.get_core_state();
2143 core_state.add_synapse_count(created_u32);
2144 state_manager
2145 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2146 state_manager
2147 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2148 }
2149
2150 let src_idx_for_upstream = src_idx;
2153
2154 let has_mapping = self
2156 .cortical_areas
2157 .get(src_area_id)
2158 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2159 .and_then(|v| v.as_object())
2160 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2161 .is_some();
2162
2163 info!(target: "feagi-bdu",
2164 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2165 synapse_count,
2166 src_area_id.as_base_64(),
2167 dst_area_id.as_base_64(),
2168 has_mapping,
2169 if has_mapping { "" } else { "NOT " }
2170 );
2171
2172 if has_mapping {
2173 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2175
2176 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2177 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2178 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2179 warn!(
2180 target: "feagi-bdu",
2181 "Failed to ensure memory twin for {} -> {}: {}",
2182 src_area_id.as_base_64(),
2183 dst_area_id.as_base_64(),
2184 e
2185 );
2186 }
2187 }
2188 }
2189
2190 #[cfg(feature = "plasticity")]
2192 if let Some(ref executor) = self.plasticity_executor {
2193 use feagi_evolutionary::extract_memory_properties;
2194
2195 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2196 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2197 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2198 let upstream_non_memory =
2199 self.filter_non_memory_upstream_areas(&upstream_areas);
2200 debug!(
2201 target: "feagi-bdu",
2202 "Registering memory area idx={} id={} upstream={} depth={}",
2203 dst_area.cortical_idx,
2204 dst_area_id.as_base_64(),
2205 upstream_areas.len(),
2206 mem_props.temporal_depth
2207 );
2208
2209 if let Some(ref npu_arc) = self.npu {
2212 if let Ok(mut npu) = npu_arc.lock() {
2213 let existing_configs = npu.get_all_fire_ledger_configs();
2214 for &upstream_idx in &upstream_areas {
2215 let existing = existing_configs
2216 .iter()
2217 .find(|(idx, _)| *idx == upstream_idx)
2218 .map(|(_, w)| *w)
2219 .unwrap_or(0);
2220
2221 let desired = mem_props.temporal_depth as usize;
2222 let resolved = existing.max(desired);
2223 if resolved != existing {
2224 if let Err(e) =
2225 npu.configure_fire_ledger_window(upstream_idx, resolved)
2226 {
2227 warn!(
2228 target: "feagi-bdu",
2229 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2230 upstream_idx,
2231 resolved,
2232 e
2233 );
2234 }
2235 }
2236 }
2237 } else {
2238 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2239 }
2240 }
2241
2242 if let Ok(exec) = executor.lock() {
2243 use feagi_npu_plasticity::{
2244 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2245 };
2246
2247 let lifecycle_config = MemoryNeuronLifecycleConfig {
2248 initial_lifespan: mem_props.init_lifespan,
2249 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2250 longterm_threshold: mem_props.longterm_threshold,
2251 max_reactivations: 1000,
2252 };
2253
2254 exec.register_memory_area(
2255 dst_area.cortical_idx,
2256 dst_area_id.as_base_64(),
2257 mem_props.temporal_depth,
2258 upstream_non_memory,
2259 Some(lifecycle_config),
2260 );
2261 } else {
2262 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2263 }
2264 } else {
2265 debug!(
2266 target: "feagi-bdu",
2267 "Skipping plasticity registration: no memory properties for area {}",
2268 dst_area_id.as_base_64()
2269 );
2270 }
2271 } else {
2272 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2273 }
2274 } else {
2275 warn!(
2276 target: "feagi-bdu",
2277 "PlasticityExecutor not available; memory area {} not registered",
2278 dst_area_id.as_base_64()
2279 );
2280 }
2281
2282 #[cfg(not(feature = "plasticity"))]
2283 {
2284 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2285 }
2286 } else {
2287 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2289
2290 let mut npu = npu_arc.lock().unwrap();
2292 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2293 }
2294
2295 info!(
2296 target: "feagi-bdu",
2297 "Created {} new synapses: {} -> {}",
2298 synapse_count,
2299 src_area_id,
2300 dst_area_id
2301 );
2302
2303 if pruned_synapse_count > 0 || synapse_count == 0 {
2306 let mut npu = npu_arc.lock().unwrap();
2307 npu.rebuild_synapse_index();
2308 info!(
2309 target: "feagi-bdu",
2310 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2311 src_area_id,
2312 dst_area_id,
2313 pruned_synapse_count,
2314 synapse_count
2315 );
2316 } else {
2317 info!(
2318 target: "feagi-bdu",
2319 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2320 src_area_id,
2321 dst_area_id,
2322 synapse_count
2323 );
2324 }
2325
2326 {
2328 let npu = npu_arc.lock().unwrap();
2329 let fresh_count = npu.get_synapse_count();
2330 self.cached_synapse_count
2331 .store(fresh_count, Ordering::Relaxed);
2332 }
2333
2334 Ok(synapse_count)
2335 }
2336
2337 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2339 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2340 }
2341
2342 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2343 v.as_u64()
2344 .map(|n| n as usize)
2345 .or_else(|| v.as_f64().map(|f| f as usize))
2346 }
2347
2348 #[allow(clippy::too_many_arguments)]
2350 fn register_stdp_mapping_for_rule(
2351 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2352 src_area_id: &CorticalID,
2353 dst_area_id: &CorticalID,
2354 src_cortical_idx: u32,
2355 dst_cortical_idx: u32,
2356 rule_obj: &serde_json::Map<String, serde_json::Value>,
2357 bidirectional_stdp: bool,
2358 synapse_psp: f32,
2359 synapse_type: feagi_npu_neural::SynapseType,
2360 ) -> BduResult<()> {
2361 let plasticity_window = rule_obj
2362 .get("plasticity_window")
2363 .and_then(Self::json_number_as_usize_for_stdp)
2364 .ok_or_else(|| {
2365 BduError::Internal(format!(
2366 "Missing plasticity_window in plastic mapping rule {} -> {}",
2367 src_area_id, dst_area_id
2368 ))
2369 })?;
2370 let plasticity_constant = rule_obj
2371 .get("plasticity_constant")
2372 .and_then(Self::json_number_as_i64_for_stdp)
2373 .ok_or_else(|| {
2374 BduError::Internal(format!(
2375 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2376 src_area_id, dst_area_id
2377 ))
2378 })?;
2379 let ltp_multiplier = rule_obj
2380 .get("ltp_multiplier")
2381 .and_then(Self::json_number_as_i64_for_stdp)
2382 .ok_or_else(|| {
2383 BduError::Internal(format!(
2384 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2385 src_area_id, dst_area_id
2386 ))
2387 })?;
2388 let ltd_multiplier = rule_obj
2389 .get("ltd_multiplier")
2390 .and_then(Self::json_number_as_i64_for_stdp)
2391 .ok_or_else(|| {
2392 BduError::Internal(format!(
2393 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2394 src_area_id, dst_area_id
2395 ))
2396 })?;
2397
2398 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2399 plasticity_window,
2400 plasticity_constant,
2401 ltp_multiplier,
2402 ltd_multiplier,
2403 bidirectional_stdp,
2404 synapse_psp,
2405 synapse_type,
2406 };
2407
2408 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2409 let mut npu_lock = npu
2410 .lock()
2411 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2412 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2413
2414 npu_lock
2415 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2416 .map_err(|e| {
2417 BduError::Internal(format!(
2418 "Failed to register STDP mapping {} -> {}: {}",
2419 src_area_id, dst_area_id, e
2420 ))
2421 })?;
2422
2423 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2425 for area_idx in [src_cortical_idx, dst_cortical_idx] {
2426 let existing = existing_configs
2427 .iter()
2428 .find(|(idx, _)| *idx == area_idx)
2429 .map(|(_, w)| *w)
2430 .unwrap_or(0);
2431 let resolved = existing.max(plasticity_window);
2432 if resolved != existing {
2433 npu_lock
2434 .configure_fire_ledger_window(area_idx, resolved)
2435 .map_err(|e| {
2436 BduError::Internal(format!(
2437 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2438 area_idx, resolved, e
2439 ))
2440 })?;
2441 }
2442 }
2443
2444 Ok(())
2445 }
2446
2447 fn resolve_synapse_params_for_rule(
2449 &self,
2450 src_area_id: &CorticalID,
2451 rule: &serde_json::Value,
2452 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
2453 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2455 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2456 })?;
2457
2458 let (weight, synapse_type) = {
2460 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
2461 if let Some(i) = v.as_i64() {
2462 return Some(i as f64);
2463 }
2464 v.as_f64()
2465 };
2466
2467 let mult: f64 = if let Some(obj) = rule.as_object() {
2468 obj.get("postSynapticCurrent_multiplier")
2469 .and_then(parse_f64)
2470 .unwrap_or(1.0)
2471 } else if let Some(arr) = rule.as_array() {
2472 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
2473 } else {
2474 128.0
2475 };
2476
2477 if mult < 0.0 {
2478 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
2479 } else {
2480 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
2481 }
2482 };
2483
2484 use crate::models::cortical_area::CorticalAreaExt;
2486 let psp_f32 = src_area.postsynaptic_current();
2487
2488 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
2489 obj.get("synaptic_delay_bursts")
2490 .and_then(|v| v.as_u64())
2491 .map(|d| u8::try_from(d).unwrap_or(1))
2492 .unwrap_or(1)
2493 } else if let Some(arr) = rule.as_array() {
2494 arr.get(8)
2495 .and_then(|v| v.as_u64())
2496 .map(|d| u8::try_from(d).unwrap_or(1))
2497 .unwrap_or(1)
2498 } else {
2499 1
2500 };
2501 if delay_bursts < 1 {
2502 return Err(crate::types::BduError::Internal(format!(
2503 "synaptic_delay_bursts must be >= 1 (src area {})",
2504 src_area_id.as_base_64()
2505 )));
2506 }
2507
2508 tracing::debug!(
2509 target: "feagi-bdu",
2510 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
2511 src_area_id.as_base_64(),
2512 weight,
2513 psp_f32,
2514 synapse_type,
2515 delay_bursts
2516 );
2517
2518 Ok((weight, psp_f32, synapse_type, delay_bursts))
2519 }
2520
2521 fn apply_cortical_mapping_for_pair(
2523 &mut self,
2524 src_area_id: &CorticalID,
2525 dst_area_id: &CorticalID,
2526 ) -> BduResult<usize> {
2527 let rules = {
2533 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2534 crate::types::BduError::InvalidArea(format!(
2535 "Source area not found: {}",
2536 src_area_id
2537 ))
2538 })?;
2539
2540 let Some(mapping_dst) = src_area
2541 .properties
2542 .get("cortical_mapping_dst")
2543 .and_then(|v| v.as_object())
2544 else {
2545 return Ok(0);
2546 };
2547
2548 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
2549 else {
2550 return Ok(0);
2551 };
2552
2553 rules.clone()
2554 }; if rules.is_empty() {
2557 return Ok(0);
2558 }
2559
2560 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2562 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2563 })?;
2564 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2565 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2566 })?;
2567
2568 let npu_arc = self
2570 .npu
2571 .as_ref()
2572 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2573 .clone();
2574
2575 tracing::debug!(
2576 target: "feagi-bdu",
2577 "Applying {} mapping rule(s) for {} -> {}",
2578 rules.len(),
2579 src_area_id,
2580 dst_area_id
2581 );
2582 let mut total_synapses = 0;
2584 for rule in &rules {
2585 let morphology_id = if let Some(rule_obj) = rule.as_object() {
2586 rule_obj
2587 .get("morphology_id")
2588 .and_then(|v| v.as_str())
2589 .unwrap_or("unknown")
2590 .to_string()
2591 } else if let Some(rule_arr) = rule.as_array() {
2592 rule_arr
2593 .first()
2594 .and_then(|v| v.as_str())
2595 .unwrap_or("unknown")
2596 .to_string()
2597 } else {
2598 "unknown".to_string()
2599 };
2600
2601 let rule_keys: Vec<String> = rule
2602 .as_object()
2603 .map(|obj| obj.keys().cloned().collect())
2604 .unwrap_or_default();
2605
2606 let mut plasticity_flag = rule
2608 .as_object()
2609 .and_then(|obj| obj.get("plasticity_flag"))
2610 .and_then(|v| v.as_bool())
2611 .unwrap_or(false);
2612 if morphology_id == "associative_memory" {
2613 plasticity_flag = true;
2614 }
2615 if plasticity_flag {
2616 let Some(rule_obj) = rule.as_object() else {
2617 return Err(crate::types::BduError::InvalidMorphology(
2618 "Plasticity mapping rule must be an object format".to_string(),
2619 ));
2620 };
2621 let (_weight, psp, synapse_type, _delay_bursts) =
2622 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2623 let bidirectional_stdp = morphology_id == "associative_memory";
2624 if let Err(e) = Self::register_stdp_mapping_for_rule(
2625 &npu_arc,
2626 src_area_id,
2627 dst_area_id,
2628 src_cortical_idx,
2629 dst_cortical_idx,
2630 rule_obj,
2631 bidirectional_stdp,
2632 psp,
2633 synapse_type,
2634 ) {
2635 tracing::error!(
2636 target: "feagi-bdu",
2637 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2638 src_area_id,
2639 dst_area_id,
2640 morphology_id,
2641 rule_keys,
2642 e
2643 );
2644 return Err(e);
2645 }
2646 }
2647
2648 let synapse_count = match self.apply_single_morphology_rule(
2650 src_area_id,
2651 dst_area_id,
2652 rule,
2653 ) {
2654 Ok(count) => count,
2655 Err(e) => {
2656 tracing::error!(
2657 target: "feagi-bdu",
2658 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2659 src_area_id,
2660 dst_area_id,
2661 morphology_id,
2662 rule_keys,
2663 e
2664 );
2665 return Err(e);
2666 }
2667 };
2668 total_synapses += synapse_count;
2669 tracing::debug!(
2670 target: "feagi-bdu",
2671 "Rule {} created {} synapses for {} -> {}",
2672 morphology_id,
2673 synapse_count,
2674 src_area_id,
2675 dst_area_id
2676 );
2677 }
2678
2679 Ok(total_synapses)
2680 }
2681
2682 #[allow(clippy::too_many_arguments)]
2696 fn apply_function_morphology(
2697 &self,
2698 morphology_id: &str,
2699 rule: &serde_json::Value,
2700 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2701 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2702 src_area_id: &CorticalID,
2703 dst_area_id: &CorticalID,
2704 src_idx: u32,
2705 dst_idx: u32,
2706 weight: f32,
2707 psp: f32,
2708 synapse_attractivity: u8,
2709 synapse_type: feagi_npu_neural::SynapseType,
2710 delay_bursts: u8,
2711 ) -> BduResult<usize> {
2712 match morphology_id {
2713 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
2714 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 let src_dimensions = (
2729 src_area.dimensions.width as usize,
2730 src_area.dimensions.height as usize,
2731 src_area.dimensions.depth as usize,
2732 );
2733 let dst_dimensions = (
2734 dst_area.dimensions.width as usize,
2735 dst_area.dimensions.height as usize,
2736 dst_area.dimensions.depth as usize,
2737 );
2738
2739 let transpose = match morphology_id {
2742 "transpose_xy" => Some((1, 0, 2)),
2743 "transpose_yz" => Some((0, 2, 1)),
2744 "transpose_xz" => Some((2, 1, 0)),
2745 _ => None,
2746 };
2747
2748 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2749 let count = apply_projector_morphology_with_dimensions(
2750 npu,
2751 src_idx,
2752 dst_idx,
2753 src_dimensions,
2754 dst_dimensions,
2755 transpose,
2756 None, weight,
2758 psp,
2759 synapse_attractivity,
2760 synapse_type,
2761 0,
2762 delay_bursts,
2763 )?;
2764 npu.rebuild_synapse_index();
2766 Ok(count as usize)
2767 }
2768 "episodic_memory" => {
2769 use tracing::trace;
2772 trace!(
2773 target: "feagi-bdu",
2774 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
2775 src_idx, dst_idx
2776 );
2777 Ok(0)
2778 }
2779 "memory_replay" => {
2780 use tracing::trace;
2782 trace!(
2783 target: "feagi-bdu",
2784 "Memory replay morphology: {} -> {} (no physical synapses)",
2785 src_idx, dst_idx
2786 );
2787 Ok(0)
2788 }
2789 "associative_memory" => {
2790 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2794 crate::types::BduError::InvalidArea(format!(
2795 "Source area not found: {}",
2796 src_area_id
2797 ))
2798 })?;
2799 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2800 crate::types::BduError::InvalidArea(format!(
2801 "Destination area not found: {}",
2802 dst_area_id
2803 ))
2804 })?;
2805
2806 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2807 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2808 {
2809 let src_dimensions = (
2810 src_area.dimensions.width as usize,
2811 src_area.dimensions.height as usize,
2812 src_area.dimensions.depth as usize,
2813 );
2814 let dst_dimensions = (
2815 dst_area.dimensions.width as usize,
2816 dst_area.dimensions.height as usize,
2817 dst_area.dimensions.depth as usize,
2818 );
2819 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2820 let count = apply_projector_morphology_with_dimensions(
2821 npu,
2822 src_idx,
2823 dst_idx,
2824 src_dimensions,
2825 dst_dimensions,
2826 None,
2827 None,
2828 weight,
2829 psp,
2830 synapse_attractivity,
2831 synapse_type,
2832 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
2833 delay_bursts,
2834 )?;
2835 npu.rebuild_synapse_index();
2836 Ok(count as usize)
2837 } else {
2838 Ok(0)
2839 }
2840 }
2841 "block_to_block" => {
2842 tracing::warn!(
2843 target: "feagi-bdu",
2844 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
2845 src_idx, dst_idx
2846 );
2847 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2849 crate::types::BduError::InvalidArea(format!(
2850 "Source area not found: {}",
2851 src_area_id
2852 ))
2853 })?;
2854 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2855 crate::types::BduError::InvalidArea(format!(
2856 "Destination area not found: {}",
2857 dst_area_id
2858 ))
2859 })?;
2860
2861 let src_dimensions = (
2862 src_area.dimensions.width as usize,
2863 src_area.dimensions.height as usize,
2864 src_area.dimensions.depth as usize,
2865 );
2866 let dst_dimensions = (
2867 dst_area.dimensions.width as usize,
2868 dst_area.dimensions.height as usize,
2869 dst_area.dimensions.depth as usize,
2870 );
2871
2872 let scalar = if let Some(obj) = rule.as_object() {
2874 if let Some(scalar_arr) =
2876 obj.get("morphology_scalar").and_then(|v| v.as_array())
2877 {
2878 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
2880 } else {
2881 1 }
2883 } else if let Some(arr) = rule.as_array() {
2884 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
2886 } else {
2887 1 };
2889
2890 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
2893 let count = if estimated_neurons > 100_000 {
2894 let _ = npu;
2896
2897 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
2898 npu_arc,
2899 src_idx,
2900 dst_idx,
2901 src_dimensions,
2902 dst_dimensions,
2903 scalar, weight,
2905 psp,
2906 synapse_attractivity,
2907 synapse_type,
2908 delay_bursts,
2909 )? as usize
2910 } else {
2911 tracing::warn!(
2913 target: "feagi-bdu",
2914 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
2915 src_idx, dst_idx, src_dimensions, dst_dimensions
2916 );
2917 let count =
2918 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
2919 npu,
2920 src_idx,
2921 dst_idx,
2922 src_dimensions,
2923 dst_dimensions,
2924 scalar, weight,
2926 psp,
2927 synapse_attractivity,
2928 synapse_type,
2929 delay_bursts,
2930 )? as usize;
2931 tracing::warn!(
2932 target: "feagi-bdu",
2933 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
2934 count
2935 );
2936 if count > 0 {
2938 npu.rebuild_synapse_index();
2939 }
2940 count
2941 };
2942
2943 if count > 0 && estimated_neurons > 100_000 {
2945 let mut npu_lock = npu_arc.lock().unwrap();
2946 npu_lock.rebuild_synapse_index();
2947 }
2948
2949 Ok(count)
2950 }
2951 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
2952 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
2953 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2954 crate::types::BduError::InvalidArea(format!(
2955 "Source area not found: {}",
2956 src_area_id
2957 ))
2958 })?;
2959 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2960 crate::types::BduError::InvalidArea(format!(
2961 "Destination area not found: {}",
2962 dst_area_id
2963 ))
2964 })?;
2965
2966 let src_dimensions = (
2967 src_area.dimensions.width as usize,
2968 src_area.dimensions.height as usize,
2969 src_area.dimensions.depth as usize,
2970 );
2971 let dst_dimensions = (
2972 dst_area.dimensions.width as usize,
2973 dst_area.dimensions.height as usize,
2974 dst_area.dimensions.depth as usize,
2975 );
2976
2977 let (axis, mode) = match morphology_id {
2978 "bitmask_encoder_x" => (
2979 crate::connectivity::core_morphologies::BitmaskAxis::X,
2980 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2981 ),
2982 "bitmask_encoder_y" => (
2983 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2984 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2985 ),
2986 "bitmask_encoder_z" => (
2987 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2988 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2989 ),
2990 "bitmask_decoder_x" => (
2991 crate::connectivity::core_morphologies::BitmaskAxis::X,
2992 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2993 ),
2994 "bitmask_decoder_y" => (
2995 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2996 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2997 ),
2998 "bitmask_decoder_z" => (
2999 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3000 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3001 ),
3002 _ => unreachable!("matched bitmask morphology above"),
3003 };
3004
3005 let count =
3006 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
3007 npu,
3008 src_idx,
3009 dst_idx,
3010 src_dimensions,
3011 dst_dimensions,
3012 axis,
3013 mode,
3014 weight,
3015 psp,
3016 synapse_attractivity,
3017 synapse_type,
3018 delay_bursts,
3019 )?;
3020 if count > 0 {
3021 npu.rebuild_synapse_index();
3022 }
3023 Ok(count as usize)
3024 }
3025 "sweeper" => {
3026 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3027 crate::types::BduError::InvalidArea(format!(
3028 "Destination area not found: {}",
3029 dst_area_id
3030 ))
3031 })?;
3032 let dst_dimensions = (
3033 dst_area.dimensions.width as usize,
3034 dst_area.dimensions.height as usize,
3035 dst_area.dimensions.depth as usize,
3036 );
3037
3038 let count =
3039 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3040 npu,
3041 src_idx,
3042 dst_idx,
3043 dst_dimensions,
3044 weight,
3045 psp,
3046 synapse_attractivity,
3047 synapse_type,
3048 delay_bursts,
3049 )?;
3050 if count > 0 {
3051 npu.rebuild_synapse_index();
3052 }
3053 Ok(count as usize)
3054 }
3055 "last_to_first" => {
3056 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3057 crate::types::BduError::InvalidArea(format!(
3058 "Source area not found: {}",
3059 src_area_id
3060 ))
3061 })?;
3062 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3063 crate::types::BduError::InvalidArea(format!(
3064 "Destination area not found: {}",
3065 dst_area_id
3066 ))
3067 })?;
3068 let src_dimensions = (
3069 src_area.dimensions.width as usize,
3070 src_area.dimensions.height as usize,
3071 src_area.dimensions.depth as usize,
3072 );
3073 let dst_dimensions = (
3074 dst_area.dimensions.width as usize,
3075 dst_area.dimensions.height as usize,
3076 dst_area.dimensions.depth as usize,
3077 );
3078
3079 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3080 npu,
3081 src_idx,
3082 dst_idx,
3083 src_dimensions,
3084 dst_dimensions,
3085 weight,
3086 psp,
3087 synapse_attractivity,
3088 synapse_type,
3089 delay_bursts,
3090 )?;
3091 if count > 0 {
3092 npu.rebuild_synapse_index();
3093 }
3094 Ok(count as usize)
3095 }
3096 "rotator_z" => {
3097 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3098 crate::types::BduError::InvalidArea(format!(
3099 "Source area not found: {}",
3100 src_area_id
3101 ))
3102 })?;
3103 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3104 crate::types::BduError::InvalidArea(format!(
3105 "Destination area not found: {}",
3106 dst_area_id
3107 ))
3108 })?;
3109 let src_dimensions = (
3110 src_area.dimensions.width as usize,
3111 src_area.dimensions.height as usize,
3112 src_area.dimensions.depth as usize,
3113 );
3114 let dst_dimensions = (
3115 dst_area.dimensions.width as usize,
3116 dst_area.dimensions.height as usize,
3117 dst_area.dimensions.depth as usize,
3118 );
3119
3120 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3121 npu,
3122 src_idx,
3123 dst_idx,
3124 src_dimensions,
3125 dst_dimensions,
3126 weight,
3127 psp,
3128 synapse_attractivity,
3129 synapse_type,
3130 delay_bursts,
3131 )?;
3132 if count > 0 {
3133 npu.rebuild_synapse_index();
3134 }
3135 Ok(count as usize)
3136 }
3137 _ => {
3138 use tracing::debug;
3141 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3142 Ok(0)
3143 }
3144 }
3145 }
3146
3147 fn apply_single_morphology_rule(
3149 &mut self,
3150 src_area_id: &CorticalID,
3151 dst_area_id: &CorticalID,
3152 rule: &serde_json::Value,
3153 ) -> BduResult<usize> {
3154 let morphology_id = if let Some(arr) = rule.as_array() {
3156 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3157 } else if let Some(obj) = rule.as_object() {
3158 obj.get("morphology_id")
3159 .and_then(|v| v.as_str())
3160 .unwrap_or("")
3161 } else {
3162 return Ok(0);
3163 };
3164
3165 if morphology_id.is_empty() {
3166 return Ok(0);
3167 }
3168
3169 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3171 crate::types::BduError::InvalidMorphology(format!(
3172 "Morphology not found: {}",
3173 morphology_id
3174 ))
3175 })?;
3176
3177 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3179 crate::types::BduError::InvalidArea(format!(
3180 "Source area ID not found: {}",
3181 src_area_id
3182 ))
3183 })?;
3184 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3185 crate::types::BduError::InvalidArea(format!(
3186 "Destination area ID not found: {}",
3187 dst_area_id
3188 ))
3189 })?;
3190
3191 if let Some(ref npu_arc) = self.npu {
3193 let lock_start = std::time::Instant::now();
3194 let mut npu = npu_arc.lock().unwrap();
3195 let lock_wait = lock_start.elapsed();
3196 tracing::debug!(
3197 target: "feagi-bdu",
3198 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3199 lock_wait.as_secs_f64() * 1000.0,
3200 src_area_id,
3201 dst_area_id,
3202 morphology_id
3203 );
3204
3205 let (weight, psp, synapse_type, delay_bursts) =
3206 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3207
3208 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3210 obj.get("synapse_attractivity")
3211 .and_then(|v| v.as_u64())
3212 .unwrap_or(100) as u8
3213 } else {
3214 100 };
3216
3217 match morphology.morphology_type {
3218 feagi_evolutionary::MorphologyType::Functions => {
3219 tracing::warn!(
3220 target: "feagi-bdu",
3221 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3222 morphology_id
3223 );
3224 self.apply_function_morphology(
3227 morphology_id,
3228 rule,
3229 npu_arc,
3230 &mut npu,
3231 src_area_id,
3232 dst_area_id,
3233 *src_idx,
3234 *dst_idx,
3235 weight,
3236 psp,
3237 synapse_attractivity,
3238 synapse_type,
3239 delay_bursts,
3240 )
3241 }
3242 feagi_evolutionary::MorphologyType::Vectors => {
3243 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3244
3245 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3247 crate::types::BduError::InvalidArea(format!(
3248 "Destination area not found: {}",
3249 dst_area_id
3250 ))
3251 })?;
3252
3253 let dst_dimensions = (
3254 dst_area.dimensions.width as usize,
3255 dst_area.dimensions.height as usize,
3256 dst_area.dimensions.depth as usize,
3257 );
3258
3259 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3260 morphology.parameters
3261 {
3262 let vectors_tuples: Vec<(i32, i32, i32)> =
3264 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3265
3266 let count = apply_vectors_morphology_with_dimensions(
3267 &mut npu,
3268 *src_idx,
3269 *dst_idx,
3270 vectors_tuples,
3271 dst_dimensions,
3272 weight, psp, synapse_attractivity, synapse_type,
3276 delay_bursts,
3277 )?;
3278 npu.rebuild_synapse_index();
3281 Ok(count as usize)
3282 } else {
3283 Ok(0)
3284 }
3285 }
3286 feagi_evolutionary::MorphologyType::Patterns => {
3287 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3288 use crate::connectivity::rules::patterns::{
3289 Pattern3D, PatternElement as RulePatternElement,
3290 };
3291 use feagi_evolutionary::PatternElement as EvoPatternElement;
3292
3293 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3294 morphology.parameters
3295 else {
3296 return Ok(0);
3297 };
3298
3299 let convert_element =
3300 |element: &EvoPatternElement|
3301 -> crate::types::BduResult<RulePatternElement> {
3302 match element {
3303 EvoPatternElement::Value(value) => {
3304 if *value < 0 {
3305 return Err(crate::types::BduError::InvalidMorphology(
3306 format!(
3307 "Pattern morphology {} contains negative voxel coordinate {}",
3308 morphology_id, value
3309 ),
3310 ));
3311 }
3312 Ok(RulePatternElement::Exact(*value))
3313 }
3314 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3315 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3316 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3317 }
3318 };
3319
3320 let mut converted_patterns = Vec::with_capacity(patterns.len());
3321 for pattern_pair in patterns {
3322 if pattern_pair.len() != 2 {
3323 return Err(crate::types::BduError::InvalidMorphology(format!(
3324 "Pattern morphology {} must contain [src, dst] pairs",
3325 morphology_id
3326 )));
3327 }
3328
3329 let src_pattern = &pattern_pair[0];
3330 let dst_pattern = &pattern_pair[1];
3331
3332 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3333 return Err(crate::types::BduError::InvalidMorphology(format!(
3334 "Pattern morphology {} requires 3-axis patterns",
3335 morphology_id
3336 )));
3337 }
3338
3339 let src: Pattern3D = (
3340 convert_element(&src_pattern[0])?,
3341 convert_element(&src_pattern[1])?,
3342 convert_element(&src_pattern[2])?,
3343 );
3344 let dst: Pattern3D = (
3345 convert_element(&dst_pattern[0])?,
3346 convert_element(&dst_pattern[1])?,
3347 convert_element(&dst_pattern[2])?,
3348 );
3349
3350 converted_patterns.push((src, dst));
3351 }
3352
3353 let count = apply_patterns_morphology(
3354 &mut npu,
3355 *src_idx,
3356 *dst_idx,
3357 converted_patterns,
3358 weight,
3359 psp,
3360 synapse_attractivity,
3361 synapse_type,
3362 delay_bursts,
3363 )?;
3364 if count > 0 {
3365 npu.rebuild_synapse_index();
3366 }
3367 Ok(count as usize)
3368 }
3369 feagi_evolutionary::MorphologyType::Composite => {
3370 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3371 morphology.parameters
3372 else {
3373 return Ok(0);
3374 };
3375
3376 if morphology_id != "tile" {
3377 use tracing::debug;
3378 debug!(
3379 target: "feagi-bdu",
3380 "Composite morphology {} not yet implemented",
3381 morphology_id
3382 );
3383 return Ok(0);
3384 }
3385
3386 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3387 crate::types::BduError::InvalidArea(format!(
3388 "Source area not found: {}",
3389 src_area_id
3390 ))
3391 })?;
3392 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3393 crate::types::BduError::InvalidArea(format!(
3394 "Destination area not found: {}",
3395 dst_area_id
3396 ))
3397 })?;
3398 let src_dimensions = (
3399 src_area.dimensions.width as usize,
3400 src_area.dimensions.height as usize,
3401 src_area.dimensions.depth as usize,
3402 );
3403 let dst_dimensions = (
3404 dst_area.dimensions.width as usize,
3405 dst_area.dimensions.height as usize,
3406 dst_area.dimensions.depth as usize,
3407 );
3408
3409 let count =
3410 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3411 &mut npu,
3412 *src_idx,
3413 *dst_idx,
3414 src_dimensions,
3415 dst_dimensions,
3416 weight,
3417 psp,
3418 synapse_attractivity,
3419 synapse_type,
3420 delay_bursts,
3421 )?;
3422 if count > 0 {
3423 npu.rebuild_synapse_index();
3424 }
3425 Ok(count as usize)
3426 }
3427 }
3428 } else {
3429 Ok(0) }
3431 }
3432
3433 pub fn set_npu(
3446 &mut self,
3447 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3448 ) {
3449 self.npu = Some(Arc::clone(&npu));
3450 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3451
3452 #[cfg(not(feature = "wasm"))]
3455 {
3456 use feagi_state_manager::StateManager;
3457 let state_manager = StateManager::instance();
3458 let state_manager = state_manager.read();
3459 let core_state = state_manager.get_core_state();
3460 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3462 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3463 info!(
3464 target: "feagi-bdu",
3465 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3466 self.config.max_neurons, self.config.max_synapses
3467 );
3468 }
3469
3470 let existing_area_count = self.cortical_id_to_idx.len();
3477 if existing_area_count > 0 {
3478 match npu.lock() {
3479 Ok(mut npu_lock) => {
3480 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3481 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3482 }
3483 info!(
3484 target: "feagi-bdu",
3485 "🔁 Backfilled {} cortical area registrations into NPU",
3486 existing_area_count
3487 );
3488 }
3489 Err(e) => {
3490 warn!(
3491 target: "feagi-bdu",
3492 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3493 e
3494 );
3495 }
3496 }
3497 }
3498
3499 self.update_all_cached_stats();
3501 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3502 self.get_neuron_count(), self.get_synapse_count());
3503 }
3504
3505 pub fn has_npu(&self) -> bool {
3507 self.npu.is_some()
3508 }
3509
3510 pub fn get_npu(
3517 &self,
3518 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3519 {
3520 self.npu.as_ref()
3521 }
3522
3523 #[cfg(feature = "plasticity")]
3526 pub fn set_plasticity_executor(
3527 &mut self,
3528 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3529 ) {
3530 self.plasticity_executor = Some(executor);
3531 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3532 }
3533
3534 #[cfg(feature = "plasticity")]
3536 pub fn get_plasticity_executor(
3537 &self,
3538 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3539 self.plasticity_executor.as_ref()
3540 }
3541
3542 pub fn get_neuron_capacity(&self) -> usize {
3554 self.config.max_neurons
3556 }
3557
3558 pub fn get_synapse_capacity(&self) -> usize {
3570 self.config.max_synapses
3572 }
3573
3574 pub fn update_fatigue_index(&self) -> Option<u8> {
3589 let mut last_calc = match self.last_fatigue_calculation.lock() {
3591 Ok(guard) => guard,
3592 Err(_) => return None, };
3594
3595 let now = std::time::Instant::now();
3596 if now.duration_since(*last_calc).as_secs() < 2 {
3597 return None; }
3599 *last_calc = now;
3600 drop(last_calc);
3601
3602 let regular_neuron_count = self.get_neuron_count();
3604 let regular_neuron_capacity = self.get_neuron_capacity();
3605 let regular_neuron_util = if regular_neuron_capacity > 0 {
3606 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3607 } else {
3608 0
3609 };
3610
3611 let memory_neuron_util = match StateManager::instance().try_read() {
3615 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3616 None => {
3617 return None;
3619 }
3620 };
3621
3622 let synapse_count = self.get_synapse_count();
3624 let synapse_capacity = self.get_synapse_capacity();
3625 let synapse_util = if synapse_capacity > 0 {
3626 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3627 } else {
3628 0
3629 };
3630
3631 let fatigue_index = regular_neuron_util
3633 .max(memory_neuron_util)
3634 .max(synapse_util);
3635
3636 let current_fatigue_active = {
3638 StateManager::instance()
3640 .try_read()
3641 .map(|m| m.get_core_state().is_fatigue_active())
3642 .unwrap_or(false)
3643 };
3644
3645 let new_fatigue_active = if fatigue_index >= 85 {
3646 true
3647 } else if fatigue_index < 80 {
3648 false
3649 } else {
3650 current_fatigue_active };
3652
3653 if let Some(state_manager) = StateManager::instance().try_write() {
3657 let core_state = state_manager.get_core_state();
3658 core_state.set_fatigue_index(fatigue_index);
3659 core_state.set_fatigue_active(new_fatigue_active);
3660 core_state.set_regular_neuron_util(regular_neuron_util);
3661 core_state.set_memory_neuron_util(memory_neuron_util);
3662 core_state.set_synapse_util(synapse_util);
3663 } else {
3664 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3666 }
3667
3668 if let Some(ref npu) = self.npu {
3670 if let Ok(mut npu_lock) = npu.lock() {
3671 npu_lock.set_fatigue_active(new_fatigue_active);
3672 }
3673 }
3674
3675 trace!(
3676 target: "feagi-bdu",
3677 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3678 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3679 );
3680
3681 Some(fatigue_index)
3682 }
3683
3684 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3701 let area = self
3703 .cortical_areas
3704 .get(cortical_id)
3705 .ok_or_else(|| {
3706 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3707 })?
3708 .clone();
3709
3710 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3712 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3713 })?;
3714
3715 let npu = self
3717 .npu
3718 .as_ref()
3719 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3720
3721 use crate::models::CorticalAreaExt;
3724 let per_voxel_cnt = area.neurons_per_voxel();
3725 let firing_threshold = area.firing_threshold();
3726 let firing_threshold_increment_x = area.firing_threshold_increment_x();
3727 let firing_threshold_increment_y = area.firing_threshold_increment_y();
3728 let firing_threshold_increment_z = area.firing_threshold_increment_z();
3729 let firing_threshold_limit_raw = area.firing_threshold_limit();
3731 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
3732 f32::MAX } else {
3734 firing_threshold_limit_raw
3735 };
3736
3737 if firing_threshold_increment_x != 0.0
3739 || firing_threshold_increment_y != 0.0
3740 || firing_threshold_increment_z != 0.0
3741 {
3742 info!(
3743 target: "feagi-bdu",
3744 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
3745 cortical_id.as_base_64(),
3746 firing_threshold_increment_x,
3747 firing_threshold_increment_y,
3748 firing_threshold_increment_z
3749 );
3750 } else {
3751 if area.properties.contains_key("firing_threshold_increment_x")
3753 || area.properties.contains_key("firing_threshold_increment_y")
3754 || area.properties.contains_key("firing_threshold_increment_z")
3755 {
3756 info!(
3757 target: "feagi-bdu",
3758 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
3759 cortical_id.as_base_64(),
3760 area.properties.get("firing_threshold_increment_x"),
3761 area.properties.get("firing_threshold_increment_y"),
3762 area.properties.get("firing_threshold_increment_z")
3763 );
3764 }
3765 }
3766
3767 let leak_coefficient = area.leak_coefficient();
3768 let excitability = area.neuron_excitability();
3769 let refractory_period = area.refractory_period();
3770 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
3772 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
3773 u16::MAX } else {
3775 consecutive_fire_limit_raw
3776 };
3777 let snooze_length = area.snooze_period();
3778 let mp_charge_accumulation = area.mp_charge_accumulation();
3779
3780 let voxels = area.dimensions.width as usize
3782 * area.dimensions.height as usize
3783 * area.dimensions.depth as usize;
3784 let expected_neurons = voxels * per_voxel_cnt as usize;
3785
3786 trace!(
3787 target: "feagi-bdu",
3788 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
3789 cortical_id.as_base_64(),
3790 area.dimensions.width,
3791 area.dimensions.height,
3792 area.dimensions.depth,
3793 per_voxel_cnt,
3794 expected_neurons
3795 );
3796
3797 let mut npu_lock = npu
3800 .lock()
3801 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3802
3803 let neuron_count = npu_lock
3804 .create_cortical_area_neurons(
3805 *cortical_idx,
3806 area.dimensions.width,
3807 area.dimensions.height,
3808 area.dimensions.depth,
3809 per_voxel_cnt,
3810 firing_threshold,
3811 firing_threshold_increment_x,
3812 firing_threshold_increment_y,
3813 firing_threshold_increment_z,
3814 firing_threshold_limit,
3815 leak_coefficient,
3816 0.0, 0, refractory_period,
3819 excitability,
3820 consecutive_fire_limit,
3821 snooze_length,
3822 mp_charge_accumulation,
3823 )
3824 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?;
3825
3826 trace!(
3827 target: "feagi-bdu",
3828 "Created {} neurons for area {} via NPU",
3829 neuron_count,
3830 cortical_id.as_base_64()
3831 );
3832
3833 {
3836 let mut cache = self.cached_neuron_counts_per_area.write();
3837 cache
3838 .entry(*cortical_id)
3839 .or_insert_with(|| AtomicUsize::new(0))
3840 .store(neuron_count as usize, Ordering::Relaxed);
3841 }
3842
3843 let state_manager = StateManager::instance();
3845 let state_manager = state_manager.read();
3846 state_manager
3847 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
3848
3849 self.cached_neuron_count
3851 .fetch_add(neuron_count as usize, Ordering::Relaxed);
3852
3853 let state_manager = StateManager::instance();
3855 let state_manager = state_manager.read();
3856 let core_state = state_manager.get_core_state();
3857 core_state.add_neuron_count(neuron_count);
3858 core_state.add_regular_neuron_count(neuron_count);
3859
3860 Ok(neuron_count)
3868 }
3869
3870 #[allow(clippy::too_many_arguments)]
3894 pub fn add_neuron(
3895 &mut self,
3896 cortical_id: &CorticalID,
3897 x: u32,
3898 y: u32,
3899 z: u32,
3900 firing_threshold: f32,
3901 firing_threshold_limit: f32,
3902 leak_coefficient: f32,
3903 resting_potential: f32,
3904 neuron_type: u8,
3905 refractory_period: u16,
3906 excitability: f32,
3907 consecutive_fire_limit: u16,
3908 snooze_length: u16,
3909 mp_charge_accumulation: bool,
3910 ) -> BduResult<u64> {
3911 if !self.cortical_areas.contains_key(cortical_id) {
3913 return Err(BduError::InvalidArea(format!(
3914 "Cortical area {} not found",
3915 cortical_id
3916 )));
3917 }
3918
3919 let cortical_idx = *self
3920 .cortical_id_to_idx
3921 .get(cortical_id)
3922 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
3923
3924 let npu = self
3926 .npu
3927 .as_ref()
3928 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3929
3930 let mut npu_lock = npu
3931 .lock()
3932 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3933
3934 let neuron_id = npu_lock
3936 .add_neuron(
3937 firing_threshold,
3938 firing_threshold_limit,
3939 leak_coefficient,
3940 resting_potential,
3941 neuron_type as i32,
3942 refractory_period,
3943 excitability,
3944 consecutive_fire_limit,
3945 snooze_length,
3946 mp_charge_accumulation,
3947 cortical_idx,
3948 x,
3949 y,
3950 z,
3951 )
3952 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
3953
3954 trace!(
3955 target: "feagi-bdu",
3956 "Created neuron {} in area {} at ({}, {}, {})",
3957 neuron_id.0,
3958 cortical_id,
3959 x,
3960 y,
3961 z
3962 );
3963
3964 let state_manager = StateManager::instance();
3966 let state_manager = state_manager.read();
3967 let core_state = state_manager.get_core_state();
3968 core_state.add_neuron_count(1);
3969 core_state.add_regular_neuron_count(1);
3970 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3971
3972 Ok(neuron_id.0 as u64)
3973 }
3974
3975 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
3986 let npu = self
3988 .npu
3989 .as_ref()
3990 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3991
3992 let mut npu_lock = npu
3993 .lock()
3994 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3995
3996 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
3997 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
3998
3999 let deleted = npu_lock.delete_neuron(neuron_id as u32);
4000
4001 if deleted {
4002 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
4003
4004 let state_manager = StateManager::instance();
4006 let state_manager = state_manager.read();
4007 let core_state = state_manager.get_core_state();
4008 core_state.subtract_neuron_count(1);
4009 core_state.subtract_regular_neuron_count(1);
4010 if let Some(cortical_id) = cortical_id {
4011 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4012 }
4013
4014 }
4018
4019 Ok(deleted)
4020 }
4021
4022 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
4036 let src_area = self
4038 .cortical_areas
4039 .get(src_cortical_id)
4040 .ok_or_else(|| {
4041 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
4042 })?
4043 .clone();
4044
4045 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
4047 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
4048 _ => return Ok(0), };
4050
4051 let src_cortical_idx = *self
4052 .cortical_id_to_idx
4053 .get(src_cortical_id)
4054 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
4055
4056 let mut total_synapses = 0u32;
4057 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
4061 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
4063 Ok(id) => id,
4064 Err(_) => {
4065 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
4066 continue;
4067 }
4068 };
4069
4070 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
4072 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
4073 continue;
4074 }
4075
4076 let synapse_count =
4078 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
4079 total_synapses += synapse_count as u32;
4080
4081 upstream_updates.push((dst_cortical_id, src_cortical_idx));
4084 }
4085
4086 for (dst_id, src_idx) in upstream_updates {
4088 self.add_upstream_area(&dst_id, src_idx);
4089 }
4090
4091 trace!(
4092 target: "feagi-bdu",
4093 "Created {} synapses for area {} via NPU",
4094 total_synapses,
4095 src_cortical_id
4096 );
4097
4098 if total_synapses > 0 {
4101 let mut cache = self.cached_synapse_counts_per_area.write();
4102 cache
4103 .entry(*src_cortical_id)
4104 .or_insert_with(|| AtomicUsize::new(0))
4105 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4106 }
4107
4108 self.cached_synapse_count
4110 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4111
4112 if total_synapses > 0 {
4114 let state_manager = StateManager::instance();
4115 let state_manager = state_manager.read();
4116 let core_state = state_manager.get_core_state();
4117 core_state.add_synapse_count(total_synapses);
4118 }
4119
4120 Ok(total_synapses)
4121 }
4122
4123 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4142 if let Some(ref npu) = self.npu {
4143 if let Ok(npu_lock) = npu.lock() {
4144 npu_lock.is_neuron_valid(neuron_id as u32)
4146 } else {
4147 false
4148 }
4149 } else {
4150 false
4151 }
4152 }
4153
4154 pub fn get_neuron_count(&self) -> usize {
4166 if let Some(ref npu) = self.npu {
4168 if let Ok(npu_lock) = npu.try_lock() {
4169 let fresh_count = npu_lock.get_neuron_count();
4170 self.cached_neuron_count
4171 .store(fresh_count, Ordering::Relaxed);
4172 }
4173 }
4175
4176 self.cached_neuron_count.load(Ordering::Relaxed)
4178 }
4179
4180 pub fn update_cached_neuron_count(&self) {
4186 if let Some(ref npu) = self.npu {
4187 if let Ok(npu_lock) = npu.try_lock() {
4188 let count = npu_lock.get_neuron_count();
4189 self.cached_neuron_count.store(count, Ordering::Relaxed);
4190 }
4191 }
4192 }
4193
4194 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4198 let npu = self.npu.as_ref()?;
4199 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4200 let npu_lock = npu.lock().ok()?;
4201 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4202 drop(npu_lock);
4203
4204 let mut cache = self.cached_neuron_counts_per_area.write();
4205 cache
4206 .entry(*cortical_id)
4207 .or_insert_with(|| AtomicUsize::new(0))
4208 .store(count, Ordering::Relaxed);
4209
4210 let state_manager = StateManager::instance();
4212 let state_manager = state_manager.read();
4213 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4214
4215 self.update_cached_neuron_count();
4216
4217 Some(count)
4218 }
4219
4220 pub fn get_synapse_count(&self) -> usize {
4232 if let Some(ref npu) = self.npu {
4234 if let Ok(npu_lock) = npu.try_lock() {
4235 let fresh_count = npu_lock.get_synapse_count();
4236 self.cached_synapse_count
4237 .store(fresh_count, Ordering::Relaxed);
4238 }
4239 }
4241
4242 self.cached_synapse_count.load(Ordering::Relaxed)
4244 }
4245
4246 pub fn update_cached_synapse_count(&self) {
4252 if let Some(ref npu) = self.npu {
4253 if let Ok(npu_lock) = npu.try_lock() {
4254 let count = npu_lock.get_synapse_count();
4255 self.cached_synapse_count.store(count, Ordering::Relaxed);
4256 }
4257 }
4258 }
4259
4260 pub fn update_all_cached_stats(&self) {
4266 self.update_cached_neuron_count();
4267 self.update_cached_synapse_count();
4268 }
4269
4270 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4281 #[cfg(feature = "plasticity")]
4286 {
4287 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4288 return (0, 0, 0);
4289 }
4290 }
4291 if let Some(ref npu) = self.npu {
4292 if let Ok(npu_lock) = npu.lock() {
4293 npu_lock
4294 .get_neuron_coordinates(neuron_id as u32)
4295 .unwrap_or((0, 0, 0))
4296 } else {
4297 (0, 0, 0)
4298 }
4299 } else {
4300 (0, 0, 0)
4301 }
4302 }
4303
4304 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
4315 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
4316 }
4317
4318 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
4324 #[cfg(feature = "plasticity")]
4325 {
4326 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4327 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
4328 }
4329 }
4330 if let Some(ref npu) = self.npu {
4331 if let Ok(npu_lock) = npu.lock() {
4332 npu_lock.get_neuron_cortical_area(neuron_id as u32)
4333 } else {
4334 None
4335 }
4336 } else {
4337 None
4338 }
4339 }
4340
4341 #[cfg(feature = "plasticity")]
4343 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
4344 let exec = self.get_plasticity_executor()?;
4345 let guard = exec.lock().ok()?;
4346 guard
4347 .memory_neuron_detail(neuron_id)
4348 .map(|d| d.cortical_area_idx)
4349 }
4350
4351 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4362 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4364 Some(idx) => *idx,
4365 None => return Vec::new(),
4366 };
4367
4368 if let Some(ref npu) = self.npu {
4369 if let Ok(npu_lock) = npu.lock() {
4370 npu_lock
4372 .get_neurons_in_cortical_area(cortical_idx)
4373 .into_iter()
4374 .map(|id| id as u64)
4375 .collect()
4376 } else {
4377 Vec::new()
4378 }
4379 } else {
4380 Vec::new()
4381 }
4382 }
4383
4384 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4395 if let Some(ref npu) = self.npu {
4396 if let Ok(npu_lock) = npu.lock() {
4397 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4398 } else {
4399 Vec::new()
4400 }
4401 } else {
4402 Vec::new()
4403 }
4404 }
4405
4406 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4417 if let Some(ref npu) = self.npu {
4418 if let Ok(npu_lock) = npu.lock() {
4419 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4420 } else {
4421 Vec::new()
4422 }
4423 } else {
4424 Vec::new()
4425 }
4426 }
4427
4428 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4454 let cache = self.cached_neuron_counts_per_area.read();
4456 let base_count = cache
4457 .get(cortical_id)
4458 .map(|count| count.load(Ordering::Relaxed))
4459 .unwrap_or(0);
4460
4461 let memory_count = self
4463 .cortical_areas
4464 .get(cortical_id)
4465 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4466 .and_then(|_| {
4467 StateManager::instance()
4468 .try_read()
4469 .and_then(|state_manager| {
4470 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4471 })
4472 })
4473 .map(|stats| stats.neuron_count)
4474 .unwrap_or(0);
4475
4476 base_count.saturating_add(memory_count)
4477 }
4478
4479 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4486 let mut result = Vec::new();
4487
4488 for cortical_id in self.cortical_areas.keys() {
4489 let count = self.get_neuron_count_in_area(cortical_id);
4490 if count > 0 {
4491 result.push((cortical_id.to_string(), count));
4492 }
4493 }
4494
4495 result
4496 }
4497
4498 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4509 self.get_neuron_count_in_area(cortical_id) > 0
4510 }
4511
4512 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4528 let cache = self.cached_synapse_counts_per_area.read();
4530 cache
4531 .get(cortical_id)
4532 .map(|count| count.load(Ordering::Relaxed))
4533 .unwrap_or(0)
4534 }
4535
4536 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4546 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4547 return 0;
4548 }
4549
4550 if let Some(state_manager) = StateManager::instance().try_read() {
4551 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4552 return stats.incoming_synapse_count;
4553 }
4554 }
4555
4556 0
4557 }
4558
4559 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4569 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4570 return 0;
4571 }
4572
4573 if let Some(state_manager) = StateManager::instance().try_read() {
4574 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4575 return stats.outgoing_synapse_count;
4576 }
4577 }
4578
4579 0
4580 }
4581
4582 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4594 let synapses = self.get_outgoing_synapses(source_neuron_id);
4595 synapses
4596 .iter()
4597 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4598 }
4599
4600 pub fn get_connection_weight(
4612 &self,
4613 source_neuron_id: u64,
4614 target_neuron_id: u64,
4615 ) -> Option<f32> {
4616 let synapses = self.get_outgoing_synapses(source_neuron_id);
4617 synapses
4618 .iter()
4619 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4620 .map(|(_, weight, _, _)| *weight)
4621 }
4622
4623 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4634 let neurons = self.get_neurons_in_area(cortical_id);
4635 let neuron_count = neurons.len();
4636
4637 if neuron_count == 0 {
4638 return (0, 0, 0.0);
4639 }
4640
4641 let mut total_synapses = 0;
4642 for neuron_id in neurons {
4643 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4644 }
4645
4646 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4647
4648 (neuron_count, total_synapses, avg_synapses)
4649 }
4650
4651 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4662 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
4663 self.cortical_idx_to_id.get(&cortical_idx).copied()
4664 }
4665
4666 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4677 let area = match self.cortical_areas.get(cortical_id) {
4678 Some(a) => a,
4679 None => return 0.0,
4680 };
4681
4682 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4683 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4684
4685 if volume == 0 {
4686 return 0.0;
4687 }
4688
4689 neuron_count as f32 / volume as f32
4690 }
4691
4692 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
4699 let mut stats = Vec::new();
4700
4701 for cortical_id in self.cortical_areas.keys() {
4702 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4703 let synapse_count = self.get_synapse_count_in_area(cortical_id);
4704 let density = self.get_neuron_density(cortical_id);
4705
4706 stats.push((
4707 cortical_id.to_string(),
4708 neuron_count,
4709 synapse_count,
4710 density,
4711 ));
4712 }
4713
4714 stats
4715 }
4716
4717 pub fn get_config(&self) -> &ConnectomeConfig {
4723 &self.config
4724 }
4725
4726 pub fn set_config(&mut self, config: ConnectomeConfig) {
4728 self.config = config;
4729 }
4730
4731 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
4750 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
4751
4752 use feagi_structures::genomic::cortical_area::{
4753 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
4754 };
4755
4756 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
4758 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
4759 })?;
4760
4761 let core_position = (0, 0, 0).into();
4763
4764 let death_id = CoreCorticalType::Death.to_cortical_id();
4766 if !self.cortical_areas.contains_key(&death_id) {
4767 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
4768 let death_area = CorticalArea::new(
4769 death_id,
4770 0, "_death".to_string(),
4772 core_dimensions,
4773 core_position,
4774 CorticalAreaType::Core(CoreCorticalType::Death),
4775 )
4776 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
4777 match self.add_cortical_area(death_area) {
4778 Ok(idx) => {
4779 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
4780 }
4781 Err(e) => {
4782 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
4783 return Err(e);
4784 }
4785 }
4786 } else {
4787 info!(target: "feagi-bdu", " ✓ _death area already exists");
4788 }
4789
4790 let power_id = CoreCorticalType::Power.to_cortical_id();
4792 if !self.cortical_areas.contains_key(&power_id) {
4793 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
4794 let power_area = CorticalArea::new(
4795 power_id,
4796 1, "_power".to_string(),
4798 core_dimensions,
4799 core_position,
4800 CorticalAreaType::Core(CoreCorticalType::Power),
4801 )
4802 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
4803 match self.add_cortical_area(power_area) {
4804 Ok(idx) => {
4805 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
4806 }
4807 Err(e) => {
4808 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
4809 return Err(e);
4810 }
4811 }
4812 } else {
4813 info!(target: "feagi-bdu", " ✓ _power area already exists");
4814 }
4815
4816 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
4818 if !self.cortical_areas.contains_key(&fatigue_id) {
4819 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
4820 let fatigue_area = CorticalArea::new(
4821 fatigue_id,
4822 2, "_fatigue".to_string(),
4824 core_dimensions,
4825 core_position,
4826 CorticalAreaType::Core(CoreCorticalType::Fatigue),
4827 )
4828 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
4829 match self.add_cortical_area(fatigue_area) {
4830 Ok(idx) => {
4831 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
4832 }
4833 Err(e) => {
4834 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
4835 return Err(e);
4836 }
4837 }
4838 } else {
4839 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
4840 }
4841
4842 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
4843 Ok(())
4844 }
4845
4846 #[deprecated(
4863 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
4864 )]
4865 #[allow(deprecated)]
4866 pub fn save_genome_to_json(
4867 &self,
4868 genome_id: Option<String>,
4869 genome_title: Option<String>,
4870 ) -> BduResult<String> {
4871 let mut brain_regions_with_parents = std::collections::HashMap::new();
4873
4874 for region_id in self.brain_regions.get_all_region_ids() {
4875 if let Some(region) = self.brain_regions.get_region(region_id) {
4876 let parent_id = self
4877 .brain_regions
4878 .get_parent(region_id)
4879 .map(|s| s.to_string());
4880 brain_regions_with_parents
4881 .insert(region_id.to_string(), (region.clone(), parent_id));
4882 }
4883 }
4884
4885 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
4887 &self.cortical_areas,
4888 &brain_regions_with_parents,
4889 genome_id,
4890 genome_title,
4891 )?)
4892 }
4893
4894 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
4925 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
4926
4927 self.cortical_areas.clear();
4929 self.cortical_id_to_idx.clear();
4930 self.cortical_idx_to_id.clear();
4931 self.next_cortical_idx = 3;
4933 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4934
4935 self.brain_regions = BrainRegionHierarchy::new();
4937
4938 if let Some(ref npu) = self.npu {
4940 let mut npu_lock = npu
4941 .lock()
4942 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4943 npu_lock
4944 .reset_for_new_genome()
4945 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
4946 }
4947
4948 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
4949 Ok(())
4950 }
4951
4952 pub fn resize_for_genome(
4962 &mut self,
4963 genome: &feagi_evolutionary::RuntimeGenome,
4964 ) -> BduResult<()> {
4965 self.morphology_registry = genome.morphologies.clone();
4967 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
4968
4969 let required_neurons = genome.stats.innate_neuron_count;
4971 let required_synapses = genome.stats.innate_synapse_count;
4972
4973 info!(target: "feagi-bdu",
4974 "Genome requires: {} neurons, {} synapses",
4975 required_neurons,
4976 required_synapses
4977 );
4978
4979 let mut total_voxels = 0;
4981 for area in genome.cortical_areas.values() {
4982 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4983 }
4984
4985 info!(target: "feagi-bdu",
4986 "Genome has {} cortical areas with {} total voxels",
4987 genome.cortical_areas.len(),
4988 total_voxels
4989 );
4990
4991 Ok(())
4996 }
4997
4998 pub fn create_synapse(
5017 &mut self,
5018 source_neuron_id: u64,
5019 target_neuron_id: u64,
5020 weight: f32,
5021 psp: f32,
5022 synapse_type: u8,
5023 ) -> BduResult<()> {
5024 let npu = self
5026 .npu
5027 .as_ref()
5028 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5029
5030 let mut npu_lock = npu
5031 .lock()
5032 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5033
5034 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5036 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5037
5038 if !source_exists {
5039 return Err(BduError::InvalidNeuron(format!(
5040 "Source neuron {} not found",
5041 source_neuron_id
5042 )));
5043 }
5044 if !target_exists {
5045 return Err(BduError::InvalidNeuron(format!(
5046 "Target neuron {} not found",
5047 target_neuron_id
5048 )));
5049 }
5050
5051 let syn_type = if synapse_type == 0 {
5053 feagi_npu_neural::synapse::SynapseType::Excitatory
5054 } else {
5055 feagi_npu_neural::synapse::SynapseType::Inhibitory
5056 };
5057
5058 let synapse_idx = npu_lock
5059 .add_synapse(
5060 NeuronId(source_neuron_id as u32),
5061 NeuronId(target_neuron_id as u32),
5062 feagi_npu_neural::types::SynapticWeight(weight),
5063 feagi_npu_neural::types::SynapticPsp(psp),
5064 syn_type,
5065 0,
5066 1,
5067 )
5068 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
5069
5070 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
5071 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
5072
5073 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5074 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5075 let source_cortical_id =
5076 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5077 let target_cortical_id =
5078 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5079
5080 let state_manager = StateManager::instance();
5081 let state_manager = state_manager.read();
5082 let core_state = state_manager.get_core_state();
5083 core_state.add_synapse_count(1);
5084 if let Some(cortical_id) = source_cortical_id {
5085 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5086 }
5087 if let Some(cortical_id) = target_cortical_id {
5088 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5089 }
5090
5091 Ok(())
5096 }
5097
5098 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
5101 if let Some(ref npu) = self.npu {
5102 if let Ok(mut npu_lock) = npu.lock() {
5103 let mut psp_uniform_flags = ahash::AHashMap::new();
5105 let mut mp_driven_psp_flags = ahash::AHashMap::new();
5106
5107 for (cortical_id, area) in &self.cortical_areas {
5108 let default_psp_uniform = *cortical_id
5111 == CoreCorticalType::Power.to_cortical_id()
5112 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
5113 let psp_uniform = area
5114 .get_property("psp_uniform_distribution")
5115 .and_then(|v| v.as_bool())
5116 .unwrap_or(default_psp_uniform);
5117 psp_uniform_flags.insert(*cortical_id, psp_uniform);
5118
5119 let mp_driven_psp = area
5121 .get_property("mp_driven_psp")
5122 .and_then(|v| v.as_bool())
5123 .unwrap_or(false);
5124 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
5125 }
5126
5127 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
5129 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5130
5131 trace!(
5132 target: "feagi-bdu",
5133 "Synchronized cortical area flags to NPU ({} areas)",
5134 self.cortical_areas.len()
5135 );
5136 }
5137 }
5138
5139 Ok(())
5140 }
5141
5142 pub fn get_synapse(
5154 &self,
5155 source_neuron_id: u64,
5156 target_neuron_id: u64,
5157 ) -> Option<(f32, f32, u8)> {
5158 let npu = self.npu.as_ref()?;
5160 let npu_lock = npu.lock().ok()?;
5161
5162 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
5165
5166 for (source_id, weight, psp, synapse_type) in incoming {
5168 if source_id == source_neuron_id as u32 {
5169 return Some((weight, psp, synapse_type));
5170 }
5171 }
5172
5173 None
5174 }
5175
5176 pub fn update_synapse_weight(
5189 &mut self,
5190 source_neuron_id: u64,
5191 target_neuron_id: u64,
5192 new_weight: f32,
5193 ) -> BduResult<()> {
5194 let npu = self
5196 .npu
5197 .as_ref()
5198 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5199
5200 let mut npu_lock = npu
5201 .lock()
5202 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5203
5204 let updated = npu_lock.update_synapse_weight(
5206 NeuronId(source_neuron_id as u32),
5207 NeuronId(target_neuron_id as u32),
5208 feagi_npu_neural::types::SynapticWeight(new_weight),
5209 );
5210
5211 if updated {
5212 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
5213 Ok(())
5214 } else {
5215 Err(BduError::InvalidSynapse(format!(
5216 "Synapse {} -> {} not found",
5217 source_neuron_id, target_neuron_id
5218 )))
5219 }
5220 }
5221
5222 pub fn remove_synapse(
5234 &mut self,
5235 source_neuron_id: u64,
5236 target_neuron_id: u64,
5237 ) -> BduResult<bool> {
5238 let npu = self
5240 .npu
5241 .as_ref()
5242 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5243
5244 let mut npu_lock = npu
5245 .lock()
5246 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5247
5248 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5249 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5250 let source_cortical_id =
5251 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5252 let target_cortical_id =
5253 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5254
5255 let removed = npu_lock.remove_synapse(
5257 NeuronId(source_neuron_id as u32),
5258 NeuronId(target_neuron_id as u32),
5259 );
5260
5261 if removed {
5262 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
5263
5264 let state_manager = StateManager::instance();
5266 let state_manager = state_manager.read();
5267 let core_state = state_manager.get_core_state();
5268 core_state.subtract_synapse_count(1);
5269 if let Some(cortical_id) = source_cortical_id {
5270 state_manager
5271 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5272 }
5273 if let Some(cortical_id) = target_cortical_id {
5274 state_manager
5275 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5276 }
5277 }
5278
5279 Ok(removed)
5280 }
5281
5282 pub fn batch_create_neurons(
5300 &mut self,
5301 cortical_id: &CorticalID,
5302 neurons: Vec<NeuronData>,
5303 ) -> BduResult<Vec<u64>> {
5304 let npu = self
5306 .npu
5307 .as_ref()
5308 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5309
5310 let mut npu_lock = npu
5311 .lock()
5312 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5313
5314 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5316 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5317 })?;
5318 let cortical_idx = area.cortical_idx;
5319
5320 let count = neurons.len();
5321
5322 let mut x_coords = Vec::with_capacity(count);
5324 let mut y_coords = Vec::with_capacity(count);
5325 let mut z_coords = Vec::with_capacity(count);
5326 let mut firing_thresholds = Vec::with_capacity(count);
5327 let mut threshold_limits = Vec::with_capacity(count);
5328 let mut leak_coeffs = Vec::with_capacity(count);
5329 let mut resting_potentials = Vec::with_capacity(count);
5330 let mut neuron_types = Vec::with_capacity(count);
5331 let mut refractory_periods = Vec::with_capacity(count);
5332 let mut excitabilities = Vec::with_capacity(count);
5333 let mut consec_fire_limits = Vec::with_capacity(count);
5334 let mut snooze_lengths = Vec::with_capacity(count);
5335 let mut mp_accums = Vec::with_capacity(count);
5336 let mut cortical_areas = Vec::with_capacity(count);
5337
5338 for (
5339 x,
5340 y,
5341 z,
5342 threshold,
5343 threshold_limit,
5344 leak,
5345 resting,
5346 ntype,
5347 refract,
5348 excit,
5349 consec_limit,
5350 snooze,
5351 mp_accum,
5352 ) in neurons
5353 {
5354 x_coords.push(x);
5355 y_coords.push(y);
5356 z_coords.push(z);
5357 firing_thresholds.push(threshold);
5358 threshold_limits.push(threshold_limit);
5359 leak_coeffs.push(leak);
5360 resting_potentials.push(resting);
5361 neuron_types.push(ntype);
5362 refractory_periods.push(refract);
5363 excitabilities.push(excit);
5364 consec_fire_limits.push(consec_limit);
5365 snooze_lengths.push(snooze);
5366 mp_accums.push(mp_accum);
5367 cortical_areas.push(cortical_idx);
5368 }
5369
5370 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5372
5373 let firing_thresholds_t = firing_thresholds;
5378 let threshold_limits_t = threshold_limits;
5379 let resting_potentials_t = resting_potentials;
5380 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5381 firing_thresholds_t,
5382 threshold_limits_t,
5383 leak_coeffs,
5384 resting_potentials_t,
5385 neuron_types,
5386 refractory_periods,
5387 excitabilities,
5388 consec_fire_limits,
5389 snooze_lengths,
5390 mp_accums,
5391 cortical_areas,
5392 x_coords,
5393 y_coords,
5394 z_coords,
5395 );
5396
5397 let mut neuron_ids = Vec::with_capacity(count);
5399 for i in 0..neurons_created {
5400 neuron_ids.push((first_neuron_id + i) as u64);
5401 }
5402
5403 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5404
5405 let state_manager = StateManager::instance();
5407 let state_manager = state_manager.read();
5408 let core_state = state_manager.get_core_state();
5409 core_state.add_neuron_count(neurons_created);
5410 core_state.add_regular_neuron_count(neurons_created);
5411 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5412
5413 {
5415 let mut cache = self.cached_neuron_counts_per_area.write();
5416 cache
5417 .entry(*cortical_id)
5418 .or_insert_with(|| AtomicUsize::new(0))
5419 .fetch_add(count, Ordering::Relaxed);
5420 }
5421
5422 Ok(neuron_ids)
5423 }
5424
5425 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5436 let npu = self
5438 .npu
5439 .as_ref()
5440 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5441
5442 let mut npu_lock = npu
5443 .lock()
5444 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5445
5446 let mut deleted_count = 0;
5447 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5448 std::collections::HashMap::new();
5449
5450 for neuron_id in neuron_ids {
5453 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5454 let cortical_id =
5455 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5456
5457 if npu_lock.delete_neuron(neuron_id as u32) {
5458 deleted_count += 1;
5459 if let Some(cortical_id) = cortical_id {
5460 let key = cortical_id.as_base_64();
5461 *per_area_deleted.entry(key).or_insert(0) += 1;
5462 }
5463 }
5464 }
5465
5466 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5467
5468 if deleted_count > 0 {
5470 let state_manager = StateManager::instance();
5471 let state_manager = state_manager.read();
5472 let core_state = state_manager.get_core_state();
5473 core_state.subtract_neuron_count(deleted_count as u32);
5474 core_state.subtract_regular_neuron_count(deleted_count as u32);
5475 for (cortical_id, count) in per_area_deleted {
5476 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5477 }
5478 }
5479
5480 Ok(deleted_count)
5487 }
5488
5489 pub fn update_neuron_properties(
5508 &mut self,
5509 neuron_id: u64,
5510 firing_threshold: Option<f32>,
5511 leak_coefficient: Option<f32>,
5512 resting_potential: Option<f32>,
5513 excitability: Option<f32>,
5514 ) -> BduResult<()> {
5515 let npu = self
5517 .npu
5518 .as_ref()
5519 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5520
5521 let mut npu_lock = npu
5522 .lock()
5523 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5524
5525 let neuron_id_u32 = neuron_id as u32;
5526
5527 let mut updated = false;
5529
5530 if let Some(threshold) = firing_threshold {
5532 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5533 updated = true;
5534 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5535 } else if !updated {
5536 return Err(BduError::InvalidNeuron(format!(
5537 "Neuron {} not found",
5538 neuron_id
5539 )));
5540 }
5541 }
5542
5543 if let Some(leak) = leak_coefficient {
5544 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5545 updated = true;
5546 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5547 } else if !updated {
5548 return Err(BduError::InvalidNeuron(format!(
5549 "Neuron {} not found",
5550 neuron_id
5551 )));
5552 }
5553 }
5554
5555 if let Some(resting) = resting_potential {
5556 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5557 updated = true;
5558 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5559 } else if !updated {
5560 return Err(BduError::InvalidNeuron(format!(
5561 "Neuron {} not found",
5562 neuron_id
5563 )));
5564 }
5565 }
5566
5567 if let Some(excit) = excitability {
5568 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
5569 updated = true;
5570 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
5571 } else if !updated {
5572 return Err(BduError::InvalidNeuron(format!(
5573 "Neuron {} not found",
5574 neuron_id
5575 )));
5576 }
5577 }
5578
5579 if !updated {
5580 return Err(BduError::Internal(
5581 "No properties provided for update".to_string(),
5582 ));
5583 }
5584
5585 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
5586
5587 Ok(())
5588 }
5589
5590 pub fn set_neuron_firing_threshold(
5602 &mut self,
5603 neuron_id: u64,
5604 new_threshold: f32,
5605 ) -> BduResult<()> {
5606 let npu = self
5608 .npu
5609 .as_ref()
5610 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5611
5612 let mut npu_lock = npu
5613 .lock()
5614 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5615
5616 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
5618 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
5619 Ok(())
5620 } else {
5621 Err(BduError::InvalidNeuron(format!(
5622 "Neuron {} not found",
5623 neuron_id
5624 )))
5625 }
5626 }
5627
5628 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
5643 self.cortical_areas
5644 .values()
5645 .find(|area| area.name == name)
5646 .cloned()
5647 }
5648
5649 pub fn resize_cortical_area(
5666 &mut self,
5667 cortical_id: &CorticalID,
5668 new_dimensions: CorticalAreaDimensions,
5669 ) -> BduResult<()> {
5670 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
5672 return Err(BduError::InvalidArea(format!(
5673 "Invalid dimensions: {:?} (all must be > 0)",
5674 new_dimensions
5675 )));
5676 }
5677
5678 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
5680 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5681 })?;
5682
5683 let old_dimensions = area.dimensions;
5684 area.dimensions = new_dimensions;
5685
5686 info!(target: "feagi-bdu",
5690 "Resized cortical area {} from {:?} to {:?}",
5691 cortical_id,
5692 old_dimensions,
5693 new_dimensions
5694 );
5695
5696 self.refresh_cortical_area_hashes(false, true);
5697
5698 Ok(())
5699 }
5700
5701 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
5712 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
5713 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5714 })?;
5715
5716 Ok(region
5718 .cortical_areas
5719 .iter()
5720 .map(|id| id.as_base_64())
5721 .collect())
5722 }
5723
5724 pub fn update_brain_region(
5737 &mut self,
5738 region_id: &str,
5739 new_name: Option<String>,
5740 new_description: Option<String>,
5741 ) -> BduResult<()> {
5742 let region = self
5743 .brain_regions
5744 .get_region_mut(region_id)
5745 .ok_or_else(|| {
5746 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5747 })?;
5748
5749 if let Some(name) = new_name {
5750 region.name = name;
5751 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
5752 }
5753
5754 if let Some(desc) = new_description {
5755 region
5757 .properties
5758 .insert("description".to_string(), serde_json::json!(desc));
5759 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
5760 }
5761
5762 info!(target: "feagi-bdu","Updated brain region {}", region_id);
5763
5764 self.refresh_brain_regions_hash();
5765
5766 Ok(())
5767 }
5768
5769 pub fn update_brain_region_properties(
5783 &mut self,
5784 region_id: &str,
5785 properties: std::collections::HashMap<String, serde_json::Value>,
5786 ) -> BduResult<Option<BrainRegionIoRegistry>> {
5787 use tracing::{debug, info};
5788
5789 let should_recompute_io = properties
5790 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
5791 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
5792
5793 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
5794 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
5795 {
5796 let region_snapshot = self
5797 .brain_regions
5798 .get_region(region_id)
5799 .ok_or_else(|| {
5800 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5801 })?
5802 .clone();
5803 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
5804 ®ion_snapshot,
5805 &properties,
5806 )?;
5807 crate::region_io_designation::validate_merged_designations_against_connectivity(
5808 self,
5809 ®ion_snapshot,
5810 &merged_in,
5811 &merged_out,
5812 )?;
5813 }
5814
5815 let region = self
5816 .brain_regions
5817 .get_region_mut(region_id)
5818 .ok_or_else(|| {
5819 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5820 })?;
5821
5822 for (key, value) in properties {
5823 match key.as_str() {
5824 "title" | "name" | "region_title" => {
5826 if let Some(name) = value.as_str() {
5827 region.name = name.to_string();
5828 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
5829 }
5830 }
5831 "coordinate_3d" | "coordinates_3d" => {
5832 region
5833 .properties
5834 .insert("coordinate_3d".to_string(), value.clone());
5835 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
5836 }
5837 "coordinate_2d" | "coordinates_2d" => {
5838 region
5839 .properties
5840 .insert("coordinate_2d".to_string(), value.clone());
5841 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
5842 }
5843 "description" => {
5844 region
5845 .properties
5846 .insert("description".to_string(), value.clone());
5847 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
5848 }
5849 "region_type" => {
5850 if let Some(type_str) = value.as_str() {
5851 region.region_type = feagi_structures::genomic::RegionType::Undefined;
5854 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
5855 }
5856 }
5857 _ => {
5859 region.properties.insert(key.clone(), value.clone());
5860 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
5861 }
5862 }
5863 }
5864
5865 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
5866
5867 if should_recompute_io {
5870 let registry = self.recompute_brain_region_io_registry()?;
5871 return Ok(Some(registry));
5872 }
5873
5874 self.refresh_brain_regions_hash();
5878
5879 Ok(None)
5880 }
5881
5882 pub fn get_neuron_by_coordinates(
5900 &self,
5901 cortical_id: &CorticalID,
5902 x: u32,
5903 y: u32,
5904 z: u32,
5905 ) -> Option<u64> {
5906 let area = self.get_cortical_area(cortical_id)?;
5908 let cortical_idx = area.cortical_idx;
5909
5910 let npu = self.npu.as_ref()?;
5912 let npu_lock = npu.lock().ok()?;
5913
5914 npu_lock
5915 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
5916 .map(|id| id as u64)
5917 }
5918
5919 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
5930 let npu = self.npu.as_ref()?;
5931 let npu_lock = npu.lock().ok()?;
5932
5933 let neuron_count = npu_lock.get_neuron_count();
5935 if (neuron_id as usize) >= neuron_count {
5936 return None;
5937 }
5938
5939 Some(
5940 npu_lock
5941 .get_neuron_coordinates(neuron_id as u32)
5942 .unwrap_or((0, 0, 0)),
5943 )
5944 }
5945
5946 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
5957 let npu = self.npu.as_ref()?;
5958 let npu_lock = npu.lock().ok()?;
5959
5960 let neuron_count = npu_lock.get_neuron_count();
5962 if (neuron_id as usize) >= neuron_count {
5963 return None;
5964 }
5965
5966 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
5967
5968 self.cortical_areas
5970 .values()
5971 .find(|area| area.cortical_idx == cortical_idx)
5972 .map(|area| area.cortical_id)
5973 }
5974
5975 pub fn get_neuron_properties(
5986 &self,
5987 neuron_id: u64,
5988 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5989 let npu = self.npu.as_ref()?;
5990 let npu_lock = npu.lock().ok()?;
5991
5992 let neuron_id_u32 = neuron_id as u32;
5993 let idx = neuron_id as usize;
5994
5995 let neuron_count = npu_lock.get_neuron_count();
5997 if idx >= neuron_count {
5998 return None;
5999 }
6000
6001 let mut properties = std::collections::HashMap::new();
6002
6003 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
6005
6006 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
6008 properties.insert("x".to_string(), serde_json::json!(x));
6009 properties.insert("y".to_string(), serde_json::json!(y));
6010 properties.insert("z".to_string(), serde_json::json!(z));
6011
6012 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
6014 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
6015
6016 properties.insert(
6018 "mp_charge_accumulation".to_string(),
6019 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
6020 );
6021 properties.insert(
6022 "neuron_type".to_string(),
6023 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
6024 );
6025 let (mp_drv, psp_uni) = self
6026 .cortical_idx_to_id
6027 .get(&cortical_idx)
6028 .map(|cid| {
6029 (
6030 npu_lock.get_mp_driven_psp_for_cortical(cid),
6031 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
6032 )
6033 })
6034 .unwrap_or((false, false));
6035 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
6036 properties.insert(
6037 "psp_uniform_distribution".to_string(),
6038 serde_json::json!(psp_uni),
6039 );
6040
6041 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
6044 .get_neuron_state(NeuronId(neuron_id_u32))
6045 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
6046 properties.insert(
6047 "consecutive_fire_count".to_string(),
6048 serde_json::json!(consec_count),
6049 );
6050 properties.insert(
6051 "consecutive_fire_limit".to_string(),
6052 serde_json::json!(consec_limit),
6053 );
6054 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
6055 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
6056 properties.insert("threshold".to_string(), serde_json::json!(threshold));
6057 properties.insert(
6058 "refractory_countdown".to_string(),
6059 serde_json::json!(refract_countdown),
6060 );
6061
6062 properties.insert(
6064 "leak_coefficient".to_string(),
6065 serde_json::json!(npu_lock
6066 .get_neuron_property_by_index(idx, "leak_coefficient")
6067 .unwrap_or(0.0)),
6068 );
6069 properties.insert(
6070 "resting_potential".to_string(),
6071 serde_json::json!(npu_lock
6072 .get_neuron_property_by_index(idx, "resting_potential")
6073 .unwrap_or(0.0)),
6074 );
6075 properties.insert(
6076 "excitability".to_string(),
6077 serde_json::json!(npu_lock
6078 .get_neuron_property_by_index(idx, "excitability")
6079 .unwrap_or(0.0)),
6080 );
6081 properties.insert(
6082 "threshold_limit".to_string(),
6083 serde_json::json!(npu_lock
6084 .get_neuron_property_by_index(idx, "threshold_limit")
6085 .unwrap_or(0.0)),
6086 );
6087 properties.insert(
6088 "refractory_period".to_string(),
6089 serde_json::json!(npu_lock
6090 .get_neuron_property_u16_by_index(idx, "refractory_period")
6091 .unwrap_or(0)),
6092 );
6093
6094 Some(properties)
6095 }
6096
6097 pub fn get_neuron_property(
6109 &self,
6110 neuron_id: u64,
6111 property_name: &str,
6112 ) -> Option<serde_json::Value> {
6113 self.get_neuron_properties(neuron_id)?
6114 .get(property_name)
6115 .cloned()
6116 }
6117
6118 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
6129 self.cortical_areas.keys().copied().collect()
6130 }
6131
6132 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
6139 self.cortical_areas
6140 .values()
6141 .map(|area| area.cortical_idx)
6142 .collect()
6143 }
6144
6145 pub fn get_cortical_area_names(&self) -> Vec<String> {
6152 self.cortical_areas
6153 .values()
6154 .map(|area| area.name.clone())
6155 .collect()
6156 }
6157
6158 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
6165 use crate::models::CorticalAreaExt;
6166 self.cortical_areas
6167 .values()
6168 .filter(|area| area.is_input_area())
6169 .map(|area| area.cortical_id)
6170 .collect()
6171 }
6172
6173 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
6180 use crate::models::CorticalAreaExt;
6181 self.cortical_areas
6182 .values()
6183 .filter(|area| area.is_output_area())
6184 .map(|area| area.cortical_id)
6185 .collect()
6186 }
6187
6188 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
6195 self.cortical_areas
6196 .values()
6197 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
6198 (
6199 max_w.max(area.dimensions.width as usize),
6200 max_h.max(area.dimensions.height as usize),
6201 max_d.max(area.dimensions.depth as usize),
6202 )
6203 })
6204 }
6205
6206 pub fn get_cortical_area_properties(
6217 &self,
6218 cortical_id: &CorticalID,
6219 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6220 let area = self.get_cortical_area(cortical_id)?;
6221
6222 let mut properties = std::collections::HashMap::new();
6223 properties.insert(
6224 "cortical_id".to_string(),
6225 serde_json::json!(area.cortical_id),
6226 );
6227 properties.insert(
6228 "cortical_id_s".to_string(),
6229 serde_json::json!(area.cortical_id.to_string()),
6230 );
6231 properties.insert(
6232 "cortical_idx".to_string(),
6233 serde_json::json!(area.cortical_idx),
6234 );
6235 properties.insert("name".to_string(), serde_json::json!(area.name));
6236 use crate::models::CorticalAreaExt;
6237 properties.insert(
6238 "area_type".to_string(),
6239 serde_json::json!(area.get_cortical_group()),
6240 );
6241 properties.insert(
6242 "dimensions".to_string(),
6243 serde_json::json!({
6244 "width": area.dimensions.width,
6245 "height": area.dimensions.height,
6246 "depth": area.dimensions.depth,
6247 }),
6248 );
6249 properties.insert("position".to_string(), serde_json::json!(area.position));
6250
6251 for (key, value) in &area.properties {
6253 properties.insert(key.clone(), value.clone());
6254 }
6255
6256 properties.extend(area.properties.clone());
6258
6259 Some(properties)
6260 }
6261
6262 pub fn get_all_cortical_area_properties(
6269 &self,
6270 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
6271 self.cortical_areas
6272 .keys()
6273 .filter_map(|id| self.get_cortical_area_properties(id))
6274 .collect()
6275 }
6276
6277 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
6288 self.brain_regions
6289 .get_all_region_ids()
6290 .into_iter()
6291 .cloned()
6292 .collect()
6293 }
6294
6295 pub fn get_brain_region_names(&self) -> Vec<String> {
6302 self.brain_regions
6303 .get_all_region_ids()
6304 .iter()
6305 .filter_map(|id| {
6306 self.brain_regions
6307 .get_region(id)
6308 .map(|region| region.name.clone())
6309 })
6310 .collect()
6311 }
6312
6313 pub fn get_brain_region_properties(
6324 &self,
6325 region_id: &str,
6326 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6327 let region = self.brain_regions.get_region(region_id)?;
6328
6329 let mut properties = std::collections::HashMap::new();
6330 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
6331 properties.insert("name".to_string(), serde_json::json!(region.name));
6332 properties.insert(
6333 "region_type".to_string(),
6334 serde_json::json!(format!("{:?}", region.region_type)),
6335 );
6336 properties.insert(
6337 "cortical_areas".to_string(),
6338 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
6339 );
6340
6341 properties.extend(region.properties.clone());
6343
6344 Some(properties)
6345 }
6346
6347 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
6358 self.cortical_areas.contains_key(cortical_id)
6359 }
6360
6361 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6372 self.brain_regions.get_region(region_id).is_some()
6373 }
6374
6375 pub fn get_brain_region_count(&self) -> usize {
6382 self.brain_regions.region_count()
6383 }
6384
6385 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6396 self.get_neurons_in_area(cortical_id)
6400 }
6401}
6402
6403impl std::fmt::Debug for ConnectomeManager {
6405 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6406 f.debug_struct("ConnectomeManager")
6407 .field("cortical_areas", &self.cortical_areas.len())
6408 .field("next_cortical_idx", &self.next_cortical_idx)
6409 .field("brain_regions", &self.brain_regions)
6410 .field(
6411 "npu",
6412 &if self.npu.is_some() {
6413 "Connected"
6414 } else {
6415 "Not connected"
6416 },
6417 )
6418 .field("initialized", &self.initialized)
6419 .finish()
6420 }
6421}
6422
6423#[cfg(test)]
6424mod tests {
6425 use super::*;
6426 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6427
6428 #[test]
6429 fn test_singleton_instance() {
6430 let instance1 = ConnectomeManager::instance();
6431 let instance2 = ConnectomeManager::instance();
6432
6433 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6435 }
6436
6437 #[test]
6438 fn test_add_cortical_area() {
6439 ConnectomeManager::reset_for_testing();
6440
6441 let instance = ConnectomeManager::instance();
6442 let mut manager = instance.write();
6443
6444 use feagi_structures::genomic::cortical_area::{
6445 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6446 };
6447 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6449 let area = CorticalArea::new(
6450 cortical_id,
6451 0,
6452 "Visual Input".to_string(),
6453 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6454 (0, 0, 0).into(),
6455 cortical_type,
6456 )
6457 .unwrap();
6458
6459 let initial_count = manager.get_cortical_area_count();
6460 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6461
6462 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6463 assert!(manager.has_cortical_area(&cortical_id));
6464 assert!(manager.is_initialized());
6465 }
6466
6467 #[test]
6468 fn test_cortical_area_lookups() {
6469 ConnectomeManager::reset_for_testing();
6470
6471 let instance = ConnectomeManager::instance();
6472 let mut manager = instance.write();
6473
6474 use feagi_structures::genomic::cortical_area::{
6475 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6476 };
6477 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6479 let area = CorticalArea::new(
6480 cortical_id,
6481 0,
6482 "Test Area".to_string(),
6483 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6484 (0, 0, 0).into(),
6485 cortical_type,
6486 )
6487 .unwrap();
6488
6489 let cortical_idx = manager.add_cortical_area(area).unwrap();
6490
6491 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6493
6494 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6496
6497 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6499 assert_eq!(retrieved_area.name, "Test Area");
6500 }
6501
6502 #[test]
6503 fn test_remove_cortical_area() {
6504 ConnectomeManager::reset_for_testing();
6505
6506 let instance = ConnectomeManager::instance();
6507 let mut manager = instance.write();
6508
6509 use feagi_structures::genomic::cortical_area::{
6510 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6511 };
6512 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6513
6514 if manager.has_cortical_area(&cortical_id) {
6516 manager.remove_cortical_area(&cortical_id).unwrap();
6517 }
6518
6519 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6520 let area = CorticalArea::new(
6521 cortical_id,
6522 0,
6523 "Test".to_string(),
6524 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6525 (0, 0, 0).into(),
6526 cortical_type,
6527 )
6528 .unwrap();
6529
6530 let initial_count = manager.get_cortical_area_count();
6531 manager.add_cortical_area(area).unwrap();
6532 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6533
6534 manager.remove_cortical_area(&cortical_id).unwrap();
6535 assert_eq!(manager.get_cortical_area_count(), initial_count);
6536 assert!(!manager.has_cortical_area(&cortical_id));
6537 }
6538
6539 #[test]
6540 fn test_duplicate_area_error() {
6541 ConnectomeManager::reset_for_testing();
6542
6543 let instance = ConnectomeManager::instance();
6544 let mut manager = instance.write();
6545
6546 use feagi_structures::genomic::cortical_area::{
6547 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6548 };
6549 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
6552 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6553 let area1 = CorticalArea::new(
6554 cortical_id,
6555 0,
6556 "First".to_string(),
6557 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6558 (0, 0, 0).into(),
6559 cortical_type,
6560 )
6561 .unwrap();
6562
6563 let area2 = CorticalArea::new(
6564 cortical_id, 1,
6566 "Second".to_string(),
6567 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6568 (0, 0, 0).into(),
6569 cortical_type,
6570 )
6571 .unwrap();
6572
6573 manager.add_cortical_area(area1).unwrap();
6574 let result = manager.add_cortical_area(area2);
6575
6576 assert!(result.is_err());
6577 }
6578
6579 #[test]
6580 fn test_brain_region_management() {
6581 ConnectomeManager::reset_for_testing();
6582
6583 let instance = ConnectomeManager::instance();
6584 let mut manager = instance.write();
6585
6586 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
6587 let region_id_str = region_id.to_string();
6588 let root = BrainRegion::new(
6589 region_id,
6590 "Root".to_string(),
6591 feagi_structures::genomic::brain_regions::RegionType::Undefined,
6592 )
6593 .unwrap();
6594
6595 let initial_count = manager.get_brain_region_ids().len();
6596 manager.add_brain_region(root, None).unwrap();
6597
6598 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
6599 assert!(manager.get_brain_region(®ion_id_str).is_some());
6600 }
6601
6602 #[test]
6603 fn test_synapse_operations() {
6604 use feagi_npu_burst_engine::npu::RustNPU;
6605 use feagi_npu_burst_engine::TracingMutex;
6606 use std::sync::Arc;
6607
6608 use feagi_npu_burst_engine::backend::CPUBackend;
6610 use feagi_npu_burst_engine::DynamicNPU;
6611 use feagi_npu_runtime::StdRuntime;
6612
6613 let runtime = StdRuntime;
6614 let backend = CPUBackend::new();
6615 let npu_result =
6616 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
6617 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
6618 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
6619
6620 use feagi_structures::genomic::cortical_area::{
6622 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6623 };
6624 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6626 let area = CorticalArea::new(
6627 cortical_id,
6628 0, "Test Area".to_string(),
6630 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
6631 (0, 0, 0).into(), cortical_type,
6633 )
6634 .unwrap();
6635 let cortical_idx = manager.add_cortical_area(area).unwrap();
6636
6637 if let Some(npu_arc) = manager.get_npu() {
6639 if let Ok(mut npu_guard) = npu_arc.try_lock() {
6640 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
6641 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
6642 }
6643 }
6644 }
6645
6646 let neuron1_id = manager
6648 .add_neuron(
6649 &cortical_id,
6650 0,
6651 0,
6652 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
6664 .unwrap();
6665
6666 let neuron2_id = manager
6667 .add_neuron(
6668 &cortical_id,
6669 1,
6670 0,
6671 0, 100.0,
6673 f32::MAX, 0.1,
6675 -60.0,
6676 0,
6677 2,
6678 1.0,
6679 5,
6680 10,
6681 false,
6682 )
6683 .unwrap();
6684
6685 manager
6687 .create_synapse(
6688 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
6692 .unwrap();
6693
6694 println!("✅ Synapse creation test passed");
6697 }
6698
6699 #[test]
6700 fn test_apply_cortical_mapping_missing_rules_is_ok() {
6701 let mut manager = ConnectomeManager::new_for_testing();
6704
6705 use feagi_structures::genomic::cortical_area::{
6706 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6707 };
6708
6709 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
6710 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
6711
6712 let src_area = CorticalArea::new(
6713 src_id,
6714 0,
6715 "src".to_string(),
6716 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6717 (0, 0, 0).into(),
6718 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6719 )
6720 .unwrap();
6721
6722 let dst_area = CorticalArea::new(
6723 dst_id,
6724 1,
6725 "dst".to_string(),
6726 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6727 (0, 0, 0).into(),
6728 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6729 )
6730 .unwrap();
6731
6732 manager.add_cortical_area(src_area).unwrap();
6733 manager.add_cortical_area(dst_area).unwrap();
6734
6735 let count = manager
6737 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6738 .unwrap();
6739 assert_eq!(count, 0);
6740
6741 manager
6743 .update_cortical_mapping(
6744 &src_id,
6745 &dst_id,
6746 vec![serde_json::json!({"morphology_id":"m1"})],
6747 )
6748 .unwrap();
6749 manager
6750 .update_cortical_mapping(&src_id, &dst_id, vec![])
6751 .unwrap();
6752
6753 let count2 = manager
6754 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6755 .unwrap();
6756 assert_eq!(count2, 0);
6757 }
6758
6759 #[test]
6760 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
6761 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6762 let mapping_dst = serde_json::json!({
6763 "csrc0002": [
6764 {"morphology_id": "m1"}
6765 ]
6766 });
6767 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
6768
6769 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
6770 .expect("legacy destination key should resolve");
6771 assert_eq!(rules.len(), 1);
6772 assert_eq!(
6773 rules[0].get("morphology_id").and_then(|v| v.as_str()),
6774 Some("m1")
6775 );
6776 }
6777
6778 #[test]
6779 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
6780 use feagi_npu_burst_engine::backend::CPUBackend;
6781 use feagi_npu_burst_engine::RustNPU;
6782 use feagi_npu_burst_engine::TracingMutex;
6783 use feagi_npu_runtime::StdRuntime;
6784 use feagi_structures::genomic::cortical_area::{
6785 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6786 };
6787 use std::sync::Arc;
6788
6789 let runtime = StdRuntime;
6790 let backend = CPUBackend::new();
6791 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6792 let dyn_npu = Arc::new(TracingMutex::new(
6793 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6794 "TestNPU",
6795 ));
6796 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6797
6798 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
6799 let area = CorticalArea::new(
6800 area_id,
6801 0,
6802 "n".to_string(),
6803 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6804 (0, 0, 0).into(),
6805 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6806 )
6807 .unwrap();
6808
6809 manager.add_cortical_area(area).unwrap();
6810 let nid = manager
6811 .add_neuron(
6812 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
6813 )
6814 .unwrap();
6815
6816 let props = manager
6817 .get_neuron_properties(nid)
6818 .expect("neuron properties");
6819 for key in [
6820 "consecutive_fire_count",
6821 "consecutive_fire_limit",
6822 "snooze_period",
6823 "membrane_potential",
6824 "threshold",
6825 "refractory_countdown",
6826 "mp_charge_accumulation",
6827 "neuron_type",
6828 "mp_driven_psp",
6829 "psp_uniform_distribution",
6830 "leak_coefficient",
6831 "resting_potential",
6832 "excitability",
6833 "threshold_limit",
6834 "refractory_period",
6835 ] {
6836 assert!(props.contains_key(key), "missing neuron state key: {key}");
6837 }
6838 }
6839
6840 #[test]
6841 fn test_mapping_deletion_prunes_synapses_between_areas() {
6842 use feagi_npu_burst_engine::backend::CPUBackend;
6843 use feagi_npu_burst_engine::RustNPU;
6844 use feagi_npu_burst_engine::TracingMutex;
6845 use feagi_npu_runtime::StdRuntime;
6846 use feagi_structures::genomic::cortical_area::{
6847 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6848 };
6849 use std::sync::Arc;
6850
6851 let runtime = StdRuntime;
6853 let backend = CPUBackend::new();
6854 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6855 let dyn_npu = Arc::new(TracingMutex::new(
6856 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6857 "TestNPU",
6858 ));
6859 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6860
6861 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
6863 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
6864
6865 let src_area = CorticalArea::new(
6866 src_id,
6867 0,
6868 "src".to_string(),
6869 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6870 (0, 0, 0).into(),
6871 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6872 )
6873 .unwrap();
6874 let dst_area = CorticalArea::new(
6875 dst_id,
6876 1,
6877 "dst".to_string(),
6878 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6879 (0, 0, 0).into(),
6880 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6881 )
6882 .unwrap();
6883
6884 manager.add_cortical_area(src_area).unwrap();
6885 manager.add_cortical_area(dst_area).unwrap();
6886
6887 let s0 = manager
6889 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6890 .unwrap();
6891 let s1 = manager
6892 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6893 .unwrap();
6894 let t0 = manager
6895 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6896 .unwrap();
6897 let t1 = manager
6898 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6899 .unwrap();
6900
6901 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
6903 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
6904
6905 {
6907 let mut npu = dyn_npu.lock().unwrap();
6908 npu.rebuild_synapse_index();
6909 assert_eq!(npu.get_synapse_count(), 2);
6910 }
6911
6912 manager
6914 .update_cortical_mapping(&src_id, &dst_id, vec![])
6915 .unwrap();
6916 let created = manager
6917 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6918 .unwrap();
6919 assert_eq!(created, 0);
6920
6921 {
6923 let mut npu = dyn_npu.lock().unwrap();
6924 npu.rebuild_synapse_index();
6926 assert_eq!(npu.get_synapse_count(), 0);
6927 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6928 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6929 }
6930 }
6931
6932 #[test]
6933 fn test_mapping_update_prunes_synapses_between_areas() {
6934 use feagi_npu_burst_engine::backend::CPUBackend;
6935 use feagi_npu_burst_engine::RustNPU;
6936 use feagi_npu_burst_engine::TracingMutex;
6937 use feagi_npu_runtime::StdRuntime;
6938 use feagi_structures::genomic::cortical_area::{
6939 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6940 };
6941 use std::sync::Arc;
6942
6943 let runtime = StdRuntime;
6945 let backend = CPUBackend::new();
6946 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6947 let dyn_npu = Arc::new(TracingMutex::new(
6948 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6949 "TestNPU",
6950 ));
6951 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6952
6953 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6955
6956 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
6959 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
6960
6961 let src_area = CorticalArea::new(
6962 src_id,
6963 0,
6964 "src".to_string(),
6965 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6966 (0, 0, 0).into(),
6967 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6968 )
6969 .unwrap();
6970 let dst_area = CorticalArea::new(
6971 dst_id,
6972 0,
6973 "dst".to_string(),
6974 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6975 (0, 0, 0).into(),
6976 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6977 )
6978 .unwrap();
6979
6980 manager.add_cortical_area(src_area).unwrap();
6981 manager.add_cortical_area(dst_area).unwrap();
6982
6983 let s0 = manager
6985 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6986 .unwrap();
6987 let s1 = manager
6988 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6989 .unwrap();
6990 let t0 = manager
6991 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6992 .unwrap();
6993 let t1 = manager
6994 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6995 .unwrap();
6996
6997 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
6999 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7000
7001 {
7003 let mut npu = dyn_npu.lock().unwrap();
7004 npu.rebuild_synapse_index();
7005 assert_eq!(npu.get_synapse_count(), 2);
7006 }
7007
7008 manager
7014 .update_cortical_mapping(
7015 &src_id,
7016 &dst_id,
7017 vec![serde_json::json!({
7018 "morphology_id": "episodic_memory",
7019 "morphology_scalar": [1],
7020 "postSynapticCurrent_multiplier": 1,
7021 "plasticity_flag": false,
7022 "plasticity_constant": 0,
7023 "ltp_multiplier": 0,
7024 "ltd_multiplier": 0,
7025 "plasticity_window": 0,
7026 })],
7027 )
7028 .unwrap();
7029 let created = manager
7030 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7031 .unwrap();
7032 assert_eq!(created, 0);
7033
7034 {
7036 let mut npu = dyn_npu.lock().unwrap();
7037 npu.rebuild_synapse_index();
7039 assert_eq!(npu.get_synapse_count(), 0);
7040 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7041 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7042 }
7043 }
7044
7045 #[test]
7046 fn test_upstream_area_tracking() {
7047 use crate::models::cortical_area::CorticalArea;
7049 use feagi_npu_burst_engine::backend::CPUBackend;
7050 use feagi_npu_burst_engine::TracingMutex;
7051 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7052 use feagi_npu_runtime::StdRuntime;
7053 use feagi_structures::genomic::cortical_area::{
7054 CorticalAreaDimensions, CorticalAreaType, CorticalID,
7055 };
7056
7057 let runtime = StdRuntime;
7059 let backend = CPUBackend::new();
7060 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7061 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7062 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7063
7064 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7067
7068 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
7070 let src_area = CorticalArea::new(
7071 src_id,
7072 0,
7073 "Source Area".to_string(),
7074 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7075 (0, 0, 0).into(),
7076 CorticalAreaType::Custom(
7077 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7078 ),
7079 )
7080 .unwrap();
7081 let src_idx = manager.add_cortical_area(src_area).unwrap();
7082
7083 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
7085 let dst_area = CorticalArea::new(
7086 dst_id,
7087 0,
7088 "Dest Area".to_string(),
7089 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7090 (0, 0, 0).into(),
7091 CorticalAreaType::Custom(
7092 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7093 ),
7094 )
7095 .unwrap();
7096 manager.add_cortical_area(dst_area).unwrap();
7097
7098 {
7100 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
7101 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
7102 assert!(
7103 upstream.as_array().unwrap().is_empty(),
7104 "Upstream areas should be empty initially"
7105 );
7106 }
7107
7108 let mapping_data = vec![serde_json::json!({
7110 "morphology_id": "episodic_memory",
7111 "morphology_scalar": 1,
7112 "postSynapticCurrent_multiplier": 1.0,
7113 })];
7114 manager
7115 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7116 .unwrap();
7117 manager
7118 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7119 .unwrap();
7120
7121 {
7123 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7124 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
7125 assert_eq!(
7126 upstream_areas[0], src_idx,
7127 "Upstream area should be src_idx"
7128 );
7129 }
7130
7131 manager
7133 .update_cortical_mapping(&src_id, &dst_id, vec![])
7134 .unwrap();
7135 manager
7136 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7137 .unwrap();
7138
7139 {
7141 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7142 assert_eq!(
7143 upstream_areas.len(),
7144 0,
7145 "Should have 0 upstream areas after deletion"
7146 );
7147 }
7148 }
7149
7150 #[test]
7151 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
7152 use crate::models::cortical_area::CorticalArea;
7153 use feagi_npu_burst_engine::backend::CPUBackend;
7154 use feagi_npu_burst_engine::TracingMutex;
7155 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7156 use feagi_npu_runtime::StdRuntime;
7157 use feagi_structures::genomic::cortical_area::{
7158 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7159 };
7160 use std::sync::Arc;
7161
7162 let runtime = StdRuntime;
7163 let backend = CPUBackend::new();
7164 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7165 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7166 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7167 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7168
7169 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7170 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
7171 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7172 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
7173
7174 let a1_area = CorticalArea::new(
7175 a1_id,
7176 0,
7177 "A1".to_string(),
7178 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7179 (0, 0, 0).into(),
7180 CorticalAreaType::Custom(
7181 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7182 ),
7183 )
7184 .unwrap();
7185 let a2_area = CorticalArea::new(
7186 a2_id,
7187 0,
7188 "A2".to_string(),
7189 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7190 (0, 0, 0).into(),
7191 CorticalAreaType::Custom(
7192 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7193 ),
7194 )
7195 .unwrap();
7196
7197 let mut m1_area = CorticalArea::new(
7198 m1_id,
7199 0,
7200 "M1".to_string(),
7201 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7202 (0, 0, 0).into(),
7203 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7204 )
7205 .unwrap();
7206 m1_area
7207 .properties
7208 .insert("is_mem_type".to_string(), serde_json::json!(true));
7209 m1_area
7210 .properties
7211 .insert("temporal_depth".to_string(), serde_json::json!(1));
7212
7213 let mut m2_area = CorticalArea::new(
7214 m2_id,
7215 0,
7216 "M2".to_string(),
7217 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7218 (0, 0, 0).into(),
7219 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7220 )
7221 .unwrap();
7222 m2_area
7223 .properties
7224 .insert("is_mem_type".to_string(), serde_json::json!(true));
7225 m2_area
7226 .properties
7227 .insert("temporal_depth".to_string(), serde_json::json!(1));
7228
7229 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
7230 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
7231 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
7232 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
7233
7234 manager
7235 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7236 .unwrap();
7237 manager
7238 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7239 .unwrap();
7240
7241 let episodic_mapping = vec![serde_json::json!({
7242 "morphology_id": "episodic_memory",
7243 "morphology_scalar": 1,
7244 "postSynapticCurrent_multiplier": 1.0,
7245 })];
7246 manager
7247 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
7248 .unwrap();
7249 manager
7250 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
7251 .unwrap();
7252 manager
7253 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
7254 .unwrap();
7255 manager
7256 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
7257 .unwrap();
7258
7259 let assoc_mapping = vec![serde_json::json!({
7260 "morphology_id": "associative_memory",
7261 "morphology_scalar": 1,
7262 "postSynapticCurrent_multiplier": 1.0,
7263 "plasticity_flag": true,
7264 "plasticity_constant": 1,
7265 "ltp_multiplier": 1,
7266 "ltd_multiplier": 1,
7267 "plasticity_window": 5,
7268 })];
7269 manager
7270 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
7271 .unwrap();
7272 manager
7273 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7274 .unwrap();
7275 manager
7277 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
7278 .unwrap();
7279 manager
7280 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
7281 .unwrap();
7282
7283 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7284 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7285 assert_eq!(
7286 upstream_m1.len(),
7287 2,
7288 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
7289 );
7290 assert_eq!(
7291 upstream_m2.len(),
7292 2,
7293 "M2 should have A2 and M1 as upstreams"
7294 );
7295
7296 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
7297 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
7298
7299 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7300 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7301 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
7302 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
7303 assert!(upstream_m1.contains(&a1_idx));
7304 assert!(upstream_m1.contains(&m2_idx));
7305 assert!(upstream_m2.contains(&a2_idx));
7306 assert!(upstream_m2.contains(&m1_idx));
7307
7308 {
7310 let mut npu_lock = dyn_npu.lock().unwrap();
7311 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
7312 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
7313 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
7314 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
7315 npu_lock.process_burst().expect("Burst processing failed");
7316 }
7317
7318 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7319 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7320 assert_eq!(
7321 upstream_m1.len(),
7322 2,
7323 "M1 should keep 2 upstreams after firing"
7324 );
7325 assert_eq!(
7326 upstream_m2.len(),
7327 2,
7328 "M2 should keep 2 upstreams after firing"
7329 );
7330 }
7331
7332 #[test]
7333 fn test_memory_twin_created_for_memory_mapping() {
7334 use crate::models::cortical_area::CorticalArea;
7335 use feagi_npu_burst_engine::backend::CPUBackend;
7336 use feagi_npu_burst_engine::TracingMutex;
7337 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7338 use feagi_npu_runtime::StdRuntime;
7339 use feagi_structures::genomic::cortical_area::{
7340 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7341 MemoryCorticalType,
7342 };
7343 use std::sync::Arc;
7344
7345 let runtime = StdRuntime;
7346 let backend = CPUBackend::new();
7347 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7348 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7349 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7350 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7351
7352 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
7353 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
7354
7355 let src_area = CorticalArea::new(
7356 src_id,
7357 0,
7358 "Source Area".to_string(),
7359 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7360 (0, 0, 0).into(),
7361 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7362 )
7363 .unwrap();
7364 let mut dst_area = CorticalArea::new(
7365 dst_id,
7366 0,
7367 "Memory Area".to_string(),
7368 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7369 (0, 0, 0).into(),
7370 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7371 )
7372 .unwrap();
7373 dst_area
7374 .properties
7375 .insert("is_mem_type".to_string(), serde_json::json!(true));
7376 dst_area
7377 .properties
7378 .insert("temporal_depth".to_string(), serde_json::json!(1));
7379
7380 manager.add_cortical_area(src_area).unwrap();
7381 manager.add_cortical_area(dst_area).unwrap();
7382
7383 let mapping_data = vec![serde_json::json!({
7384 "morphology_id": "episodic_memory",
7385 "morphology_scalar": 1,
7386 "postSynapticCurrent_multiplier": 1.0,
7387 })];
7388 manager
7389 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7390 .unwrap();
7391 manager
7392 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7393 .unwrap();
7394
7395 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7396 let twin_map = memory_area
7397 .properties
7398 .get("memory_twin_areas")
7399 .and_then(|v| v.as_object())
7400 .expect("memory_twin_areas should be set");
7401 let twin_id_str = twin_map
7402 .get(&src_id.as_base_64())
7403 .and_then(|v| v.as_str())
7404 .expect("Missing twin entry for upstream area");
7405 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7406 let mapping = memory_area
7407 .properties
7408 .get("cortical_mapping_dst")
7409 .and_then(|v| v.as_object())
7410 .and_then(|map| map.get(&twin_id.as_base_64()))
7411 .and_then(|v| v.as_array())
7412 .expect("Missing memory replay mapping for twin area");
7413 let uses_replay = mapping.iter().any(|rule| {
7414 rule.get("morphology_id")
7415 .and_then(|v| v.as_str())
7416 .is_some_and(|id| id == "memory_replay")
7417 });
7418 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7419
7420 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7421 assert!(matches!(
7422 twin_area.cortical_type,
7423 CorticalAreaType::Custom(_)
7424 ));
7425 assert_eq!(
7426 twin_area
7427 .properties
7428 .get("memory_twin_of")
7429 .and_then(|v| v.as_str()),
7430 Some(src_id.as_base_64().as_str())
7431 );
7432 assert_eq!(
7433 twin_area
7434 .properties
7435 .get("memory_twin_for")
7436 .and_then(|v| v.as_str()),
7437 Some(dst_id.as_base_64().as_str())
7438 );
7439 }
7440
7441 #[test]
7442 fn test_associative_memory_between_memory_areas_creates_synapses() {
7443 use crate::models::cortical_area::CorticalArea;
7444 use feagi_npu_burst_engine::backend::CPUBackend;
7445 use feagi_npu_burst_engine::TracingMutex;
7446 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7447 use feagi_npu_runtime::StdRuntime;
7448 use feagi_structures::genomic::cortical_area::{
7449 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7450 };
7451 use std::sync::Arc;
7452
7453 let runtime = StdRuntime;
7454 let backend = CPUBackend::new();
7455 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7456 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7457 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7458 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7459
7460 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
7461 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
7462
7463 let mut m1_area = CorticalArea::new(
7464 m1_id,
7465 0,
7466 "Memory M1".to_string(),
7467 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7468 (0, 0, 0).into(),
7469 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7470 )
7471 .unwrap();
7472 m1_area
7473 .properties
7474 .insert("is_mem_type".to_string(), serde_json::json!(true));
7475 m1_area
7476 .properties
7477 .insert("temporal_depth".to_string(), serde_json::json!(1));
7478
7479 let mut m2_area = CorticalArea::new(
7480 m2_id,
7481 0,
7482 "Memory M2".to_string(),
7483 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7484 (0, 0, 0).into(),
7485 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7486 )
7487 .unwrap();
7488 m2_area
7489 .properties
7490 .insert("is_mem_type".to_string(), serde_json::json!(true));
7491 m2_area
7492 .properties
7493 .insert("temporal_depth".to_string(), serde_json::json!(1));
7494
7495 manager.add_cortical_area(m1_area).unwrap();
7496 manager.add_cortical_area(m2_area).unwrap();
7497
7498 manager
7499 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7500 .unwrap();
7501 manager
7502 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7503 .unwrap();
7504
7505 let mapping_data = vec![serde_json::json!({
7506 "morphology_id": "associative_memory",
7507 "morphology_scalar": 1,
7508 "postSynapticCurrent_multiplier": 1.0,
7509 "plasticity_flag": true,
7510 "plasticity_constant": 1,
7511 "ltp_multiplier": 1,
7512 "ltd_multiplier": 1,
7513 "plasticity_window": 5,
7514 })];
7515 manager
7516 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
7517 .unwrap();
7518 let created = manager
7519 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7520 .unwrap();
7521 assert!(
7522 created > 0,
7523 "Expected associative memory mapping between memory areas to create synapses"
7524 );
7525 let npu_guard = dyn_npu.lock().unwrap();
7526 let assoc_tagged =
7527 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
7528 assert!(
7529 assoc_tagged >= 1,
7530 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
7531 );
7532 }
7533
7534 #[test]
7535 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7536 use crate::models::cortical_area::CorticalArea;
7537 use feagi_npu_burst_engine::backend::CPUBackend;
7538 use feagi_npu_burst_engine::TracingMutex;
7539 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7540 use feagi_npu_runtime::StdRuntime;
7541 use feagi_structures::genomic::cortical_area::{
7542 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7543 MemoryCorticalType,
7544 };
7545 use std::sync::Arc;
7546
7547 let runtime = StdRuntime;
7548 let backend = CPUBackend::new();
7549 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7550 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7551 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7552 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7553
7554 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7555 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7556
7557 let src_area = CorticalArea::new(
7558 src_id,
7559 0,
7560 "Source Area".to_string(),
7561 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7562 (0, 0, 0).into(),
7563 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7564 )
7565 .unwrap();
7566 let mut mem_area = CorticalArea::new(
7567 mem_id,
7568 0,
7569 "Memory Area".to_string(),
7570 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7571 (0, 0, 0).into(),
7572 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7573 )
7574 .unwrap();
7575 mem_area
7576 .properties
7577 .insert("is_mem_type".to_string(), serde_json::json!(true));
7578 mem_area
7579 .properties
7580 .insert("temporal_depth".to_string(), serde_json::json!(1));
7581
7582 manager.add_cortical_area(src_area).unwrap();
7583 manager.add_cortical_area(mem_area).unwrap();
7584
7585 let twin_id = manager
7586 .build_memory_twin_id(&mem_id, &src_id)
7587 .expect("Failed to build twin id");
7588 let twin_area = CorticalArea::new(
7589 twin_id,
7590 0,
7591 "Source Area_twin".to_string(),
7592 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7593 (0, 0, 0).into(),
7594 CorticalAreaType::Custom(
7595 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7596 ),
7597 )
7598 .unwrap();
7599 manager.add_cortical_area(twin_area).unwrap();
7600
7601 let repaired = manager
7602 .ensure_memory_twin_area(&mem_id, &src_id)
7603 .expect("Failed to repair twin");
7604 assert_eq!(repaired, twin_id);
7605
7606 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
7607 let twin_map = mem_area
7608 .properties
7609 .get("memory_twin_areas")
7610 .and_then(|v| v.as_object())
7611 .expect("memory_twin_areas should be set");
7612 let twin_id_str = twin_map
7613 .get(&src_id.as_base_64())
7614 .and_then(|v| v.as_str())
7615 .expect("Missing twin entry for upstream area");
7616 assert_eq!(twin_id_str, twin_id.as_base_64());
7617
7618 let replay_map = mem_area
7619 .properties
7620 .get("cortical_mapping_dst")
7621 .and_then(|v| v.as_object())
7622 .and_then(|map| map.get(&twin_id.as_base_64()))
7623 .and_then(|v| v.as_array())
7624 .expect("Missing memory replay mapping for twin area");
7625 let uses_replay = replay_map.iter().any(|rule| {
7626 rule.get("morphology_id")
7627 .and_then(|v| v.as_str())
7628 .is_some_and(|id| id == "memory_replay")
7629 });
7630 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7631
7632 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7633 assert_eq!(
7634 twin_area
7635 .properties
7636 .get("memory_twin_of")
7637 .and_then(|v| v.as_str()),
7638 Some(src_id.as_base_64().as_str())
7639 );
7640 assert_eq!(
7641 twin_area
7642 .properties
7643 .get("memory_twin_for")
7644 .and_then(|v| v.as_str()),
7645 Some(mem_id.as_base_64().as_str())
7646 );
7647 }
7648}