1use once_cell::sync::Lazy;
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::hash::Hasher;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::sync::{Arc, Mutex};
38use tracing::{debug, error, info, trace, warn};
39use xxhash_rust::xxh64::Xxh64;
40
41type BrainRegionIoRegistry = HashMap<String, (Vec<String>, Vec<String>)>;
42
43use crate::models::{BrainRegion, BrainRegionHierarchy, CorticalArea, CorticalAreaDimensions};
44use crate::types::{BduError, BduResult};
45use feagi_npu_neural::synapse::SYNAPSE_EDGE_ASSOCIATIVE_MEMORY;
46use feagi_npu_neural::types::NeuronId;
47use feagi_structures::genomic::cortical_area::{
48 CoreCorticalType, CorticalAreaType, CorticalID, CustomCorticalType,
49};
50use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
51
52use feagi_state_manager::StateManager;
55
56const DATA_HASH_SEED: u64 = 0;
57const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
59
60static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
65 Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ConnectomeConfig {
70 pub max_neurons: usize,
72
73 pub max_synapses: usize,
75
76 pub backend: String,
78}
79
80impl Default for ConnectomeConfig {
81 fn default() -> Self {
82 Self {
83 max_neurons: 10_000_000,
84 max_synapses: 100_000_000,
85 backend: "cpu".to_string(),
86 }
87 }
88}
89
90pub struct ConnectomeManager {
112 cortical_areas: HashMap<CorticalID, CorticalArea>,
114
115 cortical_id_to_idx: HashMap<CorticalID, u32>,
117
118 cortical_idx_to_id: HashMap<u32, CorticalID>,
120
121 next_cortical_idx: u32,
123
124 brain_regions: BrainRegionHierarchy,
126
127 morphology_registry: feagi_evolutionary::MorphologyRegistry,
129
130 config: ConnectomeConfig,
132
133 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
139
140 #[cfg(feature = "plasticity")]
142 plasticity_executor:
143 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
144
145 cached_neuron_count: Arc<AtomicUsize>,
148
149 cached_synapse_count: Arc<AtomicUsize>,
152
153 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
156
157 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
160
161 initialized: bool,
163
164 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
166}
167
168type NeuronData = (
170 u32,
171 u32,
172 u32,
173 f32,
174 f32,
175 f32,
176 f32,
177 i32,
178 u16,
179 f32,
180 u16,
181 u16,
182 bool,
183);
184
185impl ConnectomeManager {
186 fn get_mapping_rules_for_destination<'a>(
187 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
188 dst_area_id: &CorticalID,
189 ) -> Option<&'a Vec<serde_json::Value>> {
190 if let Some(rules) = mapping_dst
191 .get(&dst_area_id.as_base_64())
192 .and_then(|value| value.as_array())
193 {
194 return Some(rules);
195 }
196
197 for (raw_dst_key, rules_value) in mapping_dst {
200 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
201 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
202 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
203 continue;
204 }
205 if let Some(rules) = rules_value.as_array() {
206 return Some(rules);
207 }
208 }
209
210 None
211 }
212
213 fn new() -> Self {
215 Self {
216 cortical_areas: HashMap::new(),
217 cortical_id_to_idx: HashMap::new(),
218 cortical_idx_to_id: HashMap::new(),
219 next_cortical_idx: 3, brain_regions: BrainRegionHierarchy::new(),
222 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
223 config: ConnectomeConfig::default(),
224 npu: None,
225 #[cfg(feature = "plasticity")]
226 plasticity_executor: None,
227 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
228 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
229 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
230 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
231 initialized: false,
232 last_fatigue_calculation: Arc::new(Mutex::new(
233 std::time::Instant::now() - std::time::Duration::from_secs(10),
234 )), }
236 }
237
238 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
255 Arc::clone(&*INSTANCE)
258 }
259
260 pub fn new_for_testing() -> Self {
288 Self {
289 cortical_areas: HashMap::new(),
290 cortical_id_to_idx: HashMap::new(),
291 cortical_idx_to_id: HashMap::new(),
292 next_cortical_idx: 0,
293 brain_regions: BrainRegionHierarchy::new(),
294 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
295 config: ConnectomeConfig::default(),
296 npu: None,
297 #[cfg(feature = "plasticity")]
298 plasticity_executor: None,
299 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
300 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
301 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
302 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
303 initialized: false,
304 last_fatigue_calculation: Arc::new(Mutex::new(
305 std::time::Instant::now() - std::time::Duration::from_secs(10),
306 )),
307 }
308 }
309
310 pub fn new_for_testing_with_npu(
326 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
327 ) -> Self {
328 Self {
329 cortical_areas: HashMap::new(),
330 cortical_id_to_idx: HashMap::new(),
331 cortical_idx_to_id: HashMap::new(),
332 next_cortical_idx: 3,
333 brain_regions: BrainRegionHierarchy::new(),
334 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
335 config: ConnectomeConfig::default(),
336 npu: Some(npu),
337 #[cfg(feature = "plasticity")]
338 plasticity_executor: None,
339 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
340 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
341 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
342 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
343 initialized: false,
344 last_fatigue_calculation: Arc::new(Mutex::new(
345 std::time::Instant::now() - std::time::Duration::from_secs(10),
346 )),
347 }
348 }
349
350 pub fn setup_core_morphologies_for_testing(&mut self) {
360 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
361 }
362
363 #[cfg(test)]
372 pub fn reset_for_testing() {
373 let mut instance = INSTANCE.write();
374 *instance = Self::new();
375 }
376
377 fn update_state_hashes(
383 &self,
384 brain_regions: Option<u64>,
385 cortical_areas: Option<u64>,
386 brain_geometry: Option<u64>,
387 morphologies: Option<u64>,
388 cortical_mappings: Option<u64>,
389 ) {
390 let state_manager = StateManager::instance();
391 let state_manager = state_manager.read();
392 if let Some(value) = brain_regions {
393 state_manager.set_brain_regions_hash(value);
394 }
395 if let Some(value) = cortical_areas {
396 state_manager.set_cortical_areas_hash(value);
397 }
398 if let Some(value) = brain_geometry {
399 state_manager.set_brain_geometry_hash(value);
400 }
401 if let Some(value) = morphologies {
402 state_manager.set_morphologies_hash(value);
403 }
404 if let Some(value) = cortical_mappings {
405 state_manager.set_cortical_mappings_hash(value);
406 }
407 }
408
409 fn refresh_brain_regions_hash(&self) {
411 let hash = self.compute_brain_regions_hash();
412 self.update_state_hashes(Some(hash), None, None, None, None);
413 }
414
415 #[allow(dead_code)]
416 fn refresh_cortical_areas_hash(&self) {
418 let hash = self.compute_cortical_areas_hash();
419 self.update_state_hashes(None, Some(hash), None, None, None);
420 }
421
422 #[allow(dead_code)]
423 fn refresh_brain_geometry_hash(&self) {
425 let hash = self.compute_brain_geometry_hash();
426 self.update_state_hashes(None, None, Some(hash), None, None);
427 }
428
429 fn refresh_morphologies_hash(&self) {
431 let hash = self.compute_morphologies_hash();
432 self.update_state_hashes(None, None, None, Some(hash), None);
433 }
434
435 fn refresh_cortical_mappings_hash(&self) {
437 let hash = self.compute_cortical_mappings_hash();
438 self.update_state_hashes(None, None, None, None, Some(hash));
439 }
440
441 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
443 let cortical_hash = if properties_changed {
444 Some(self.compute_cortical_areas_hash())
445 } else {
446 None
447 };
448 let geometry_hash = if geometry_changed {
449 Some(self.compute_brain_geometry_hash())
450 } else {
451 None
452 };
453 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
454 }
455
456 fn compute_brain_regions_hash(&self) -> u64 {
458 let mut hasher = Xxh64::new(DATA_HASH_SEED);
459 let mut region_ids: Vec<String> = self
460 .brain_regions
461 .get_all_region_ids()
462 .into_iter()
463 .cloned()
464 .collect();
465 region_ids.sort();
466
467 for region_id in region_ids {
468 let Some(region) = self.brain_regions.get_region(®ion_id) else {
469 continue;
470 };
471 Self::hash_str(&mut hasher, ®ion_id);
472 Self::hash_str(&mut hasher, ®ion.name);
473 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
474 let parent_id = self.brain_regions.get_parent(®ion_id);
475 match parent_id {
476 Some(parent) => Self::hash_str(&mut hasher, parent),
477 None => Self::hash_str(&mut hasher, "null"),
478 }
479
480 let mut cortical_ids: Vec<String> = region
481 .cortical_areas
482 .iter()
483 .map(|id| id.as_base_64())
484 .collect();
485 cortical_ids.sort();
486 for cortical_id in cortical_ids {
487 Self::hash_str(&mut hasher, &cortical_id);
488 }
489
490 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
491 }
492
493 hasher.finish() & HASH_SAFE_MASK
494 }
495
496 fn compute_cortical_areas_hash(&self) -> u64 {
498 let mut hasher = Xxh64::new(DATA_HASH_SEED);
499 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
500 areas.sort_by_key(|area| area.cortical_id.as_base_64());
501
502 for area in areas {
503 let cortical_id = area.cortical_id.as_base_64();
504 Self::hash_str(&mut hasher, &cortical_id);
505 hasher.write_u32(area.cortical_idx);
506 Self::hash_str(&mut hasher, &area.name);
507 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
508
509 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
510 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
511 }
512
513 hasher.finish() & HASH_SAFE_MASK
514 }
515
516 fn compute_brain_geometry_hash(&self) -> u64 {
518 let mut hasher = Xxh64::new(DATA_HASH_SEED);
519 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
520 areas.sort_by_key(|area| area.cortical_id.as_base_64());
521
522 for area in areas {
523 let cortical_id = area.cortical_id.as_base_64();
524 Self::hash_str(&mut hasher, &cortical_id);
525
526 Self::hash_i32(&mut hasher, area.position.x);
527 Self::hash_i32(&mut hasher, area.position.y);
528 Self::hash_i32(&mut hasher, area.position.z);
529
530 Self::hash_u32(&mut hasher, area.dimensions.width);
531 Self::hash_u32(&mut hasher, area.dimensions.height);
532 Self::hash_u32(&mut hasher, area.dimensions.depth);
533
534 let coord_2d = area
535 .properties
536 .get("coordinate_2d")
537 .or_else(|| area.properties.get("coordinates_2d"));
538 match coord_2d {
539 Some(value) => Self::hash_json_value(&mut hasher, value),
540 None => Self::hash_str(&mut hasher, "null"),
541 }
542 }
543
544 hasher.finish() & HASH_SAFE_MASK
545 }
546
547 fn compute_morphologies_hash(&self) -> u64 {
549 let mut hasher = Xxh64::new(DATA_HASH_SEED);
550 let mut morphology_ids = self.morphology_registry.morphology_ids();
551 morphology_ids.sort();
552
553 for morphology_id in morphology_ids {
554 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
555 Self::hash_str(&mut hasher, &morphology_id);
556 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
557 Self::hash_str(&mut hasher, &morphology.class);
558 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
559 Self::hash_json_value(&mut hasher, &value);
560 }
561 }
562 }
563
564 hasher.finish() & HASH_SAFE_MASK
565 }
566
567 fn compute_cortical_mappings_hash(&self) -> u64 {
569 let mut hasher = Xxh64::new(DATA_HASH_SEED);
570 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
571 areas.sort_by_key(|area| area.cortical_id.as_base_64());
572
573 for area in areas {
574 let cortical_id = area.cortical_id.as_base_64();
575 Self::hash_str(&mut hasher, &cortical_id);
576 if let Some(serde_json::Value::Object(map)) =
577 area.properties.get("cortical_mapping_dst")
578 {
579 let mut dest_ids: Vec<&String> = map.keys().collect();
580 dest_ids.sort();
581 for dest_id in dest_ids {
582 Self::hash_str(&mut hasher, dest_id);
583 if let Some(value) = map.get(dest_id) {
584 Self::hash_json_value(&mut hasher, value);
585 }
586 }
587 } else {
588 Self::hash_str(&mut hasher, "null");
589 }
590 }
591
592 hasher.finish() & HASH_SAFE_MASK
593 }
594
595 fn hash_str(hasher: &mut Xxh64, value: &str) {
597 hasher.write(value.as_bytes());
598 hasher.write_u8(0);
599 }
600
601 fn hash_i32(hasher: &mut Xxh64, value: i32) {
603 hasher.write(&value.to_le_bytes());
604 }
605
606 fn hash_u32(hasher: &mut Xxh64, value: u32) {
608 hasher.write(&value.to_le_bytes());
609 }
610
611 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
613 match value {
614 serde_json::Value::Null => {
615 hasher.write_u8(0);
616 }
617 serde_json::Value::Bool(val) => {
618 hasher.write_u8(1);
619 hasher.write_u8(*val as u8);
620 }
621 serde_json::Value::Number(num) => {
622 hasher.write_u8(2);
623 Self::hash_str(hasher, &num.to_string());
624 }
625 serde_json::Value::String(val) => {
626 hasher.write_u8(3);
627 Self::hash_str(hasher, val);
628 }
629 serde_json::Value::Array(items) => {
630 hasher.write_u8(4);
631 for item in items {
632 Self::hash_json_value(hasher, item);
633 }
634 }
635 serde_json::Value::Object(map) => {
636 hasher.write_u8(5);
637 let mut keys: Vec<&String> = map.keys().collect();
638 keys.sort();
639 for key in keys {
640 Self::hash_str(hasher, key);
641 if let Some(val) = map.get(key) {
642 Self::hash_json_value(hasher, val);
643 }
644 }
645 }
646 }
647 }
648
649 fn hash_properties_filtered(
651 hasher: &mut Xxh64,
652 properties: &HashMap<String, serde_json::Value>,
653 excluded_keys: &[&str],
654 ) {
655 let mut keys: Vec<&String> = properties.keys().collect();
656 keys.sort();
657 for key in keys {
658 if excluded_keys.contains(&key.as_str()) {
659 continue;
660 }
661 Self::hash_str(hasher, key);
662 if let Some(value) = properties.get(key) {
663 Self::hash_json_value(hasher, value);
664 }
665 }
666 }
667
668 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
689 if self.cortical_areas.contains_key(&area.cortical_id) {
691 return Err(BduError::InvalidArea(format!(
692 "Cortical area {} already exists",
693 area.cortical_id
694 )));
695 }
696
697 use feagi_structures::genomic::cortical_area::CoreCorticalType;
700
701 let death_id = CoreCorticalType::Death.to_cortical_id();
702 let power_id = CoreCorticalType::Power.to_cortical_id();
703 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
704
705 let is_death_area = area.cortical_id == death_id;
706 let is_power_area = area.cortical_id == power_id;
707 let is_fatigue_area = area.cortical_id == fatigue_id;
708
709 if is_death_area {
710 trace!(
711 target: "feagi-bdu",
712 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
713 area.cortical_id
714 );
715 area.cortical_idx = 0;
716 } else if is_power_area {
717 trace!(
718 target: "feagi-bdu",
719 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
720 area.cortical_id
721 );
722 area.cortical_idx = 1;
723 } else if is_fatigue_area {
724 trace!(
725 target: "feagi-bdu",
726 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
727 area.cortical_id
728 );
729 area.cortical_idx = 2;
730 } else {
731 if area.cortical_idx == 0 {
733 area.cortical_idx = self.next_cortical_idx;
734 self.next_cortical_idx += 1;
735 trace!(
736 target: "feagi-bdu",
737 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be ≥3)",
738 area.cortical_idx,
739 area.cortical_id.as_base_64()
740 );
741 } else {
742 if area.cortical_idx == 0 || area.cortical_idx == 1 || area.cortical_idx == 2 {
744 warn!(
745 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
746 area.cortical_id, area.cortical_idx);
747 area.cortical_idx = self.next_cortical_idx;
748 self.next_cortical_idx += 1;
749 info!(
750 " Reassigned '{}' to cortical_idx={}",
751 area.cortical_id, area.cortical_idx
752 );
753 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
754 return Err(BduError::InvalidArea(format!(
755 "Cortical index {} is already in use",
756 area.cortical_idx
757 )));
758 }
759
760 if area.cortical_idx >= self.next_cortical_idx {
762 self.next_cortical_idx = area.cortical_idx + 1;
763 }
764 }
765 }
766
767 let cortical_id = area.cortical_id;
768 let cortical_idx = area.cortical_idx;
769
770 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
772 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
773
774 area.properties
776 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
777
778 let parent_region_id = area
787 .properties
788 .get("parent_region_id")
789 .and_then(|v| v.as_str())
790 .map(|s| s.to_string());
791
792 self.cortical_areas.insert(cortical_id, area);
794
795 if let Some(region_id) = parent_region_id {
797 let region = self
798 .brain_regions
799 .get_region_mut(®ion_id)
800 .ok_or_else(|| {
801 BduError::InvalidArea(format!(
802 "Unknown parent_region_id '{}' for cortical area {}",
803 region_id,
804 cortical_id.as_base_64()
805 ))
806 })?;
807 region.add_area(cortical_id);
808 }
809
810 {
813 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
814 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
815 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
816 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
817 }
818 let state_manager = StateManager::instance();
820 let state_manager = state_manager.read();
821 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
822
823 if let Some(ref npu) = self.npu {
827 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
828 if let Ok(mut npu_lock) = npu.lock() {
829 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
830 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
831 trace!(
832 target: "feagi-bdu",
833 "Registered cortical area idx={} -> '{}' in NPU",
834 cortical_idx,
835 cortical_id.as_base_64()
836 );
837 }
838 }
839
840 self.sync_cortical_area_flags_to_npu()?;
842
843 self.initialized = true;
844
845 self.refresh_cortical_area_hashes(true, true);
846 self.refresh_brain_regions_hash();
847
848 Ok(cortical_idx)
849 }
850
851 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
866 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
867 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
868 })?;
869
870 self.cortical_id_to_idx.remove(cortical_id);
872 self.cortical_idx_to_id.remove(&area.cortical_idx);
873
874 self.refresh_cortical_area_hashes(true, true);
875 Ok(())
876 }
877
878 pub fn rename_cortical_area_id(
882 &mut self,
883 old_id: &CorticalID,
884 new_id: CorticalID,
885 new_cortical_type: CorticalAreaType,
886 ) -> BduResult<()> {
887 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
888 }
889
890 pub fn rename_cortical_area_id_with_options(
892 &mut self,
893 old_id: &CorticalID,
894 new_id: CorticalID,
895 new_cortical_type: CorticalAreaType,
896 update_npu_registry: bool,
897 ) -> BduResult<()> {
898 if !self.cortical_areas.contains_key(old_id) {
899 return Err(BduError::InvalidArea(format!(
900 "Cortical area {} does not exist",
901 old_id
902 )));
903 }
904 if self.cortical_areas.contains_key(&new_id) {
905 return Err(BduError::InvalidArea(format!(
906 "Cortical area {} already exists",
907 new_id
908 )));
909 }
910
911 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
912 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
913 })?;
914 let cortical_idx = area.cortical_idx;
915 area.cortical_id = new_id;
916 area.cortical_type = new_cortical_type;
917
918 self.cortical_areas.insert(new_id, area);
919 self.cortical_id_to_idx.remove(old_id);
920 self.cortical_id_to_idx.insert(new_id, cortical_idx);
921 self.cortical_idx_to_id.insert(cortical_idx, new_id);
922
923 {
925 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
926 if let Some(value) = neuron_cache.remove(old_id) {
927 neuron_cache.insert(new_id, value);
928 }
929 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
930 if let Some(value) = synapse_cache.remove(old_id) {
931 synapse_cache.insert(new_id, value);
932 }
933 }
934
935 self.brain_regions.rename_cortical_area_id(old_id, new_id);
937
938 let old_id_str = old_id.as_base_64();
940 let new_id_str = new_id.as_base_64();
941 for area in self.cortical_areas.values_mut() {
942 if let Some(mapping) = area
943 .properties
944 .get_mut("cortical_mapping_dst")
945 .and_then(|v| v.as_object_mut())
946 {
947 if let Some(value) = mapping.remove(&old_id_str) {
948 mapping.insert(new_id_str.clone(), value);
949 }
950 }
951 }
952
953 if update_npu_registry {
955 if let Some(ref npu) = self.npu {
956 if let Ok(mut npu_lock) = npu.lock() {
957 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
958 }
959 }
960 }
961
962 self.refresh_cortical_area_hashes(true, true);
963 self.refresh_brain_regions_hash();
964 self.refresh_cortical_mappings_hash();
965
966 Ok(())
967 }
968
969 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
971 self.cortical_areas.get(cortical_id)
972 }
973
974 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
976 self.cortical_areas.get_mut(cortical_id)
977 }
978
979 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
981 self.cortical_id_to_idx.get(cortical_id).copied()
982 }
983
984 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
996 self.brain_regions.find_region_containing_area(cortical_id)
997 }
998
999 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1010 use std::collections::HashSet;
1011
1012 let region_ids: Vec<String> = self
1013 .brain_regions
1014 .get_all_region_ids()
1015 .into_iter()
1016 .cloned()
1017 .collect();
1018
1019 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1020 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1021
1022 for rid in ®ion_ids {
1024 inputs_by_region.insert(rid.clone(), HashSet::new());
1025 outputs_by_region.insert(rid.clone(), HashSet::new());
1026 }
1027
1028 for (src_id, src_area) in &self.cortical_areas {
1029 let Some(dstmap) = src_area
1030 .properties
1031 .get("cortical_mapping_dst")
1032 .and_then(|v| v.as_object())
1033 else {
1034 continue;
1035 };
1036
1037 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1038 warn!(
1039 target: "feagi-bdu",
1040 "Skipping region IO for source area {} (not in any region)",
1041 src_id.as_base_64()
1042 );
1043 continue;
1044 };
1045
1046 for dst_id_str in dstmap.keys() {
1047 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1048 BduError::InvalidArea(format!(
1049 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1050 dst_id_str,
1051 src_id.as_base_64(),
1052 e
1053 ))
1054 })?;
1055
1056 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1057 else {
1058 warn!(
1059 target: "feagi-bdu",
1060 "Skipping region IO for destination area {} (not in any region)",
1061 dst_id.as_base_64()
1062 );
1063 continue;
1064 };
1065
1066 if src_region_id == dst_region_id {
1067 continue;
1068 }
1069
1070 outputs_by_region
1071 .entry(src_region_id.clone())
1072 .or_default()
1073 .insert(src_id.as_base_64());
1074 inputs_by_region
1075 .entry(dst_region_id.clone())
1076 .or_default()
1077 .insert(dst_id.as_base_64());
1078 }
1079 }
1080
1081 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1082 for rid in region_ids {
1083 let mut inputs: Vec<String> = inputs_by_region
1084 .remove(&rid)
1085 .unwrap_or_default()
1086 .into_iter()
1087 .collect();
1088 let mut outputs: Vec<String> = outputs_by_region
1089 .remove(&rid)
1090 .unwrap_or_default()
1091 .into_iter()
1092 .collect();
1093
1094 inputs.sort();
1095 outputs.sort();
1096
1097 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1098 BduError::InvalidArea(format!(
1099 "Unable to recompute region IO: region '{}' not found in hierarchy",
1100 rid
1101 ))
1102 })?;
1103
1104 if inputs.is_empty() {
1105 region.properties.remove("inputs");
1106 } else {
1107 region
1108 .properties
1109 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1110 }
1111
1112 if outputs.is_empty() {
1113 region.properties.remove("outputs");
1114 } else {
1115 region
1116 .properties
1117 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1118 }
1119
1120 computed.insert(rid, (inputs, outputs));
1121 }
1122
1123 self.refresh_brain_regions_hash();
1124
1125 Ok(computed)
1126 }
1127
1128 pub fn get_root_region_id(&self) -> Option<String> {
1134 self.brain_regions.get_root_region_id()
1135 }
1136
1137 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1139 self.cortical_idx_to_id.get(&cortical_idx)
1140 }
1141
1142 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1145 self.cortical_idx_to_id
1146 .iter()
1147 .map(|(idx, id)| (*idx, id.as_base_64()))
1148 .collect()
1149 }
1150
1151 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1156 let mut granularities = ahash::AHashMap::new();
1157 for (cortical_id, area) in &self.cortical_areas {
1158 let cortical_idx = self
1159 .cortical_id_to_idx
1160 .get(cortical_id)
1161 .copied()
1162 .unwrap_or(0);
1163
1164 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1167 if let Some(arr) = granularity_json.as_array() {
1168 if arr.len() == 3 {
1169 let x_opt = arr[0]
1170 .as_u64()
1171 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1172 let y_opt = arr[1]
1173 .as_u64()
1174 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1175 let z_opt = arr[2]
1176 .as_u64()
1177 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1178
1179 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1180 let granularity = (x as u32, y as u32, z as u32);
1181 if granularity != (1, 1, 1) {
1183 granularities.insert(cortical_idx, granularity);
1184 }
1185 }
1186 }
1187 }
1188 }
1189 }
1190 granularities
1191 }
1192
1193 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1195 self.cortical_areas.keys().collect()
1196 }
1197
1198 pub fn get_cortical_area_count(&self) -> usize {
1200 self.cortical_areas.len()
1201 }
1202
1203 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1217 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1218 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1219 if let Some(upstream_array) = upstream_prop.as_array() {
1220 return upstream_array
1221 .iter()
1222 .filter_map(|v| v.as_u64().map(|n| n as u32))
1223 .collect();
1224 }
1225 }
1226
1227 warn!(target: "feagi-bdu",
1229 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1230 target_cortical_id.as_base_64()
1231 );
1232 }
1233
1234 Vec::new()
1235 }
1236
1237 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1239 upstream
1240 .iter()
1241 .filter_map(|idx| {
1242 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1243 let area = self.cortical_areas.get(cortical_id)?;
1244 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1245 None
1246 } else {
1247 Some(*idx)
1248 }
1249 })
1250 .collect()
1251 }
1252
1253 pub fn refresh_upstream_cortical_areas_from_mappings(
1257 &mut self,
1258 target_cortical_id: &CorticalID,
1259 ) -> Vec<u32> {
1260 use std::collections::HashSet;
1261 let target_id_str = target_cortical_id.as_base_64();
1262 let mut upstream_idxs = HashSet::new();
1263 for (src_id, src_area) in &self.cortical_areas {
1264 if src_id == target_cortical_id {
1265 continue;
1266 }
1267 if let Some(mapping) = src_area
1268 .properties
1269 .get("cortical_mapping_dst")
1270 .and_then(|v| v.as_object())
1271 {
1272 if mapping.contains_key(&target_id_str) {
1273 upstream_idxs.insert(src_area.cortical_idx);
1274 }
1275 }
1276 }
1277
1278 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1279 upstream_list.sort_unstable();
1280
1281 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1282 target_area.properties.insert(
1283 "upstream_cortical_areas".to_string(),
1284 serde_json::json!(upstream_list),
1285 );
1286 }
1287
1288 self.get_upstream_cortical_areas(target_cortical_id)
1289 }
1290
1291 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1301 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1302 let upstream_array = area
1303 .properties
1304 .entry("upstream_cortical_areas".to_string())
1305 .or_insert_with(|| serde_json::json!([]));
1306
1307 if let Some(arr) = upstream_array.as_array_mut() {
1308 let src_value = serde_json::json!(src_cortical_idx);
1309 if !arr.contains(&src_value) {
1310 arr.push(src_value);
1311 info!(target: "feagi-bdu",
1312 "✓ Added upstream area idx={} to cortical area '{}'",
1313 src_cortical_idx, target_cortical_id.as_base_64()
1314 );
1315 }
1316 }
1317 }
1318 }
1319
1320 pub fn get_memory_twin_for_upstream_idx(
1322 &self,
1323 memory_area_idx: u32,
1324 upstream_idx: u32,
1325 ) -> Option<CorticalID> {
1326 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1327 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1328 let area = self.cortical_areas.get(memory_id)?;
1329 let mapping = area
1330 .properties
1331 .get("memory_twin_areas")
1332 .and_then(|v| v.as_object())?;
1333 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1334 CorticalID::try_from_base_64(twin_b64).ok()
1335 }
1336
1337 pub fn ensure_memory_twin_area(
1339 &mut self,
1340 memory_area_id: &CorticalID,
1341 upstream_area_id: &CorticalID,
1342 ) -> BduResult<CorticalID> {
1343 use crate::models::CorticalAreaExt;
1344
1345 let register_replay_mapping = |manager: &mut ConnectomeManager,
1346 twin_id: &CorticalID|
1347 -> BduResult<()> {
1348 let Some(npu) = manager.npu.as_ref() else {
1349 return Ok(());
1350 };
1351 let memory_area_idx =
1352 *manager
1353 .cortical_id_to_idx
1354 .get(memory_area_id)
1355 .ok_or_else(|| {
1356 BduError::InvalidArea(format!(
1357 "Memory area idx missing for {}",
1358 memory_area_id.as_base_64()
1359 ))
1360 })?;
1361 let upstream_area_idx = *manager
1362 .cortical_id_to_idx
1363 .get(upstream_area_id)
1364 .ok_or_else(|| {
1365 BduError::InvalidArea(format!(
1366 "Upstream area idx missing for {}",
1367 upstream_area_id.as_base_64()
1368 ))
1369 })?;
1370 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1371 BduError::InvalidArea(format!(
1372 "Twin area idx missing for {}",
1373 twin_id.as_base_64()
1374 ))
1375 })?;
1376 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1377 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1378 })?;
1379 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1380 if let Ok(mut npu_lock) = npu.lock() {
1381 npu_lock.register_memory_twin_mapping(
1382 memory_area_idx,
1383 upstream_area_idx,
1384 twin_area_idx,
1385 potential,
1386 );
1387 }
1388 Ok(())
1389 };
1390
1391 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1392 BduError::InvalidArea(format!(
1393 "Memory area {} not found",
1394 memory_area_id.as_base_64()
1395 ))
1396 })?;
1397 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1398 BduError::InvalidArea(format!(
1399 "Upstream area {} not found",
1400 upstream_area_id.as_base_64()
1401 ))
1402 })?;
1403
1404 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1405 return Err(BduError::InvalidArea(format!(
1406 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1407 upstream_area_id.as_base_64()
1408 )));
1409 }
1410
1411 if let Some(existing) = memory_area
1412 .properties
1413 .get("memory_twin_areas")
1414 .and_then(|v| v.as_object())
1415 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1416 .and_then(|v| v.as_str())
1417 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1418 {
1419 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1420 register_replay_mapping(self, &existing)?;
1421 self.refresh_cortical_mappings_hash();
1422 return Ok(existing);
1423 }
1424
1425 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1426 if self.cortical_areas.contains_key(&twin_id) {
1427 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1428 let expected_source = upstream_area_id.as_base_64();
1429 let expected_target = memory_area_id.as_base_64();
1430 let existing_source = existing
1431 .properties
1432 .get("memory_twin_of")
1433 .and_then(|v| v.as_str());
1434 let existing_target = existing
1435 .properties
1436 .get("memory_twin_for")
1437 .and_then(|v| v.as_str());
1438 if existing_source != Some(expected_source.as_str())
1439 || existing_target != Some(expected_target.as_str())
1440 {
1441 warn!(
1442 target: "feagi-bdu",
1443 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1444 upstream_area_id.as_base_64(),
1445 memory_area_id.as_base_64()
1446 );
1447 existing.properties.insert(
1448 "memory_twin_of".to_string(),
1449 serde_json::json!(expected_source),
1450 );
1451 existing.properties.insert(
1452 "memory_twin_for".to_string(),
1453 serde_json::json!(expected_target),
1454 );
1455 }
1456 }
1457 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1458 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1459 register_replay_mapping(self, &twin_id)?;
1460 self.refresh_cortical_mappings_hash();
1461 return Ok(twin_id);
1462 }
1463
1464 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1465 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1466 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1467 let mut twin_area = CorticalArea::new(
1468 twin_id,
1469 0,
1470 twin_name,
1471 upstream_area.dimensions,
1472 twin_position,
1473 twin_type,
1474 )?;
1475 twin_area.properties = self.build_memory_twin_properties(
1476 memory_area,
1477 upstream_area,
1478 memory_area_id,
1479 upstream_area_id,
1480 );
1481
1482 let _twin_idx = self.add_cortical_area(twin_area)?;
1483 let _ = self.create_neurons_for_area(&twin_id);
1484
1485 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1486 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1487 register_replay_mapping(self, &twin_id)?;
1488 self.refresh_cortical_mappings_hash();
1489 Ok(twin_id)
1490 }
1491
1492 fn build_memory_twin_position(
1493 &self,
1494 memory_area: &CorticalArea,
1495 upstream_area: &CorticalArea,
1496 ) -> GenomeCoordinate3D {
1497 let memory_parent = memory_area
1498 .properties
1499 .get("parent_region_id")
1500 .and_then(|v| v.as_str());
1501 let upstream_parent = upstream_area
1502 .properties
1503 .get("parent_region_id")
1504 .and_then(|v| v.as_str());
1505 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1506
1507 if !same_region {
1508 return GenomeCoordinate3D::new(
1509 memory_area.position.x + 20,
1510 memory_area.position.y,
1511 memory_area.position.z,
1512 );
1513 }
1514
1515 let width = upstream_area.dimensions.width as f32;
1516 let margin = (width * 0.25).ceil() as i32;
1517 let offset = upstream_area.dimensions.width as i32 + margin;
1518 GenomeCoordinate3D::new(
1519 upstream_area.position.x + offset,
1520 upstream_area.position.y,
1521 upstream_area.position.z,
1522 )
1523 }
1524
1525 fn build_memory_twin_id(
1526 &self,
1527 memory_area_id: &CorticalID,
1528 upstream_area_id: &CorticalID,
1529 ) -> BduResult<CorticalID> {
1530 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1531 hasher.write(memory_area_id.as_base_64().as_bytes());
1532 hasher.write(upstream_area_id.as_base_64().as_bytes());
1533 hasher.write(b"memory_twin");
1534 let hash = hasher.finish();
1535 let mut bytes = hash.to_be_bytes();
1536 bytes[0] = b'c';
1537 CorticalID::try_from_bytes(&bytes)
1538 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1539 }
1540
1541 fn build_memory_twin_properties(
1542 &self,
1543 memory_area: &CorticalArea,
1544 upstream_area: &CorticalArea,
1545 memory_area_id: &CorticalID,
1546 upstream_area_id: &CorticalID,
1547 ) -> HashMap<String, serde_json::Value> {
1548 let mut props = upstream_area.properties.clone();
1549 props.remove("cortical_mapping_dst");
1550 props.remove("upstream_cortical_areas");
1551 props.remove("parent_region_id");
1552 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1553 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1554 props.insert(
1555 "memory_twin_of".to_string(),
1556 serde_json::json!(upstream_area_id.as_base_64()),
1557 );
1558 props.insert(
1559 "memory_twin_for".to_string(),
1560 serde_json::json!(memory_area_id.as_base_64()),
1561 );
1562 if let Some(parent_region_id) = memory_area
1563 .properties
1564 .get("parent_region_id")
1565 .and_then(|v| v.as_str())
1566 {
1567 props.insert(
1568 "parent_region_id".to_string(),
1569 serde_json::json!(parent_region_id),
1570 );
1571 }
1572 props
1573 }
1574
1575 fn set_memory_twin_mapping(
1576 &mut self,
1577 memory_area_id: &CorticalID,
1578 upstream_area_id: &CorticalID,
1579 twin_id: &CorticalID,
1580 ) {
1581 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1582 let mapping = memory_area
1583 .properties
1584 .entry("memory_twin_areas".to_string())
1585 .or_insert_with(|| serde_json::json!({}));
1586 if let Some(map) = mapping.as_object_mut() {
1587 map.insert(
1588 upstream_area_id.as_base_64(),
1589 serde_json::json!(twin_id.as_base_64()),
1590 );
1591 }
1592 }
1593 }
1594
1595 fn ensure_memory_replay_mapping(
1596 &mut self,
1597 memory_area_id: &CorticalID,
1598 twin_id: &CorticalID,
1599 ) -> BduResult<()> {
1600 if !self.morphology_registry.contains("memory_replay") {
1601 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1602 }
1603 self.refresh_morphologies_hash();
1604 let mapping_data = vec![serde_json::json!({
1605 "morphology_id": "memory_replay",
1606 "morphology_scalar": [1, 1, 1],
1607 "postSynapticCurrent_multiplier": 1,
1608 "plasticity_flag": false,
1609 "plasticity_constant": 0,
1610 "ltp_multiplier": 0,
1611 "ltd_multiplier": 0,
1612 "plasticity_window": 0,
1613 })];
1614 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1615 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1616 self.refresh_cortical_area_hashes(true, false);
1618 Ok(())
1619 }
1620
1621 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1631 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1632 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1633 if let Some(arr) = upstream_prop.as_array_mut() {
1634 let src_value = serde_json::json!(src_cortical_idx);
1635 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1636 arr.remove(pos);
1637 debug!(target: "feagi-bdu",
1638 "Removed upstream area idx={} from cortical area '{}'",
1639 src_cortical_idx, target_cortical_id.as_base_64()
1640 );
1641 }
1642 }
1643 }
1644 }
1645 }
1646
1647 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1649 self.cortical_areas.contains_key(cortical_id)
1650 }
1651
1652 pub fn is_initialized(&self) -> bool {
1654 self.initialized && !self.cortical_areas.is_empty()
1655 }
1656
1657 pub fn add_brain_region(
1663 &mut self,
1664 region: BrainRegion,
1665 parent_id: Option<String>,
1666 ) -> BduResult<()> {
1667 self.brain_regions.add_region(region, parent_id)?;
1668 self.refresh_brain_regions_hash();
1669 Ok(())
1670 }
1671
1672 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1674 self.brain_regions.remove_region(region_id)?;
1675 self.refresh_brain_regions_hash();
1676 Ok(())
1677 }
1678
1679 pub fn change_brain_region_parent(
1681 &mut self,
1682 region_id: &str,
1683 new_parent_id: &str,
1684 ) -> BduResult<()> {
1685 self.brain_regions.change_parent(region_id, new_parent_id)?;
1686 self.refresh_brain_regions_hash();
1687 Ok(())
1688 }
1689
1690 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1692 self.brain_regions.get_region(region_id)
1693 }
1694
1695 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1697 self.brain_regions.get_region_mut(region_id)
1698 }
1699
1700 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1702 self.brain_regions.get_all_region_ids()
1703 }
1704
1705 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1707 &self.brain_regions
1708 }
1709
1710 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1716 &self.morphology_registry
1717 }
1718
1719 pub fn get_morphology_count(&self) -> usize {
1721 self.morphology_registry.count()
1722 }
1723
1724 pub fn upsert_morphology(
1729 &mut self,
1730 morphology_id: String,
1731 morphology: feagi_evolutionary::Morphology,
1732 ) {
1733 self.morphology_registry
1734 .add_morphology(morphology_id, morphology);
1735 self.refresh_morphologies_hash();
1736 }
1737
1738 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1744 let removed = self.morphology_registry.remove_morphology(morphology_id);
1745 if removed {
1746 self.refresh_morphologies_hash();
1747 }
1748 removed
1749 }
1750
1751 pub fn update_cortical_mapping(
1769 &mut self,
1770 src_area_id: &CorticalID,
1771 dst_area_id: &CorticalID,
1772 mapping_data: Vec<serde_json::Value>,
1773 ) -> BduResult<()> {
1774 use tracing::info;
1775
1776 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1777
1778 {
1779 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1781 crate::types::BduError::InvalidArea(format!(
1782 "Source area not found: {}",
1783 src_area_id
1784 ))
1785 })?;
1786
1787 let cortical_mapping_dst =
1789 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1790 existing.as_object_mut().ok_or_else(|| {
1791 crate::types::BduError::InvalidMorphology(
1792 "cortical_mapping_dst is not an object".to_string(),
1793 )
1794 })?
1795 } else {
1796 src_area
1798 .properties
1799 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1800 src_area
1801 .properties
1802 .get_mut("cortical_mapping_dst")
1803 .unwrap()
1804 .as_object_mut()
1805 .unwrap()
1806 };
1807
1808 if mapping_data.is_empty() {
1810 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1812 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1813 } else {
1814 cortical_mapping_dst.insert(
1815 dst_area_id.as_base_64(),
1816 serde_json::Value::Array(mapping_data.clone()),
1817 );
1818 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1819 src_area_id, dst_area_id, mapping_data.len());
1820 }
1821 }
1822
1823 self.refresh_cortical_mappings_hash();
1824
1825 Ok(())
1826 }
1827
1828 pub fn regenerate_synapses_for_mapping(
1841 &mut self,
1842 src_area_id: &CorticalID,
1843 dst_area_id: &CorticalID,
1844 ) -> BduResult<usize> {
1845 use tracing::info;
1846
1847 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1848
1849 let mapping_rules_len = self
1850 .cortical_areas
1851 .get(src_area_id)
1852 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1853 .and_then(|v| v.as_object())
1854 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1855 .and_then(|v| v.as_array())
1856 .map(|arr| arr.len())
1857 .unwrap_or(0);
1858 tracing::debug!(
1859 target: "feagi-bdu",
1860 "Mapping rules for {} -> {}: {}",
1861 src_area_id,
1862 dst_area_id,
1863 mapping_rules_len
1864 );
1865
1866 let Some(npu_arc) = self.npu.clone() else {
1868 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
1869 return Ok(0);
1870 };
1871
1872 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
1882 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
1883 })?;
1884 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
1885 BduError::InvalidArea(format!(
1886 "No cortical idx for destination area {}",
1887 dst_area_id
1888 ))
1889 })?;
1890
1891 let mut pruned_synapse_count: usize = 0;
1894 use std::time::Instant;
1895 let start = Instant::now();
1896
1897 let (sources, targets) = {
1903 let lock_start = std::time::Instant::now();
1904 let npu = npu_arc.lock().unwrap();
1905 let lock_wait = lock_start.elapsed();
1906 tracing::debug!(
1907 target: "feagi-bdu",
1908 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
1909 lock_wait.as_secs_f64() * 1000.0,
1910 src_area_id,
1911 dst_area_id
1912 );
1913 let sources: Vec<NeuronId> = npu
1914 .get_neurons_in_cortical_area(src_idx)
1915 .into_iter()
1916 .map(NeuronId)
1917 .collect();
1918 let targets: Vec<NeuronId> = npu
1919 .get_neurons_in_cortical_area(dst_idx)
1920 .into_iter()
1921 .map(NeuronId)
1922 .collect();
1923 (sources, targets)
1924 };
1925
1926 tracing::debug!(
1927 target: "feagi-bdu",
1928 "Prune synapses: {} sources, {} targets",
1929 sources.len(),
1930 targets.len()
1931 );
1932
1933 if !sources.is_empty() && !targets.is_empty() {
1934 let remove_start = Instant::now();
1935 pruned_synapse_count = {
1936 let lock_start = std::time::Instant::now();
1937 let mut npu = npu_arc.lock().unwrap();
1938 let lock_wait = lock_start.elapsed();
1939 tracing::debug!(
1940 target: "feagi-bdu",
1941 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
1942 lock_wait.as_secs_f64() * 1000.0,
1943 src_area_id,
1944 dst_area_id
1945 );
1946 npu.remove_synapses_between(sources, targets)
1950 };
1951 let remove_time = remove_start.elapsed();
1952 let total_time = start.elapsed();
1953
1954 info!(
1955 target: "feagi-bdu",
1956 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
1957 pruned_synapse_count,
1958 src_area_id,
1959 dst_area_id,
1960 total_time.as_millis(),
1961 remove_time.as_millis()
1962 );
1963
1964 if pruned_synapse_count > 0 {
1966 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
1967 BduError::Internal(format!(
1968 "Pruned synapse count overflow (usize -> u32): {}",
1969 pruned_synapse_count
1970 ))
1971 })?;
1972 let state_manager = StateManager::instance();
1973 let state_manager = state_manager.read();
1974 let core_state = state_manager.get_core_state();
1975 core_state.subtract_synapse_count(pruned_u32);
1976 state_manager.subtract_cortical_area_outgoing_synapses(
1977 &src_area_id.as_base_64(),
1978 pruned_synapse_count,
1979 );
1980 state_manager.subtract_cortical_area_incoming_synapses(
1981 &dst_area_id.as_base_64(),
1982 pruned_synapse_count,
1983 );
1984
1985 {
1989 let mut cache = self.cached_synapse_counts_per_area.write();
1990 let entry = cache
1991 .entry(*src_area_id)
1992 .or_insert_with(|| AtomicUsize::new(0));
1993 let mut current = entry.load(Ordering::Relaxed);
1994 loop {
1995 let next = current.saturating_sub(pruned_synapse_count);
1996 match entry.compare_exchange(
1997 current,
1998 next,
1999 Ordering::Relaxed,
2000 Ordering::Relaxed,
2001 ) {
2002 Ok(_) => break,
2003 Err(v) => current = v,
2004 }
2005 }
2006 }
2007 }
2008 }
2009
2010 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2017 tracing::debug!(
2018 target: "feagi-bdu",
2019 "Synaptogenesis created {} synapses for {} -> {}",
2020 synapse_count,
2021 src_area_id,
2022 dst_area_id
2023 );
2024
2025 if synapse_count > 0 {
2028 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2029 BduError::Internal(format!(
2030 "Created synapse count overflow (usize -> u32): {}",
2031 synapse_count
2032 ))
2033 })?;
2034
2035 {
2037 let mut cache = self.cached_synapse_counts_per_area.write();
2038 cache
2039 .entry(*src_area_id)
2040 .or_insert_with(|| AtomicUsize::new(0))
2041 .fetch_add(synapse_count, Ordering::Relaxed);
2042 }
2043
2044 let state_manager = StateManager::instance();
2046 let state_manager = state_manager.read();
2047 let core_state = state_manager.get_core_state();
2048 core_state.add_synapse_count(created_u32);
2049 state_manager
2050 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2051 state_manager
2052 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2053 }
2054
2055 let src_idx_for_upstream = src_idx;
2058
2059 let has_mapping = self
2061 .cortical_areas
2062 .get(src_area_id)
2063 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2064 .and_then(|v| v.as_object())
2065 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2066 .is_some();
2067
2068 info!(target: "feagi-bdu",
2069 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2070 synapse_count,
2071 src_area_id.as_base_64(),
2072 dst_area_id.as_base_64(),
2073 has_mapping,
2074 if has_mapping { "" } else { "NOT " }
2075 );
2076
2077 if has_mapping {
2078 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2080
2081 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2082 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2083 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2084 warn!(
2085 target: "feagi-bdu",
2086 "Failed to ensure memory twin for {} -> {}: {}",
2087 src_area_id.as_base_64(),
2088 dst_area_id.as_base_64(),
2089 e
2090 );
2091 }
2092 }
2093 }
2094
2095 #[cfg(feature = "plasticity")]
2097 if let Some(ref executor) = self.plasticity_executor {
2098 use feagi_evolutionary::extract_memory_properties;
2099
2100 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2101 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2102 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2103 let upstream_non_memory =
2104 self.filter_non_memory_upstream_areas(&upstream_areas);
2105 debug!(
2106 target: "feagi-bdu",
2107 "Registering memory area idx={} id={} upstream={} depth={}",
2108 dst_area.cortical_idx,
2109 dst_area_id.as_base_64(),
2110 upstream_areas.len(),
2111 mem_props.temporal_depth
2112 );
2113
2114 if let Some(ref npu_arc) = self.npu {
2117 if let Ok(mut npu) = npu_arc.lock() {
2118 let existing_configs = npu.get_all_fire_ledger_configs();
2119 for &upstream_idx in &upstream_areas {
2120 let existing = existing_configs
2121 .iter()
2122 .find(|(idx, _)| *idx == upstream_idx)
2123 .map(|(_, w)| *w)
2124 .unwrap_or(0);
2125
2126 let desired = mem_props.temporal_depth as usize;
2127 let resolved = existing.max(desired);
2128 if resolved != existing {
2129 if let Err(e) =
2130 npu.configure_fire_ledger_window(upstream_idx, resolved)
2131 {
2132 warn!(
2133 target: "feagi-bdu",
2134 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2135 upstream_idx,
2136 resolved,
2137 e
2138 );
2139 }
2140 }
2141 }
2142 } else {
2143 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2144 }
2145 }
2146
2147 if let Ok(exec) = executor.lock() {
2148 use feagi_npu_plasticity::{
2149 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2150 };
2151
2152 let lifecycle_config = MemoryNeuronLifecycleConfig {
2153 initial_lifespan: mem_props.init_lifespan,
2154 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2155 longterm_threshold: mem_props.longterm_threshold,
2156 max_reactivations: 1000,
2157 };
2158
2159 exec.register_memory_area(
2160 dst_area.cortical_idx,
2161 dst_area_id.as_base_64(),
2162 mem_props.temporal_depth,
2163 upstream_non_memory,
2164 Some(lifecycle_config),
2165 );
2166 } else {
2167 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2168 }
2169 } else {
2170 debug!(
2171 target: "feagi-bdu",
2172 "Skipping plasticity registration: no memory properties for area {}",
2173 dst_area_id.as_base_64()
2174 );
2175 }
2176 } else {
2177 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2178 }
2179 } else {
2180 warn!(
2181 target: "feagi-bdu",
2182 "PlasticityExecutor not available; memory area {} not registered",
2183 dst_area_id.as_base_64()
2184 );
2185 }
2186
2187 #[cfg(not(feature = "plasticity"))]
2188 {
2189 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2190 }
2191 } else {
2192 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2194
2195 let mut npu = npu_arc.lock().unwrap();
2197 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2198 }
2199
2200 info!(
2201 target: "feagi-bdu",
2202 "Created {} new synapses: {} -> {}",
2203 synapse_count,
2204 src_area_id,
2205 dst_area_id
2206 );
2207
2208 if pruned_synapse_count > 0 || synapse_count == 0 {
2211 let mut npu = npu_arc.lock().unwrap();
2212 npu.rebuild_synapse_index();
2213 info!(
2214 target: "feagi-bdu",
2215 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2216 src_area_id,
2217 dst_area_id,
2218 pruned_synapse_count,
2219 synapse_count
2220 );
2221 } else {
2222 info!(
2223 target: "feagi-bdu",
2224 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2225 src_area_id,
2226 dst_area_id,
2227 synapse_count
2228 );
2229 }
2230
2231 {
2233 let npu = npu_arc.lock().unwrap();
2234 let fresh_count = npu.get_synapse_count();
2235 self.cached_synapse_count
2236 .store(fresh_count, Ordering::Relaxed);
2237 }
2238
2239 Ok(synapse_count)
2240 }
2241
2242 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2244 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2245 }
2246
2247 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2248 v.as_u64()
2249 .map(|n| n as usize)
2250 .or_else(|| v.as_f64().map(|f| f as usize))
2251 }
2252
2253 #[allow(clippy::too_many_arguments)]
2255 fn register_stdp_mapping_for_rule(
2256 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2257 src_area_id: &CorticalID,
2258 dst_area_id: &CorticalID,
2259 src_cortical_idx: u32,
2260 dst_cortical_idx: u32,
2261 rule_obj: &serde_json::Map<String, serde_json::Value>,
2262 bidirectional_stdp: bool,
2263 synapse_psp: f32,
2264 synapse_type: feagi_npu_neural::SynapseType,
2265 ) -> BduResult<()> {
2266 let plasticity_window = rule_obj
2267 .get("plasticity_window")
2268 .and_then(Self::json_number_as_usize_for_stdp)
2269 .ok_or_else(|| {
2270 BduError::Internal(format!(
2271 "Missing plasticity_window in plastic mapping rule {} -> {}",
2272 src_area_id, dst_area_id
2273 ))
2274 })?;
2275 let plasticity_constant = rule_obj
2276 .get("plasticity_constant")
2277 .and_then(Self::json_number_as_i64_for_stdp)
2278 .ok_or_else(|| {
2279 BduError::Internal(format!(
2280 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2281 src_area_id, dst_area_id
2282 ))
2283 })?;
2284 let ltp_multiplier = rule_obj
2285 .get("ltp_multiplier")
2286 .and_then(Self::json_number_as_i64_for_stdp)
2287 .ok_or_else(|| {
2288 BduError::Internal(format!(
2289 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2290 src_area_id, dst_area_id
2291 ))
2292 })?;
2293 let ltd_multiplier = rule_obj
2294 .get("ltd_multiplier")
2295 .and_then(Self::json_number_as_i64_for_stdp)
2296 .ok_or_else(|| {
2297 BduError::Internal(format!(
2298 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2299 src_area_id, dst_area_id
2300 ))
2301 })?;
2302
2303 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2304 plasticity_window,
2305 plasticity_constant,
2306 ltp_multiplier,
2307 ltd_multiplier,
2308 bidirectional_stdp,
2309 synapse_psp,
2310 synapse_type,
2311 };
2312
2313 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2314 let mut npu_lock = npu
2315 .lock()
2316 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2317 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2318
2319 npu_lock
2320 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2321 .map_err(|e| {
2322 BduError::Internal(format!(
2323 "Failed to register STDP mapping {} -> {}: {}",
2324 src_area_id, dst_area_id, e
2325 ))
2326 })?;
2327
2328 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2330 for area_idx in [src_cortical_idx, dst_cortical_idx] {
2331 let existing = existing_configs
2332 .iter()
2333 .find(|(idx, _)| *idx == area_idx)
2334 .map(|(_, w)| *w)
2335 .unwrap_or(0);
2336 let resolved = existing.max(plasticity_window);
2337 if resolved != existing {
2338 npu_lock
2339 .configure_fire_ledger_window(area_idx, resolved)
2340 .map_err(|e| {
2341 BduError::Internal(format!(
2342 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2343 area_idx, resolved, e
2344 ))
2345 })?;
2346 }
2347 }
2348
2349 Ok(())
2350 }
2351
2352 fn resolve_synapse_params_for_rule(
2354 &self,
2355 src_area_id: &CorticalID,
2356 rule: &serde_json::Value,
2357 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType)> {
2358 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2360 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2361 })?;
2362
2363 let (weight, synapse_type) = {
2365 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
2366 if let Some(i) = v.as_i64() {
2367 return Some(i as f64);
2368 }
2369 v.as_f64()
2370 };
2371
2372 let mult: f64 = if let Some(obj) = rule.as_object() {
2373 obj.get("postSynapticCurrent_multiplier")
2374 .and_then(parse_f64)
2375 .unwrap_or(1.0)
2376 } else if let Some(arr) = rule.as_array() {
2377 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
2378 } else {
2379 128.0
2380 };
2381
2382 if mult < 0.0 {
2383 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
2384 } else {
2385 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
2386 }
2387 };
2388
2389 use crate::models::cortical_area::CorticalAreaExt;
2391 let psp_f32 = src_area.postsynaptic_current();
2392
2393 tracing::debug!(
2394 target: "feagi-bdu",
2395 "Resolved synapse params src={} weight={} psp={} type={:?}",
2396 src_area_id.as_base_64(),
2397 weight,
2398 psp_f32,
2399 synapse_type
2400 );
2401
2402 Ok((weight, psp_f32, synapse_type))
2403 }
2404
2405 fn apply_cortical_mapping_for_pair(
2407 &mut self,
2408 src_area_id: &CorticalID,
2409 dst_area_id: &CorticalID,
2410 ) -> BduResult<usize> {
2411 let rules = {
2417 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2418 crate::types::BduError::InvalidArea(format!(
2419 "Source area not found: {}",
2420 src_area_id
2421 ))
2422 })?;
2423
2424 let Some(mapping_dst) = src_area
2425 .properties
2426 .get("cortical_mapping_dst")
2427 .and_then(|v| v.as_object())
2428 else {
2429 return Ok(0);
2430 };
2431
2432 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
2433 else {
2434 return Ok(0);
2435 };
2436
2437 rules.clone()
2438 }; if rules.is_empty() {
2441 return Ok(0);
2442 }
2443
2444 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2446 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2447 })?;
2448 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2449 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2450 })?;
2451
2452 let npu_arc = self
2454 .npu
2455 .as_ref()
2456 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2457 .clone();
2458
2459 tracing::debug!(
2460 target: "feagi-bdu",
2461 "Applying {} mapping rule(s) for {} -> {}",
2462 rules.len(),
2463 src_area_id,
2464 dst_area_id
2465 );
2466 let mut total_synapses = 0;
2468 for rule in &rules {
2469 let morphology_id = if let Some(rule_obj) = rule.as_object() {
2470 rule_obj
2471 .get("morphology_id")
2472 .and_then(|v| v.as_str())
2473 .unwrap_or("unknown")
2474 .to_string()
2475 } else if let Some(rule_arr) = rule.as_array() {
2476 rule_arr
2477 .first()
2478 .and_then(|v| v.as_str())
2479 .unwrap_or("unknown")
2480 .to_string()
2481 } else {
2482 "unknown".to_string()
2483 };
2484
2485 let rule_keys: Vec<String> = rule
2486 .as_object()
2487 .map(|obj| obj.keys().cloned().collect())
2488 .unwrap_or_default();
2489
2490 let mut plasticity_flag = rule
2492 .as_object()
2493 .and_then(|obj| obj.get("plasticity_flag"))
2494 .and_then(|v| v.as_bool())
2495 .unwrap_or(false);
2496 if morphology_id == "associative_memory" {
2497 plasticity_flag = true;
2498 }
2499 if plasticity_flag {
2500 let Some(rule_obj) = rule.as_object() else {
2501 return Err(crate::types::BduError::InvalidMorphology(
2502 "Plasticity mapping rule must be an object format".to_string(),
2503 ));
2504 };
2505 let (_weight, psp, synapse_type) =
2506 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2507 let bidirectional_stdp = morphology_id == "associative_memory";
2508 if let Err(e) = Self::register_stdp_mapping_for_rule(
2509 &npu_arc,
2510 src_area_id,
2511 dst_area_id,
2512 src_cortical_idx,
2513 dst_cortical_idx,
2514 rule_obj,
2515 bidirectional_stdp,
2516 psp,
2517 synapse_type,
2518 ) {
2519 tracing::error!(
2520 target: "feagi-bdu",
2521 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2522 src_area_id,
2523 dst_area_id,
2524 morphology_id,
2525 rule_keys,
2526 e
2527 );
2528 return Err(e);
2529 }
2530 }
2531
2532 let synapse_count = match self.apply_single_morphology_rule(
2534 src_area_id,
2535 dst_area_id,
2536 rule,
2537 ) {
2538 Ok(count) => count,
2539 Err(e) => {
2540 tracing::error!(
2541 target: "feagi-bdu",
2542 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2543 src_area_id,
2544 dst_area_id,
2545 morphology_id,
2546 rule_keys,
2547 e
2548 );
2549 return Err(e);
2550 }
2551 };
2552 total_synapses += synapse_count;
2553 tracing::debug!(
2554 target: "feagi-bdu",
2555 "Rule {} created {} synapses for {} -> {}",
2556 morphology_id,
2557 synapse_count,
2558 src_area_id,
2559 dst_area_id
2560 );
2561 }
2562
2563 Ok(total_synapses)
2564 }
2565
2566 #[allow(clippy::too_many_arguments)]
2580 fn apply_function_morphology(
2581 &self,
2582 morphology_id: &str,
2583 rule: &serde_json::Value,
2584 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2585 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2586 src_area_id: &CorticalID,
2587 dst_area_id: &CorticalID,
2588 src_idx: u32,
2589 dst_idx: u32,
2590 weight: f32,
2591 psp: f32,
2592 synapse_attractivity: u8,
2593 synapse_type: feagi_npu_neural::SynapseType,
2594 ) -> BduResult<usize> {
2595 match morphology_id {
2596 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
2597 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2599 crate::types::BduError::InvalidArea(format!(
2600 "Source area not found: {}",
2601 src_area_id
2602 ))
2603 })?;
2604 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2605 crate::types::BduError::InvalidArea(format!(
2606 "Destination area not found: {}",
2607 dst_area_id
2608 ))
2609 })?;
2610
2611 let src_dimensions = (
2612 src_area.dimensions.width as usize,
2613 src_area.dimensions.height as usize,
2614 src_area.dimensions.depth as usize,
2615 );
2616 let dst_dimensions = (
2617 dst_area.dimensions.width as usize,
2618 dst_area.dimensions.height as usize,
2619 dst_area.dimensions.depth as usize,
2620 );
2621
2622 let transpose = match morphology_id {
2625 "transpose_xy" => Some((1, 0, 2)),
2626 "transpose_yz" => Some((0, 2, 1)),
2627 "transpose_xz" => Some((2, 1, 0)),
2628 _ => None,
2629 };
2630
2631 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2632 let count = apply_projector_morphology_with_dimensions(
2633 npu,
2634 src_idx,
2635 dst_idx,
2636 src_dimensions,
2637 dst_dimensions,
2638 transpose,
2639 None, weight,
2641 psp,
2642 synapse_attractivity,
2643 synapse_type,
2644 0,
2645 )?;
2646 npu.rebuild_synapse_index();
2648 Ok(count as usize)
2649 }
2650 "episodic_memory" => {
2651 use tracing::trace;
2654 trace!(
2655 target: "feagi-bdu",
2656 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
2657 src_idx, dst_idx
2658 );
2659 Ok(0)
2660 }
2661 "memory_replay" => {
2662 use tracing::trace;
2664 trace!(
2665 target: "feagi-bdu",
2666 "Memory replay morphology: {} -> {} (no physical synapses)",
2667 src_idx, dst_idx
2668 );
2669 Ok(0)
2670 }
2671 "associative_memory" => {
2672 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2676 crate::types::BduError::InvalidArea(format!(
2677 "Source area not found: {}",
2678 src_area_id
2679 ))
2680 })?;
2681 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2682 crate::types::BduError::InvalidArea(format!(
2683 "Destination area not found: {}",
2684 dst_area_id
2685 ))
2686 })?;
2687
2688 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
2689 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
2690 {
2691 let src_dimensions = (
2692 src_area.dimensions.width as usize,
2693 src_area.dimensions.height as usize,
2694 src_area.dimensions.depth as usize,
2695 );
2696 let dst_dimensions = (
2697 dst_area.dimensions.width as usize,
2698 dst_area.dimensions.height as usize,
2699 dst_area.dimensions.depth as usize,
2700 );
2701 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2702 let count = apply_projector_morphology_with_dimensions(
2703 npu,
2704 src_idx,
2705 dst_idx,
2706 src_dimensions,
2707 dst_dimensions,
2708 None,
2709 None,
2710 weight,
2711 psp,
2712 synapse_attractivity,
2713 synapse_type,
2714 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
2715 )?;
2716 npu.rebuild_synapse_index();
2717 Ok(count as usize)
2718 } else {
2719 Ok(0)
2720 }
2721 }
2722 "block_to_block" => {
2723 tracing::warn!(
2724 target: "feagi-bdu",
2725 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
2726 src_idx, dst_idx
2727 );
2728 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2730 crate::types::BduError::InvalidArea(format!(
2731 "Source area not found: {}",
2732 src_area_id
2733 ))
2734 })?;
2735 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2736 crate::types::BduError::InvalidArea(format!(
2737 "Destination area not found: {}",
2738 dst_area_id
2739 ))
2740 })?;
2741
2742 let src_dimensions = (
2743 src_area.dimensions.width as usize,
2744 src_area.dimensions.height as usize,
2745 src_area.dimensions.depth as usize,
2746 );
2747 let dst_dimensions = (
2748 dst_area.dimensions.width as usize,
2749 dst_area.dimensions.height as usize,
2750 dst_area.dimensions.depth as usize,
2751 );
2752
2753 let scalar = if let Some(obj) = rule.as_object() {
2755 if let Some(scalar_arr) =
2757 obj.get("morphology_scalar").and_then(|v| v.as_array())
2758 {
2759 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
2761 } else {
2762 1 }
2764 } else if let Some(arr) = rule.as_array() {
2765 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
2767 } else {
2768 1 };
2770
2771 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
2774 let count = if estimated_neurons > 100_000 {
2775 let _ = npu;
2777
2778 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
2779 npu_arc,
2780 src_idx,
2781 dst_idx,
2782 src_dimensions,
2783 dst_dimensions,
2784 scalar, weight,
2786 psp,
2787 synapse_attractivity,
2788 synapse_type,
2789 )? as usize
2790 } else {
2791 tracing::warn!(
2793 target: "feagi-bdu",
2794 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
2795 src_idx, dst_idx, src_dimensions, dst_dimensions
2796 );
2797 let count =
2798 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
2799 npu,
2800 src_idx,
2801 dst_idx,
2802 src_dimensions,
2803 dst_dimensions,
2804 scalar, weight,
2806 psp,
2807 synapse_attractivity,
2808 synapse_type,
2809 )? as usize;
2810 tracing::warn!(
2811 target: "feagi-bdu",
2812 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
2813 count
2814 );
2815 if count > 0 {
2817 npu.rebuild_synapse_index();
2818 }
2819 count
2820 };
2821
2822 if count > 0 && estimated_neurons > 100_000 {
2824 let mut npu_lock = npu_arc.lock().unwrap();
2825 npu_lock.rebuild_synapse_index();
2826 }
2827
2828 Ok(count)
2829 }
2830 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
2831 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
2832 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2833 crate::types::BduError::InvalidArea(format!(
2834 "Source area not found: {}",
2835 src_area_id
2836 ))
2837 })?;
2838 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2839 crate::types::BduError::InvalidArea(format!(
2840 "Destination area not found: {}",
2841 dst_area_id
2842 ))
2843 })?;
2844
2845 let src_dimensions = (
2846 src_area.dimensions.width as usize,
2847 src_area.dimensions.height as usize,
2848 src_area.dimensions.depth as usize,
2849 );
2850 let dst_dimensions = (
2851 dst_area.dimensions.width as usize,
2852 dst_area.dimensions.height as usize,
2853 dst_area.dimensions.depth as usize,
2854 );
2855
2856 let (axis, mode) = match morphology_id {
2857 "bitmask_encoder_x" => (
2858 crate::connectivity::core_morphologies::BitmaskAxis::X,
2859 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2860 ),
2861 "bitmask_encoder_y" => (
2862 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2863 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2864 ),
2865 "bitmask_encoder_z" => (
2866 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2867 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
2868 ),
2869 "bitmask_decoder_x" => (
2870 crate::connectivity::core_morphologies::BitmaskAxis::X,
2871 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2872 ),
2873 "bitmask_decoder_y" => (
2874 crate::connectivity::core_morphologies::BitmaskAxis::Y,
2875 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2876 ),
2877 "bitmask_decoder_z" => (
2878 crate::connectivity::core_morphologies::BitmaskAxis::Z,
2879 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
2880 ),
2881 _ => unreachable!("matched bitmask morphology above"),
2882 };
2883
2884 let count =
2885 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
2886 npu,
2887 src_idx,
2888 dst_idx,
2889 src_dimensions,
2890 dst_dimensions,
2891 axis,
2892 mode,
2893 weight,
2894 psp,
2895 synapse_attractivity,
2896 synapse_type,
2897 )?;
2898 if count > 0 {
2899 npu.rebuild_synapse_index();
2900 }
2901 Ok(count as usize)
2902 }
2903 "sweeper" => {
2904 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2905 crate::types::BduError::InvalidArea(format!(
2906 "Destination area not found: {}",
2907 dst_area_id
2908 ))
2909 })?;
2910 let dst_dimensions = (
2911 dst_area.dimensions.width as usize,
2912 dst_area.dimensions.height as usize,
2913 dst_area.dimensions.depth as usize,
2914 );
2915
2916 let count =
2917 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
2918 npu,
2919 src_idx,
2920 dst_idx,
2921 dst_dimensions,
2922 weight,
2923 psp,
2924 synapse_attractivity,
2925 synapse_type,
2926 )?;
2927 if count > 0 {
2928 npu.rebuild_synapse_index();
2929 }
2930 Ok(count as usize)
2931 }
2932 "last_to_first" => {
2933 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2934 crate::types::BduError::InvalidArea(format!(
2935 "Source area not found: {}",
2936 src_area_id
2937 ))
2938 })?;
2939 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2940 crate::types::BduError::InvalidArea(format!(
2941 "Destination area not found: {}",
2942 dst_area_id
2943 ))
2944 })?;
2945 let src_dimensions = (
2946 src_area.dimensions.width as usize,
2947 src_area.dimensions.height as usize,
2948 src_area.dimensions.depth as usize,
2949 );
2950 let dst_dimensions = (
2951 dst_area.dimensions.width as usize,
2952 dst_area.dimensions.height as usize,
2953 dst_area.dimensions.depth as usize,
2954 );
2955
2956 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
2957 npu,
2958 src_idx,
2959 dst_idx,
2960 src_dimensions,
2961 dst_dimensions,
2962 weight,
2963 psp,
2964 synapse_attractivity,
2965 synapse_type,
2966 )?;
2967 if count > 0 {
2968 npu.rebuild_synapse_index();
2969 }
2970 Ok(count as usize)
2971 }
2972 "rotator_z" => {
2973 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2974 crate::types::BduError::InvalidArea(format!(
2975 "Source area not found: {}",
2976 src_area_id
2977 ))
2978 })?;
2979 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2980 crate::types::BduError::InvalidArea(format!(
2981 "Destination area not found: {}",
2982 dst_area_id
2983 ))
2984 })?;
2985 let src_dimensions = (
2986 src_area.dimensions.width as usize,
2987 src_area.dimensions.height as usize,
2988 src_area.dimensions.depth as usize,
2989 );
2990 let dst_dimensions = (
2991 dst_area.dimensions.width as usize,
2992 dst_area.dimensions.height as usize,
2993 dst_area.dimensions.depth as usize,
2994 );
2995
2996 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
2997 npu,
2998 src_idx,
2999 dst_idx,
3000 src_dimensions,
3001 dst_dimensions,
3002 weight,
3003 psp,
3004 synapse_attractivity,
3005 synapse_type,
3006 )?;
3007 if count > 0 {
3008 npu.rebuild_synapse_index();
3009 }
3010 Ok(count as usize)
3011 }
3012 _ => {
3013 use tracing::debug;
3016 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3017 Ok(0)
3018 }
3019 }
3020 }
3021
3022 fn apply_single_morphology_rule(
3024 &mut self,
3025 src_area_id: &CorticalID,
3026 dst_area_id: &CorticalID,
3027 rule: &serde_json::Value,
3028 ) -> BduResult<usize> {
3029 let morphology_id = if let Some(arr) = rule.as_array() {
3031 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3032 } else if let Some(obj) = rule.as_object() {
3033 obj.get("morphology_id")
3034 .and_then(|v| v.as_str())
3035 .unwrap_or("")
3036 } else {
3037 return Ok(0);
3038 };
3039
3040 if morphology_id.is_empty() {
3041 return Ok(0);
3042 }
3043
3044 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3046 crate::types::BduError::InvalidMorphology(format!(
3047 "Morphology not found: {}",
3048 morphology_id
3049 ))
3050 })?;
3051
3052 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3054 crate::types::BduError::InvalidArea(format!(
3055 "Source area ID not found: {}",
3056 src_area_id
3057 ))
3058 })?;
3059 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3060 crate::types::BduError::InvalidArea(format!(
3061 "Destination area ID not found: {}",
3062 dst_area_id
3063 ))
3064 })?;
3065
3066 if let Some(ref npu_arc) = self.npu {
3068 let lock_start = std::time::Instant::now();
3069 let mut npu = npu_arc.lock().unwrap();
3070 let lock_wait = lock_start.elapsed();
3071 tracing::debug!(
3072 target: "feagi-bdu",
3073 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3074 lock_wait.as_secs_f64() * 1000.0,
3075 src_area_id,
3076 dst_area_id,
3077 morphology_id
3078 );
3079
3080 let (weight, psp, synapse_type) =
3081 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3082
3083 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3085 obj.get("synapse_attractivity")
3086 .and_then(|v| v.as_u64())
3087 .unwrap_or(100) as u8
3088 } else {
3089 100 };
3091
3092 match morphology.morphology_type {
3093 feagi_evolutionary::MorphologyType::Functions => {
3094 tracing::warn!(
3095 target: "feagi-bdu",
3096 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3097 morphology_id
3098 );
3099 self.apply_function_morphology(
3102 morphology_id,
3103 rule,
3104 npu_arc,
3105 &mut npu,
3106 src_area_id,
3107 dst_area_id,
3108 *src_idx,
3109 *dst_idx,
3110 weight,
3111 psp,
3112 synapse_attractivity,
3113 synapse_type,
3114 )
3115 }
3116 feagi_evolutionary::MorphologyType::Vectors => {
3117 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3118
3119 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3121 crate::types::BduError::InvalidArea(format!(
3122 "Destination area not found: {}",
3123 dst_area_id
3124 ))
3125 })?;
3126
3127 let dst_dimensions = (
3128 dst_area.dimensions.width as usize,
3129 dst_area.dimensions.height as usize,
3130 dst_area.dimensions.depth as usize,
3131 );
3132
3133 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3134 morphology.parameters
3135 {
3136 let vectors_tuples: Vec<(i32, i32, i32)> =
3138 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3139
3140 let count = apply_vectors_morphology_with_dimensions(
3141 &mut npu,
3142 *src_idx,
3143 *dst_idx,
3144 vectors_tuples,
3145 dst_dimensions,
3146 weight, psp, synapse_attractivity, synapse_type,
3150 )?;
3151 npu.rebuild_synapse_index();
3154 Ok(count as usize)
3155 } else {
3156 Ok(0)
3157 }
3158 }
3159 feagi_evolutionary::MorphologyType::Patterns => {
3160 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3161 use crate::connectivity::rules::patterns::{
3162 Pattern3D, PatternElement as RulePatternElement,
3163 };
3164 use feagi_evolutionary::PatternElement as EvoPatternElement;
3165
3166 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3167 morphology.parameters
3168 else {
3169 return Ok(0);
3170 };
3171
3172 let convert_element =
3173 |element: &EvoPatternElement|
3174 -> crate::types::BduResult<RulePatternElement> {
3175 match element {
3176 EvoPatternElement::Value(value) => {
3177 if *value < 0 {
3178 return Err(crate::types::BduError::InvalidMorphology(
3179 format!(
3180 "Pattern morphology {} contains negative voxel coordinate {}",
3181 morphology_id, value
3182 ),
3183 ));
3184 }
3185 Ok(RulePatternElement::Exact(*value))
3186 }
3187 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3188 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3189 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3190 }
3191 };
3192
3193 let mut converted_patterns = Vec::with_capacity(patterns.len());
3194 for pattern_pair in patterns {
3195 if pattern_pair.len() != 2 {
3196 return Err(crate::types::BduError::InvalidMorphology(format!(
3197 "Pattern morphology {} must contain [src, dst] pairs",
3198 morphology_id
3199 )));
3200 }
3201
3202 let src_pattern = &pattern_pair[0];
3203 let dst_pattern = &pattern_pair[1];
3204
3205 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3206 return Err(crate::types::BduError::InvalidMorphology(format!(
3207 "Pattern morphology {} requires 3-axis patterns",
3208 morphology_id
3209 )));
3210 }
3211
3212 let src: Pattern3D = (
3213 convert_element(&src_pattern[0])?,
3214 convert_element(&src_pattern[1])?,
3215 convert_element(&src_pattern[2])?,
3216 );
3217 let dst: Pattern3D = (
3218 convert_element(&dst_pattern[0])?,
3219 convert_element(&dst_pattern[1])?,
3220 convert_element(&dst_pattern[2])?,
3221 );
3222
3223 converted_patterns.push((src, dst));
3224 }
3225
3226 let count = apply_patterns_morphology(
3227 &mut npu,
3228 *src_idx,
3229 *dst_idx,
3230 converted_patterns,
3231 weight,
3232 psp,
3233 synapse_attractivity,
3234 synapse_type,
3235 )?;
3236 if count > 0 {
3237 npu.rebuild_synapse_index();
3238 }
3239 Ok(count as usize)
3240 }
3241 feagi_evolutionary::MorphologyType::Composite => {
3242 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3243 morphology.parameters
3244 else {
3245 return Ok(0);
3246 };
3247
3248 if morphology_id != "tile" {
3249 use tracing::debug;
3250 debug!(
3251 target: "feagi-bdu",
3252 "Composite morphology {} not yet implemented",
3253 morphology_id
3254 );
3255 return Ok(0);
3256 }
3257
3258 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3259 crate::types::BduError::InvalidArea(format!(
3260 "Source area not found: {}",
3261 src_area_id
3262 ))
3263 })?;
3264 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3265 crate::types::BduError::InvalidArea(format!(
3266 "Destination area not found: {}",
3267 dst_area_id
3268 ))
3269 })?;
3270 let src_dimensions = (
3271 src_area.dimensions.width as usize,
3272 src_area.dimensions.height as usize,
3273 src_area.dimensions.depth as usize,
3274 );
3275 let dst_dimensions = (
3276 dst_area.dimensions.width as usize,
3277 dst_area.dimensions.height as usize,
3278 dst_area.dimensions.depth as usize,
3279 );
3280
3281 let count =
3282 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3283 &mut npu,
3284 *src_idx,
3285 *dst_idx,
3286 src_dimensions,
3287 dst_dimensions,
3288 weight,
3289 psp,
3290 synapse_attractivity,
3291 synapse_type,
3292 )?;
3293 if count > 0 {
3294 npu.rebuild_synapse_index();
3295 }
3296 Ok(count as usize)
3297 }
3298 }
3299 } else {
3300 Ok(0) }
3302 }
3303
3304 pub fn set_npu(
3317 &mut self,
3318 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3319 ) {
3320 self.npu = Some(Arc::clone(&npu));
3321 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3322
3323 #[cfg(not(feature = "wasm"))]
3326 {
3327 use feagi_state_manager::StateManager;
3328 let state_manager = StateManager::instance();
3329 let state_manager = state_manager.read();
3330 let core_state = state_manager.get_core_state();
3331 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3333 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3334 info!(
3335 target: "feagi-bdu",
3336 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3337 self.config.max_neurons, self.config.max_synapses
3338 );
3339 }
3340
3341 let existing_area_count = self.cortical_id_to_idx.len();
3348 if existing_area_count > 0 {
3349 match npu.lock() {
3350 Ok(mut npu_lock) => {
3351 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3352 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3353 }
3354 info!(
3355 target: "feagi-bdu",
3356 "🔁 Backfilled {} cortical area registrations into NPU",
3357 existing_area_count
3358 );
3359 }
3360 Err(e) => {
3361 warn!(
3362 target: "feagi-bdu",
3363 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3364 e
3365 );
3366 }
3367 }
3368 }
3369
3370 self.update_all_cached_stats();
3372 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3373 self.get_neuron_count(), self.get_synapse_count());
3374 }
3375
3376 pub fn has_npu(&self) -> bool {
3378 self.npu.is_some()
3379 }
3380
3381 pub fn get_npu(
3388 &self,
3389 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3390 {
3391 self.npu.as_ref()
3392 }
3393
3394 #[cfg(feature = "plasticity")]
3397 pub fn set_plasticity_executor(
3398 &mut self,
3399 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3400 ) {
3401 self.plasticity_executor = Some(executor);
3402 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3403 }
3404
3405 #[cfg(feature = "plasticity")]
3407 pub fn get_plasticity_executor(
3408 &self,
3409 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3410 self.plasticity_executor.as_ref()
3411 }
3412
3413 pub fn get_neuron_capacity(&self) -> usize {
3425 self.config.max_neurons
3427 }
3428
3429 pub fn get_synapse_capacity(&self) -> usize {
3441 self.config.max_synapses
3443 }
3444
3445 pub fn update_fatigue_index(&self) -> Option<u8> {
3460 let mut last_calc = match self.last_fatigue_calculation.lock() {
3462 Ok(guard) => guard,
3463 Err(_) => return None, };
3465
3466 let now = std::time::Instant::now();
3467 if now.duration_since(*last_calc).as_secs() < 2 {
3468 return None; }
3470 *last_calc = now;
3471 drop(last_calc);
3472
3473 let regular_neuron_count = self.get_neuron_count();
3475 let regular_neuron_capacity = self.get_neuron_capacity();
3476 let regular_neuron_util = if regular_neuron_capacity > 0 {
3477 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3478 } else {
3479 0
3480 };
3481
3482 let memory_neuron_util = match StateManager::instance().try_read() {
3486 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3487 None => {
3488 return None;
3490 }
3491 };
3492
3493 let synapse_count = self.get_synapse_count();
3495 let synapse_capacity = self.get_synapse_capacity();
3496 let synapse_util = if synapse_capacity > 0 {
3497 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3498 } else {
3499 0
3500 };
3501
3502 let fatigue_index = regular_neuron_util
3504 .max(memory_neuron_util)
3505 .max(synapse_util);
3506
3507 let current_fatigue_active = {
3509 StateManager::instance()
3511 .try_read()
3512 .map(|m| m.get_core_state().is_fatigue_active())
3513 .unwrap_or(false)
3514 };
3515
3516 let new_fatigue_active = if fatigue_index >= 85 {
3517 true
3518 } else if fatigue_index < 80 {
3519 false
3520 } else {
3521 current_fatigue_active };
3523
3524 if let Some(state_manager) = StateManager::instance().try_write() {
3528 let core_state = state_manager.get_core_state();
3529 core_state.set_fatigue_index(fatigue_index);
3530 core_state.set_fatigue_active(new_fatigue_active);
3531 core_state.set_regular_neuron_util(regular_neuron_util);
3532 core_state.set_memory_neuron_util(memory_neuron_util);
3533 core_state.set_synapse_util(synapse_util);
3534 } else {
3535 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3537 }
3538
3539 if let Some(ref npu) = self.npu {
3541 if let Ok(mut npu_lock) = npu.lock() {
3542 npu_lock.set_fatigue_active(new_fatigue_active);
3543 }
3544 }
3545
3546 trace!(
3547 target: "feagi-bdu",
3548 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3549 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3550 );
3551
3552 Some(fatigue_index)
3553 }
3554
3555 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3572 let area = self
3574 .cortical_areas
3575 .get(cortical_id)
3576 .ok_or_else(|| {
3577 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3578 })?
3579 .clone();
3580
3581 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3583 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3584 })?;
3585
3586 let npu = self
3588 .npu
3589 .as_ref()
3590 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3591
3592 use crate::models::CorticalAreaExt;
3595 let per_voxel_cnt = area.neurons_per_voxel();
3596 let firing_threshold = area.firing_threshold();
3597 let firing_threshold_increment_x = area.firing_threshold_increment_x();
3598 let firing_threshold_increment_y = area.firing_threshold_increment_y();
3599 let firing_threshold_increment_z = area.firing_threshold_increment_z();
3600 let firing_threshold_limit_raw = area.firing_threshold_limit();
3602 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
3603 f32::MAX } else {
3605 firing_threshold_limit_raw
3606 };
3607
3608 if firing_threshold_increment_x != 0.0
3610 || firing_threshold_increment_y != 0.0
3611 || firing_threshold_increment_z != 0.0
3612 {
3613 info!(
3614 target: "feagi-bdu",
3615 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
3616 cortical_id.as_base_64(),
3617 firing_threshold_increment_x,
3618 firing_threshold_increment_y,
3619 firing_threshold_increment_z
3620 );
3621 } else {
3622 if area.properties.contains_key("firing_threshold_increment_x")
3624 || area.properties.contains_key("firing_threshold_increment_y")
3625 || area.properties.contains_key("firing_threshold_increment_z")
3626 {
3627 info!(
3628 target: "feagi-bdu",
3629 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
3630 cortical_id.as_base_64(),
3631 area.properties.get("firing_threshold_increment_x"),
3632 area.properties.get("firing_threshold_increment_y"),
3633 area.properties.get("firing_threshold_increment_z")
3634 );
3635 }
3636 }
3637
3638 let leak_coefficient = area.leak_coefficient();
3639 let excitability = area.neuron_excitability();
3640 let refractory_period = area.refractory_period();
3641 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
3643 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
3644 u16::MAX } else {
3646 consecutive_fire_limit_raw
3647 };
3648 let snooze_length = area.snooze_period();
3649 let mp_charge_accumulation = area.mp_charge_accumulation();
3650
3651 let voxels = area.dimensions.width as usize
3653 * area.dimensions.height as usize
3654 * area.dimensions.depth as usize;
3655 let expected_neurons = voxels * per_voxel_cnt as usize;
3656
3657 trace!(
3658 target: "feagi-bdu",
3659 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
3660 cortical_id.as_base_64(),
3661 area.dimensions.width,
3662 area.dimensions.height,
3663 area.dimensions.depth,
3664 per_voxel_cnt,
3665 expected_neurons
3666 );
3667
3668 let mut npu_lock = npu
3671 .lock()
3672 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3673
3674 let neuron_count = npu_lock
3675 .create_cortical_area_neurons(
3676 *cortical_idx,
3677 area.dimensions.width,
3678 area.dimensions.height,
3679 area.dimensions.depth,
3680 per_voxel_cnt,
3681 firing_threshold,
3682 firing_threshold_increment_x,
3683 firing_threshold_increment_y,
3684 firing_threshold_increment_z,
3685 firing_threshold_limit,
3686 leak_coefficient,
3687 0.0, 0, refractory_period,
3690 excitability,
3691 consecutive_fire_limit,
3692 snooze_length,
3693 mp_charge_accumulation,
3694 )
3695 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?;
3696
3697 trace!(
3698 target: "feagi-bdu",
3699 "Created {} neurons for area {} via NPU",
3700 neuron_count,
3701 cortical_id.as_base_64()
3702 );
3703
3704 {
3707 let mut cache = self.cached_neuron_counts_per_area.write();
3708 cache
3709 .entry(*cortical_id)
3710 .or_insert_with(|| AtomicUsize::new(0))
3711 .store(neuron_count as usize, Ordering::Relaxed);
3712 }
3713
3714 let state_manager = StateManager::instance();
3716 let state_manager = state_manager.read();
3717 state_manager
3718 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
3719
3720 self.cached_neuron_count
3722 .fetch_add(neuron_count as usize, Ordering::Relaxed);
3723
3724 let state_manager = StateManager::instance();
3726 let state_manager = state_manager.read();
3727 let core_state = state_manager.get_core_state();
3728 core_state.add_neuron_count(neuron_count);
3729 core_state.add_regular_neuron_count(neuron_count);
3730
3731 Ok(neuron_count)
3739 }
3740
3741 #[allow(clippy::too_many_arguments)]
3765 pub fn add_neuron(
3766 &mut self,
3767 cortical_id: &CorticalID,
3768 x: u32,
3769 y: u32,
3770 z: u32,
3771 firing_threshold: f32,
3772 firing_threshold_limit: f32,
3773 leak_coefficient: f32,
3774 resting_potential: f32,
3775 neuron_type: u8,
3776 refractory_period: u16,
3777 excitability: f32,
3778 consecutive_fire_limit: u16,
3779 snooze_length: u16,
3780 mp_charge_accumulation: bool,
3781 ) -> BduResult<u64> {
3782 if !self.cortical_areas.contains_key(cortical_id) {
3784 return Err(BduError::InvalidArea(format!(
3785 "Cortical area {} not found",
3786 cortical_id
3787 )));
3788 }
3789
3790 let cortical_idx = *self
3791 .cortical_id_to_idx
3792 .get(cortical_id)
3793 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
3794
3795 let npu = self
3797 .npu
3798 .as_ref()
3799 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3800
3801 let mut npu_lock = npu
3802 .lock()
3803 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3804
3805 let neuron_id = npu_lock
3807 .add_neuron(
3808 firing_threshold,
3809 firing_threshold_limit,
3810 leak_coefficient,
3811 resting_potential,
3812 neuron_type as i32,
3813 refractory_period,
3814 excitability,
3815 consecutive_fire_limit,
3816 snooze_length,
3817 mp_charge_accumulation,
3818 cortical_idx,
3819 x,
3820 y,
3821 z,
3822 )
3823 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
3824
3825 trace!(
3826 target: "feagi-bdu",
3827 "Created neuron {} in area {} at ({}, {}, {})",
3828 neuron_id.0,
3829 cortical_id,
3830 x,
3831 y,
3832 z
3833 );
3834
3835 let state_manager = StateManager::instance();
3837 let state_manager = state_manager.read();
3838 let core_state = state_manager.get_core_state();
3839 core_state.add_neuron_count(1);
3840 core_state.add_regular_neuron_count(1);
3841 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3842
3843 Ok(neuron_id.0 as u64)
3844 }
3845
3846 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
3857 let npu = self
3859 .npu
3860 .as_ref()
3861 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
3862
3863 let mut npu_lock = npu
3864 .lock()
3865 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3866
3867 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
3868 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
3869
3870 let deleted = npu_lock.delete_neuron(neuron_id as u32);
3871
3872 if deleted {
3873 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
3874
3875 let state_manager = StateManager::instance();
3877 let state_manager = state_manager.read();
3878 let core_state = state_manager.get_core_state();
3879 core_state.subtract_neuron_count(1);
3880 core_state.subtract_regular_neuron_count(1);
3881 if let Some(cortical_id) = cortical_id {
3882 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
3883 }
3884
3885 }
3889
3890 Ok(deleted)
3891 }
3892
3893 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
3907 let src_area = self
3909 .cortical_areas
3910 .get(src_cortical_id)
3911 .ok_or_else(|| {
3912 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
3913 })?
3914 .clone();
3915
3916 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
3918 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
3919 _ => return Ok(0), };
3921
3922 let src_cortical_idx = *self
3923 .cortical_id_to_idx
3924 .get(src_cortical_id)
3925 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
3926
3927 let mut total_synapses = 0u32;
3928 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
3932 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
3934 Ok(id) => id,
3935 Err(_) => {
3936 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
3937 continue;
3938 }
3939 };
3940
3941 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
3943 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
3944 continue;
3945 }
3946
3947 let synapse_count =
3949 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
3950 total_synapses += synapse_count as u32;
3951
3952 upstream_updates.push((dst_cortical_id, src_cortical_idx));
3955 }
3956
3957 for (dst_id, src_idx) in upstream_updates {
3959 self.add_upstream_area(&dst_id, src_idx);
3960 }
3961
3962 trace!(
3963 target: "feagi-bdu",
3964 "Created {} synapses for area {} via NPU",
3965 total_synapses,
3966 src_cortical_id
3967 );
3968
3969 if total_synapses > 0 {
3972 let mut cache = self.cached_synapse_counts_per_area.write();
3973 cache
3974 .entry(*src_cortical_id)
3975 .or_insert_with(|| AtomicUsize::new(0))
3976 .fetch_add(total_synapses as usize, Ordering::Relaxed);
3977 }
3978
3979 self.cached_synapse_count
3981 .fetch_add(total_synapses as usize, Ordering::Relaxed);
3982
3983 if total_synapses > 0 {
3985 let state_manager = StateManager::instance();
3986 let state_manager = state_manager.read();
3987 let core_state = state_manager.get_core_state();
3988 core_state.add_synapse_count(total_synapses);
3989 }
3990
3991 Ok(total_synapses)
3992 }
3993
3994 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4013 if let Some(ref npu) = self.npu {
4014 if let Ok(npu_lock) = npu.lock() {
4015 npu_lock.is_neuron_valid(neuron_id as u32)
4017 } else {
4018 false
4019 }
4020 } else {
4021 false
4022 }
4023 }
4024
4025 pub fn get_neuron_count(&self) -> usize {
4037 if let Some(ref npu) = self.npu {
4039 if let Ok(npu_lock) = npu.try_lock() {
4040 let fresh_count = npu_lock.get_neuron_count();
4041 self.cached_neuron_count
4042 .store(fresh_count, Ordering::Relaxed);
4043 }
4044 }
4046
4047 self.cached_neuron_count.load(Ordering::Relaxed)
4049 }
4050
4051 pub fn update_cached_neuron_count(&self) {
4057 if let Some(ref npu) = self.npu {
4058 if let Ok(npu_lock) = npu.try_lock() {
4059 let count = npu_lock.get_neuron_count();
4060 self.cached_neuron_count.store(count, Ordering::Relaxed);
4061 }
4062 }
4063 }
4064
4065 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4069 let npu = self.npu.as_ref()?;
4070 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4071 let npu_lock = npu.lock().ok()?;
4072 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4073 drop(npu_lock);
4074
4075 let mut cache = self.cached_neuron_counts_per_area.write();
4076 cache
4077 .entry(*cortical_id)
4078 .or_insert_with(|| AtomicUsize::new(0))
4079 .store(count, Ordering::Relaxed);
4080
4081 let state_manager = StateManager::instance();
4083 let state_manager = state_manager.read();
4084 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4085
4086 self.update_cached_neuron_count();
4087
4088 Some(count)
4089 }
4090
4091 pub fn get_synapse_count(&self) -> usize {
4103 if let Some(ref npu) = self.npu {
4105 if let Ok(npu_lock) = npu.try_lock() {
4106 let fresh_count = npu_lock.get_synapse_count();
4107 self.cached_synapse_count
4108 .store(fresh_count, Ordering::Relaxed);
4109 }
4110 }
4112
4113 self.cached_synapse_count.load(Ordering::Relaxed)
4115 }
4116
4117 pub fn update_cached_synapse_count(&self) {
4123 if let Some(ref npu) = self.npu {
4124 if let Ok(npu_lock) = npu.try_lock() {
4125 let count = npu_lock.get_synapse_count();
4126 self.cached_synapse_count.store(count, Ordering::Relaxed);
4127 }
4128 }
4129 }
4130
4131 pub fn update_all_cached_stats(&self) {
4137 self.update_cached_neuron_count();
4138 self.update_cached_synapse_count();
4139 }
4140
4141 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4152 #[cfg(feature = "plasticity")]
4157 {
4158 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4159 return (0, 0, 0);
4160 }
4161 }
4162 if let Some(ref npu) = self.npu {
4163 if let Ok(npu_lock) = npu.lock() {
4164 npu_lock
4165 .get_neuron_coordinates(neuron_id as u32)
4166 .unwrap_or((0, 0, 0))
4167 } else {
4168 (0, 0, 0)
4169 }
4170 } else {
4171 (0, 0, 0)
4172 }
4173 }
4174
4175 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
4186 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
4187 }
4188
4189 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
4195 #[cfg(feature = "plasticity")]
4196 {
4197 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4198 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
4199 }
4200 }
4201 if let Some(ref npu) = self.npu {
4202 if let Ok(npu_lock) = npu.lock() {
4203 npu_lock.get_neuron_cortical_area(neuron_id as u32)
4204 } else {
4205 None
4206 }
4207 } else {
4208 None
4209 }
4210 }
4211
4212 #[cfg(feature = "plasticity")]
4214 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
4215 let exec = self.get_plasticity_executor()?;
4216 let guard = exec.lock().ok()?;
4217 guard
4218 .memory_neuron_detail(neuron_id)
4219 .map(|d| d.cortical_area_idx)
4220 }
4221
4222 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4233 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4235 Some(idx) => *idx,
4236 None => return Vec::new(),
4237 };
4238
4239 if let Some(ref npu) = self.npu {
4240 if let Ok(npu_lock) = npu.lock() {
4241 npu_lock
4243 .get_neurons_in_cortical_area(cortical_idx)
4244 .into_iter()
4245 .map(|id| id as u64)
4246 .collect()
4247 } else {
4248 Vec::new()
4249 }
4250 } else {
4251 Vec::new()
4252 }
4253 }
4254
4255 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4266 if let Some(ref npu) = self.npu {
4267 if let Ok(npu_lock) = npu.lock() {
4268 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4269 } else {
4270 Vec::new()
4271 }
4272 } else {
4273 Vec::new()
4274 }
4275 }
4276
4277 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4288 if let Some(ref npu) = self.npu {
4289 if let Ok(npu_lock) = npu.lock() {
4290 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4291 } else {
4292 Vec::new()
4293 }
4294 } else {
4295 Vec::new()
4296 }
4297 }
4298
4299 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4325 let cache = self.cached_neuron_counts_per_area.read();
4327 let base_count = cache
4328 .get(cortical_id)
4329 .map(|count| count.load(Ordering::Relaxed))
4330 .unwrap_or(0);
4331
4332 let memory_count = self
4334 .cortical_areas
4335 .get(cortical_id)
4336 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4337 .and_then(|_| {
4338 StateManager::instance()
4339 .try_read()
4340 .and_then(|state_manager| {
4341 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4342 })
4343 })
4344 .map(|stats| stats.neuron_count)
4345 .unwrap_or(0);
4346
4347 base_count.saturating_add(memory_count)
4348 }
4349
4350 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4357 let mut result = Vec::new();
4358
4359 for cortical_id in self.cortical_areas.keys() {
4360 let count = self.get_neuron_count_in_area(cortical_id);
4361 if count > 0 {
4362 result.push((cortical_id.to_string(), count));
4363 }
4364 }
4365
4366 result
4367 }
4368
4369 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4380 self.get_neuron_count_in_area(cortical_id) > 0
4381 }
4382
4383 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4399 let cache = self.cached_synapse_counts_per_area.read();
4401 cache
4402 .get(cortical_id)
4403 .map(|count| count.load(Ordering::Relaxed))
4404 .unwrap_or(0)
4405 }
4406
4407 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4417 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4418 return 0;
4419 }
4420
4421 if let Some(state_manager) = StateManager::instance().try_read() {
4422 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4423 return stats.incoming_synapse_count;
4424 }
4425 }
4426
4427 0
4428 }
4429
4430 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4440 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4441 return 0;
4442 }
4443
4444 if let Some(state_manager) = StateManager::instance().try_read() {
4445 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4446 return stats.outgoing_synapse_count;
4447 }
4448 }
4449
4450 0
4451 }
4452
4453 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4465 let synapses = self.get_outgoing_synapses(source_neuron_id);
4466 synapses
4467 .iter()
4468 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4469 }
4470
4471 pub fn get_connection_weight(
4483 &self,
4484 source_neuron_id: u64,
4485 target_neuron_id: u64,
4486 ) -> Option<f32> {
4487 let synapses = self.get_outgoing_synapses(source_neuron_id);
4488 synapses
4489 .iter()
4490 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4491 .map(|(_, weight, _, _)| *weight)
4492 }
4493
4494 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4505 let neurons = self.get_neurons_in_area(cortical_id);
4506 let neuron_count = neurons.len();
4507
4508 if neuron_count == 0 {
4509 return (0, 0, 0.0);
4510 }
4511
4512 let mut total_synapses = 0;
4513 for neuron_id in neurons {
4514 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4515 }
4516
4517 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4518
4519 (neuron_count, total_synapses, avg_synapses)
4520 }
4521
4522 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4533 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
4534 self.cortical_idx_to_id.get(&cortical_idx).copied()
4535 }
4536
4537 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4548 let area = match self.cortical_areas.get(cortical_id) {
4549 Some(a) => a,
4550 None => return 0.0,
4551 };
4552
4553 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4554 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4555
4556 if volume == 0 {
4557 return 0.0;
4558 }
4559
4560 neuron_count as f32 / volume as f32
4561 }
4562
4563 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
4570 let mut stats = Vec::new();
4571
4572 for cortical_id in self.cortical_areas.keys() {
4573 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4574 let synapse_count = self.get_synapse_count_in_area(cortical_id);
4575 let density = self.get_neuron_density(cortical_id);
4576
4577 stats.push((
4578 cortical_id.to_string(),
4579 neuron_count,
4580 synapse_count,
4581 density,
4582 ));
4583 }
4584
4585 stats
4586 }
4587
4588 pub fn get_config(&self) -> &ConnectomeConfig {
4594 &self.config
4595 }
4596
4597 pub fn set_config(&mut self, config: ConnectomeConfig) {
4599 self.config = config;
4600 }
4601
4602 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
4621 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
4622
4623 use feagi_structures::genomic::cortical_area::{
4624 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
4625 };
4626
4627 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
4629 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
4630 })?;
4631
4632 let core_position = (0, 0, 0).into();
4634
4635 let death_id = CoreCorticalType::Death.to_cortical_id();
4637 if !self.cortical_areas.contains_key(&death_id) {
4638 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
4639 let death_area = CorticalArea::new(
4640 death_id,
4641 0, "_death".to_string(),
4643 core_dimensions,
4644 core_position,
4645 CorticalAreaType::Core(CoreCorticalType::Death),
4646 )
4647 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
4648 match self.add_cortical_area(death_area) {
4649 Ok(idx) => {
4650 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
4651 }
4652 Err(e) => {
4653 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
4654 return Err(e);
4655 }
4656 }
4657 } else {
4658 info!(target: "feagi-bdu", " ✓ _death area already exists");
4659 }
4660
4661 let power_id = CoreCorticalType::Power.to_cortical_id();
4663 if !self.cortical_areas.contains_key(&power_id) {
4664 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
4665 let power_area = CorticalArea::new(
4666 power_id,
4667 1, "_power".to_string(),
4669 core_dimensions,
4670 core_position,
4671 CorticalAreaType::Core(CoreCorticalType::Power),
4672 )
4673 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
4674 match self.add_cortical_area(power_area) {
4675 Ok(idx) => {
4676 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
4677 }
4678 Err(e) => {
4679 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
4680 return Err(e);
4681 }
4682 }
4683 } else {
4684 info!(target: "feagi-bdu", " ✓ _power area already exists");
4685 }
4686
4687 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
4689 if !self.cortical_areas.contains_key(&fatigue_id) {
4690 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
4691 let fatigue_area = CorticalArea::new(
4692 fatigue_id,
4693 2, "_fatigue".to_string(),
4695 core_dimensions,
4696 core_position,
4697 CorticalAreaType::Core(CoreCorticalType::Fatigue),
4698 )
4699 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
4700 match self.add_cortical_area(fatigue_area) {
4701 Ok(idx) => {
4702 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
4703 }
4704 Err(e) => {
4705 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
4706 return Err(e);
4707 }
4708 }
4709 } else {
4710 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
4711 }
4712
4713 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
4714 Ok(())
4715 }
4716
4717 #[deprecated(
4734 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
4735 )]
4736 #[allow(deprecated)]
4737 pub fn save_genome_to_json(
4738 &self,
4739 genome_id: Option<String>,
4740 genome_title: Option<String>,
4741 ) -> BduResult<String> {
4742 let mut brain_regions_with_parents = std::collections::HashMap::new();
4744
4745 for region_id in self.brain_regions.get_all_region_ids() {
4746 if let Some(region) = self.brain_regions.get_region(region_id) {
4747 let parent_id = self
4748 .brain_regions
4749 .get_parent(region_id)
4750 .map(|s| s.to_string());
4751 brain_regions_with_parents
4752 .insert(region_id.to_string(), (region.clone(), parent_id));
4753 }
4754 }
4755
4756 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
4758 &self.cortical_areas,
4759 &brain_regions_with_parents,
4760 genome_id,
4761 genome_title,
4762 )?)
4763 }
4764
4765 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
4796 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
4797
4798 self.cortical_areas.clear();
4800 self.cortical_id_to_idx.clear();
4801 self.cortical_idx_to_id.clear();
4802 self.next_cortical_idx = 3;
4804 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 3 (reserves 0=_death, 1=_power, 2=_fatigue)");
4805
4806 self.brain_regions = BrainRegionHierarchy::new();
4808
4809 if let Some(ref npu) = self.npu {
4811 let mut npu_lock = npu
4812 .lock()
4813 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4814 npu_lock
4815 .reset_for_new_genome()
4816 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
4817 }
4818
4819 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
4820 Ok(())
4821 }
4822
4823 pub fn resize_for_genome(
4833 &mut self,
4834 genome: &feagi_evolutionary::RuntimeGenome,
4835 ) -> BduResult<()> {
4836 self.morphology_registry = genome.morphologies.clone();
4838 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
4839
4840 let required_neurons = genome.stats.innate_neuron_count;
4842 let required_synapses = genome.stats.innate_synapse_count;
4843
4844 info!(target: "feagi-bdu",
4845 "Genome requires: {} neurons, {} synapses",
4846 required_neurons,
4847 required_synapses
4848 );
4849
4850 let mut total_voxels = 0;
4852 for area in genome.cortical_areas.values() {
4853 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4854 }
4855
4856 info!(target: "feagi-bdu",
4857 "Genome has {} cortical areas with {} total voxels",
4858 genome.cortical_areas.len(),
4859 total_voxels
4860 );
4861
4862 Ok(())
4867 }
4868
4869 pub fn create_synapse(
4888 &mut self,
4889 source_neuron_id: u64,
4890 target_neuron_id: u64,
4891 weight: f32,
4892 psp: f32,
4893 synapse_type: u8,
4894 ) -> BduResult<()> {
4895 let npu = self
4897 .npu
4898 .as_ref()
4899 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4900
4901 let mut npu_lock = npu
4902 .lock()
4903 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4904
4905 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4907 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
4908
4909 if !source_exists {
4910 return Err(BduError::InvalidNeuron(format!(
4911 "Source neuron {} not found",
4912 source_neuron_id
4913 )));
4914 }
4915 if !target_exists {
4916 return Err(BduError::InvalidNeuron(format!(
4917 "Target neuron {} not found",
4918 target_neuron_id
4919 )));
4920 }
4921
4922 let syn_type = if synapse_type == 0 {
4924 feagi_npu_neural::synapse::SynapseType::Excitatory
4925 } else {
4926 feagi_npu_neural::synapse::SynapseType::Inhibitory
4927 };
4928
4929 let synapse_idx = npu_lock
4930 .add_synapse(
4931 NeuronId(source_neuron_id as u32),
4932 NeuronId(target_neuron_id as u32),
4933 feagi_npu_neural::types::SynapticWeight(weight),
4934 feagi_npu_neural::types::SynapticPsp(psp),
4935 syn_type,
4936 0,
4937 )
4938 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
4939
4940 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
4941 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
4942
4943 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
4944 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
4945 let source_cortical_id =
4946 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
4947 let target_cortical_id =
4948 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
4949
4950 let state_manager = StateManager::instance();
4951 let state_manager = state_manager.read();
4952 let core_state = state_manager.get_core_state();
4953 core_state.add_synapse_count(1);
4954 if let Some(cortical_id) = source_cortical_id {
4955 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
4956 }
4957 if let Some(cortical_id) = target_cortical_id {
4958 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
4959 }
4960
4961 Ok(())
4966 }
4967
4968 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
4971 if let Some(ref npu) = self.npu {
4972 if let Ok(mut npu_lock) = npu.lock() {
4973 let mut psp_uniform_flags = ahash::AHashMap::new();
4975 let mut mp_driven_psp_flags = ahash::AHashMap::new();
4976
4977 for (cortical_id, area) in &self.cortical_areas {
4978 let default_psp_uniform = *cortical_id
4981 == CoreCorticalType::Power.to_cortical_id()
4982 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
4983 let psp_uniform = area
4984 .get_property("psp_uniform_distribution")
4985 .and_then(|v| v.as_bool())
4986 .unwrap_or(default_psp_uniform);
4987 psp_uniform_flags.insert(*cortical_id, psp_uniform);
4988
4989 let mp_driven_psp = area
4991 .get_property("mp_driven_psp")
4992 .and_then(|v| v.as_bool())
4993 .unwrap_or(false);
4994 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
4995 }
4996
4997 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
4999 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5000
5001 trace!(
5002 target: "feagi-bdu",
5003 "Synchronized cortical area flags to NPU ({} areas)",
5004 self.cortical_areas.len()
5005 );
5006 }
5007 }
5008
5009 Ok(())
5010 }
5011
5012 pub fn get_synapse(
5024 &self,
5025 source_neuron_id: u64,
5026 target_neuron_id: u64,
5027 ) -> Option<(f32, f32, u8)> {
5028 let npu = self.npu.as_ref()?;
5030 let npu_lock = npu.lock().ok()?;
5031
5032 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
5035
5036 for (source_id, weight, psp, synapse_type) in incoming {
5038 if source_id == source_neuron_id as u32 {
5039 return Some((weight, psp, synapse_type));
5040 }
5041 }
5042
5043 None
5044 }
5045
5046 pub fn update_synapse_weight(
5059 &mut self,
5060 source_neuron_id: u64,
5061 target_neuron_id: u64,
5062 new_weight: f32,
5063 ) -> BduResult<()> {
5064 let npu = self
5066 .npu
5067 .as_ref()
5068 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5069
5070 let mut npu_lock = npu
5071 .lock()
5072 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5073
5074 let updated = npu_lock.update_synapse_weight(
5076 NeuronId(source_neuron_id as u32),
5077 NeuronId(target_neuron_id as u32),
5078 feagi_npu_neural::types::SynapticWeight(new_weight),
5079 );
5080
5081 if updated {
5082 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
5083 Ok(())
5084 } else {
5085 Err(BduError::InvalidSynapse(format!(
5086 "Synapse {} -> {} not found",
5087 source_neuron_id, target_neuron_id
5088 )))
5089 }
5090 }
5091
5092 pub fn remove_synapse(
5104 &mut self,
5105 source_neuron_id: u64,
5106 target_neuron_id: u64,
5107 ) -> BduResult<bool> {
5108 let npu = self
5110 .npu
5111 .as_ref()
5112 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5113
5114 let mut npu_lock = npu
5115 .lock()
5116 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5117
5118 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5119 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5120 let source_cortical_id =
5121 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5122 let target_cortical_id =
5123 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5124
5125 let removed = npu_lock.remove_synapse(
5127 NeuronId(source_neuron_id as u32),
5128 NeuronId(target_neuron_id as u32),
5129 );
5130
5131 if removed {
5132 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
5133
5134 let state_manager = StateManager::instance();
5136 let state_manager = state_manager.read();
5137 let core_state = state_manager.get_core_state();
5138 core_state.subtract_synapse_count(1);
5139 if let Some(cortical_id) = source_cortical_id {
5140 state_manager
5141 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5142 }
5143 if let Some(cortical_id) = target_cortical_id {
5144 state_manager
5145 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5146 }
5147 }
5148
5149 Ok(removed)
5150 }
5151
5152 pub fn batch_create_neurons(
5170 &mut self,
5171 cortical_id: &CorticalID,
5172 neurons: Vec<NeuronData>,
5173 ) -> BduResult<Vec<u64>> {
5174 let npu = self
5176 .npu
5177 .as_ref()
5178 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5179
5180 let mut npu_lock = npu
5181 .lock()
5182 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5183
5184 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5186 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5187 })?;
5188 let cortical_idx = area.cortical_idx;
5189
5190 let count = neurons.len();
5191
5192 let mut x_coords = Vec::with_capacity(count);
5194 let mut y_coords = Vec::with_capacity(count);
5195 let mut z_coords = Vec::with_capacity(count);
5196 let mut firing_thresholds = Vec::with_capacity(count);
5197 let mut threshold_limits = Vec::with_capacity(count);
5198 let mut leak_coeffs = Vec::with_capacity(count);
5199 let mut resting_potentials = Vec::with_capacity(count);
5200 let mut neuron_types = Vec::with_capacity(count);
5201 let mut refractory_periods = Vec::with_capacity(count);
5202 let mut excitabilities = Vec::with_capacity(count);
5203 let mut consec_fire_limits = Vec::with_capacity(count);
5204 let mut snooze_lengths = Vec::with_capacity(count);
5205 let mut mp_accums = Vec::with_capacity(count);
5206 let mut cortical_areas = Vec::with_capacity(count);
5207
5208 for (
5209 x,
5210 y,
5211 z,
5212 threshold,
5213 threshold_limit,
5214 leak,
5215 resting,
5216 ntype,
5217 refract,
5218 excit,
5219 consec_limit,
5220 snooze,
5221 mp_accum,
5222 ) in neurons
5223 {
5224 x_coords.push(x);
5225 y_coords.push(y);
5226 z_coords.push(z);
5227 firing_thresholds.push(threshold);
5228 threshold_limits.push(threshold_limit);
5229 leak_coeffs.push(leak);
5230 resting_potentials.push(resting);
5231 neuron_types.push(ntype);
5232 refractory_periods.push(refract);
5233 excitabilities.push(excit);
5234 consec_fire_limits.push(consec_limit);
5235 snooze_lengths.push(snooze);
5236 mp_accums.push(mp_accum);
5237 cortical_areas.push(cortical_idx);
5238 }
5239
5240 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5242
5243 let firing_thresholds_t = firing_thresholds;
5248 let threshold_limits_t = threshold_limits;
5249 let resting_potentials_t = resting_potentials;
5250 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5251 firing_thresholds_t,
5252 threshold_limits_t,
5253 leak_coeffs,
5254 resting_potentials_t,
5255 neuron_types,
5256 refractory_periods,
5257 excitabilities,
5258 consec_fire_limits,
5259 snooze_lengths,
5260 mp_accums,
5261 cortical_areas,
5262 x_coords,
5263 y_coords,
5264 z_coords,
5265 );
5266
5267 let mut neuron_ids = Vec::with_capacity(count);
5269 for i in 0..neurons_created {
5270 neuron_ids.push((first_neuron_id + i) as u64);
5271 }
5272
5273 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5274
5275 let state_manager = StateManager::instance();
5277 let state_manager = state_manager.read();
5278 let core_state = state_manager.get_core_state();
5279 core_state.add_neuron_count(neurons_created);
5280 core_state.add_regular_neuron_count(neurons_created);
5281 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5282
5283 {
5285 let mut cache = self.cached_neuron_counts_per_area.write();
5286 cache
5287 .entry(*cortical_id)
5288 .or_insert_with(|| AtomicUsize::new(0))
5289 .fetch_add(count, Ordering::Relaxed);
5290 }
5291
5292 Ok(neuron_ids)
5293 }
5294
5295 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5306 let npu = self
5308 .npu
5309 .as_ref()
5310 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5311
5312 let mut npu_lock = npu
5313 .lock()
5314 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5315
5316 let mut deleted_count = 0;
5317 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5318 std::collections::HashMap::new();
5319
5320 for neuron_id in neuron_ids {
5323 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5324 let cortical_id =
5325 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5326
5327 if npu_lock.delete_neuron(neuron_id as u32) {
5328 deleted_count += 1;
5329 if let Some(cortical_id) = cortical_id {
5330 let key = cortical_id.as_base_64();
5331 *per_area_deleted.entry(key).or_insert(0) += 1;
5332 }
5333 }
5334 }
5335
5336 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5337
5338 if deleted_count > 0 {
5340 let state_manager = StateManager::instance();
5341 let state_manager = state_manager.read();
5342 let core_state = state_manager.get_core_state();
5343 core_state.subtract_neuron_count(deleted_count as u32);
5344 core_state.subtract_regular_neuron_count(deleted_count as u32);
5345 for (cortical_id, count) in per_area_deleted {
5346 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5347 }
5348 }
5349
5350 Ok(deleted_count)
5357 }
5358
5359 pub fn update_neuron_properties(
5378 &mut self,
5379 neuron_id: u64,
5380 firing_threshold: Option<f32>,
5381 leak_coefficient: Option<f32>,
5382 resting_potential: Option<f32>,
5383 excitability: Option<f32>,
5384 ) -> BduResult<()> {
5385 let npu = self
5387 .npu
5388 .as_ref()
5389 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5390
5391 let mut npu_lock = npu
5392 .lock()
5393 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5394
5395 let neuron_id_u32 = neuron_id as u32;
5396
5397 let mut updated = false;
5399
5400 if let Some(threshold) = firing_threshold {
5402 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5403 updated = true;
5404 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5405 } else if !updated {
5406 return Err(BduError::InvalidNeuron(format!(
5407 "Neuron {} not found",
5408 neuron_id
5409 )));
5410 }
5411 }
5412
5413 if let Some(leak) = leak_coefficient {
5414 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5415 updated = true;
5416 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5417 } else if !updated {
5418 return Err(BduError::InvalidNeuron(format!(
5419 "Neuron {} not found",
5420 neuron_id
5421 )));
5422 }
5423 }
5424
5425 if let Some(resting) = resting_potential {
5426 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5427 updated = true;
5428 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5429 } else if !updated {
5430 return Err(BduError::InvalidNeuron(format!(
5431 "Neuron {} not found",
5432 neuron_id
5433 )));
5434 }
5435 }
5436
5437 if let Some(excit) = excitability {
5438 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
5439 updated = true;
5440 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
5441 } else if !updated {
5442 return Err(BduError::InvalidNeuron(format!(
5443 "Neuron {} not found",
5444 neuron_id
5445 )));
5446 }
5447 }
5448
5449 if !updated {
5450 return Err(BduError::Internal(
5451 "No properties provided for update".to_string(),
5452 ));
5453 }
5454
5455 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
5456
5457 Ok(())
5458 }
5459
5460 pub fn set_neuron_firing_threshold(
5472 &mut self,
5473 neuron_id: u64,
5474 new_threshold: f32,
5475 ) -> BduResult<()> {
5476 let npu = self
5478 .npu
5479 .as_ref()
5480 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5481
5482 let mut npu_lock = npu
5483 .lock()
5484 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5485
5486 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
5488 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
5489 Ok(())
5490 } else {
5491 Err(BduError::InvalidNeuron(format!(
5492 "Neuron {} not found",
5493 neuron_id
5494 )))
5495 }
5496 }
5497
5498 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
5513 self.cortical_areas
5514 .values()
5515 .find(|area| area.name == name)
5516 .cloned()
5517 }
5518
5519 pub fn resize_cortical_area(
5536 &mut self,
5537 cortical_id: &CorticalID,
5538 new_dimensions: CorticalAreaDimensions,
5539 ) -> BduResult<()> {
5540 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
5542 return Err(BduError::InvalidArea(format!(
5543 "Invalid dimensions: {:?} (all must be > 0)",
5544 new_dimensions
5545 )));
5546 }
5547
5548 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
5550 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5551 })?;
5552
5553 let old_dimensions = area.dimensions;
5554 area.dimensions = new_dimensions;
5555
5556 info!(target: "feagi-bdu",
5560 "Resized cortical area {} from {:?} to {:?}",
5561 cortical_id,
5562 old_dimensions,
5563 new_dimensions
5564 );
5565
5566 self.refresh_cortical_area_hashes(false, true);
5567
5568 Ok(())
5569 }
5570
5571 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
5582 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
5583 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5584 })?;
5585
5586 Ok(region
5588 .cortical_areas
5589 .iter()
5590 .map(|id| id.as_base_64())
5591 .collect())
5592 }
5593
5594 pub fn update_brain_region(
5607 &mut self,
5608 region_id: &str,
5609 new_name: Option<String>,
5610 new_description: Option<String>,
5611 ) -> BduResult<()> {
5612 let region = self
5613 .brain_regions
5614 .get_region_mut(region_id)
5615 .ok_or_else(|| {
5616 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5617 })?;
5618
5619 if let Some(name) = new_name {
5620 region.name = name;
5621 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
5622 }
5623
5624 if let Some(desc) = new_description {
5625 region
5627 .properties
5628 .insert("description".to_string(), serde_json::json!(desc));
5629 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
5630 }
5631
5632 info!(target: "feagi-bdu","Updated brain region {}", region_id);
5633
5634 self.refresh_brain_regions_hash();
5635
5636 Ok(())
5637 }
5638
5639 pub fn update_brain_region_properties(
5653 &mut self,
5654 region_id: &str,
5655 properties: std::collections::HashMap<String, serde_json::Value>,
5656 ) -> BduResult<()> {
5657 use tracing::{debug, info};
5658
5659 let region = self
5660 .brain_regions
5661 .get_region_mut(region_id)
5662 .ok_or_else(|| {
5663 BduError::InvalidArea(format!("Brain region {} not found", region_id))
5664 })?;
5665
5666 for (key, value) in properties {
5667 match key.as_str() {
5668 "title" | "name" | "region_title" => {
5670 if let Some(name) = value.as_str() {
5671 region.name = name.to_string();
5672 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
5673 }
5674 }
5675 "coordinate_3d" | "coordinates_3d" => {
5676 region
5677 .properties
5678 .insert("coordinate_3d".to_string(), value.clone());
5679 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
5680 }
5681 "coordinate_2d" | "coordinates_2d" => {
5682 region
5683 .properties
5684 .insert("coordinate_2d".to_string(), value.clone());
5685 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
5686 }
5687 "description" => {
5688 region
5689 .properties
5690 .insert("description".to_string(), value.clone());
5691 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
5692 }
5693 "region_type" => {
5694 if let Some(type_str) = value.as_str() {
5695 region.region_type = feagi_structures::genomic::RegionType::Undefined;
5698 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
5699 }
5700 }
5701 _ => {
5703 region.properties.insert(key.clone(), value.clone());
5704 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
5705 }
5706 }
5707 }
5708
5709 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
5710
5711 Ok(())
5712 }
5713
5714 pub fn get_neuron_by_coordinates(
5732 &self,
5733 cortical_id: &CorticalID,
5734 x: u32,
5735 y: u32,
5736 z: u32,
5737 ) -> Option<u64> {
5738 let area = self.get_cortical_area(cortical_id)?;
5740 let cortical_idx = area.cortical_idx;
5741
5742 let npu = self.npu.as_ref()?;
5744 let npu_lock = npu.lock().ok()?;
5745
5746 npu_lock
5747 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
5748 .map(|id| id as u64)
5749 }
5750
5751 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
5762 let npu = self.npu.as_ref()?;
5763 let npu_lock = npu.lock().ok()?;
5764
5765 let neuron_count = npu_lock.get_neuron_count();
5767 if (neuron_id as usize) >= neuron_count {
5768 return None;
5769 }
5770
5771 Some(
5772 npu_lock
5773 .get_neuron_coordinates(neuron_id as u32)
5774 .unwrap_or((0, 0, 0)),
5775 )
5776 }
5777
5778 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
5789 let npu = self.npu.as_ref()?;
5790 let npu_lock = npu.lock().ok()?;
5791
5792 let neuron_count = npu_lock.get_neuron_count();
5794 if (neuron_id as usize) >= neuron_count {
5795 return None;
5796 }
5797
5798 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
5799
5800 self.cortical_areas
5802 .values()
5803 .find(|area| area.cortical_idx == cortical_idx)
5804 .map(|area| area.cortical_id)
5805 }
5806
5807 pub fn get_neuron_properties(
5818 &self,
5819 neuron_id: u64,
5820 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
5821 let npu = self.npu.as_ref()?;
5822 let npu_lock = npu.lock().ok()?;
5823
5824 let neuron_id_u32 = neuron_id as u32;
5825 let idx = neuron_id as usize;
5826
5827 let neuron_count = npu_lock.get_neuron_count();
5829 if idx >= neuron_count {
5830 return None;
5831 }
5832
5833 let mut properties = std::collections::HashMap::new();
5834
5835 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
5837
5838 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
5840 properties.insert("x".to_string(), serde_json::json!(x));
5841 properties.insert("y".to_string(), serde_json::json!(y));
5842 properties.insert("z".to_string(), serde_json::json!(z));
5843
5844 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
5846 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
5847
5848 if let Some((consec_count, consec_limit, snooze, mp, threshold, refract_countdown)) =
5850 npu_lock.get_neuron_state(NeuronId(neuron_id_u32))
5851 {
5852 properties.insert(
5853 "consecutive_fire_count".to_string(),
5854 serde_json::json!(consec_count),
5855 );
5856 properties.insert(
5857 "consecutive_fire_limit".to_string(),
5858 serde_json::json!(consec_limit),
5859 );
5860 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
5861 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
5862 properties.insert("threshold".to_string(), serde_json::json!(threshold));
5863 properties.insert(
5864 "refractory_countdown".to_string(),
5865 serde_json::json!(refract_countdown),
5866 );
5867 }
5868
5869 if let Some(leak) = npu_lock.get_neuron_property_by_index(idx, "leak_coefficient") {
5871 properties.insert("leak_coefficient".to_string(), serde_json::json!(leak));
5872 }
5873 if let Some(resting) = npu_lock.get_neuron_property_by_index(idx, "resting_potential") {
5874 properties.insert("resting_potential".to_string(), serde_json::json!(resting));
5875 }
5876 if let Some(excit) = npu_lock.get_neuron_property_by_index(idx, "excitability") {
5877 properties.insert("excitability".to_string(), serde_json::json!(excit));
5878 }
5879 if let Some(threshold_limit) = npu_lock.get_neuron_property_by_index(idx, "threshold_limit")
5880 {
5881 properties.insert(
5882 "threshold_limit".to_string(),
5883 serde_json::json!(threshold_limit),
5884 );
5885 }
5886
5887 if let Some(refract_period) =
5889 npu_lock.get_neuron_property_u16_by_index(idx, "refractory_period")
5890 {
5891 properties.insert(
5892 "refractory_period".to_string(),
5893 serde_json::json!(refract_period),
5894 );
5895 }
5896
5897 Some(properties)
5898 }
5899
5900 pub fn get_neuron_property(
5912 &self,
5913 neuron_id: u64,
5914 property_name: &str,
5915 ) -> Option<serde_json::Value> {
5916 self.get_neuron_properties(neuron_id)?
5917 .get(property_name)
5918 .cloned()
5919 }
5920
5921 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
5932 self.cortical_areas.keys().copied().collect()
5933 }
5934
5935 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
5942 self.cortical_areas
5943 .values()
5944 .map(|area| area.cortical_idx)
5945 .collect()
5946 }
5947
5948 pub fn get_cortical_area_names(&self) -> Vec<String> {
5955 self.cortical_areas
5956 .values()
5957 .map(|area| area.name.clone())
5958 .collect()
5959 }
5960
5961 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
5968 use crate::models::CorticalAreaExt;
5969 self.cortical_areas
5970 .values()
5971 .filter(|area| area.is_input_area())
5972 .map(|area| area.cortical_id)
5973 .collect()
5974 }
5975
5976 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
5983 use crate::models::CorticalAreaExt;
5984 self.cortical_areas
5985 .values()
5986 .filter(|area| area.is_output_area())
5987 .map(|area| area.cortical_id)
5988 .collect()
5989 }
5990
5991 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
5998 self.cortical_areas
5999 .values()
6000 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
6001 (
6002 max_w.max(area.dimensions.width as usize),
6003 max_h.max(area.dimensions.height as usize),
6004 max_d.max(area.dimensions.depth as usize),
6005 )
6006 })
6007 }
6008
6009 pub fn get_cortical_area_properties(
6020 &self,
6021 cortical_id: &CorticalID,
6022 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6023 let area = self.get_cortical_area(cortical_id)?;
6024
6025 let mut properties = std::collections::HashMap::new();
6026 properties.insert(
6027 "cortical_id".to_string(),
6028 serde_json::json!(area.cortical_id),
6029 );
6030 properties.insert(
6031 "cortical_id_s".to_string(),
6032 serde_json::json!(area.cortical_id.to_string()),
6033 );
6034 properties.insert(
6035 "cortical_idx".to_string(),
6036 serde_json::json!(area.cortical_idx),
6037 );
6038 properties.insert("name".to_string(), serde_json::json!(area.name));
6039 use crate::models::CorticalAreaExt;
6040 properties.insert(
6041 "area_type".to_string(),
6042 serde_json::json!(area.get_cortical_group()),
6043 );
6044 properties.insert(
6045 "dimensions".to_string(),
6046 serde_json::json!({
6047 "width": area.dimensions.width,
6048 "height": area.dimensions.height,
6049 "depth": area.dimensions.depth,
6050 }),
6051 );
6052 properties.insert("position".to_string(), serde_json::json!(area.position));
6053
6054 for (key, value) in &area.properties {
6056 properties.insert(key.clone(), value.clone());
6057 }
6058
6059 properties.extend(area.properties.clone());
6061
6062 Some(properties)
6063 }
6064
6065 pub fn get_all_cortical_area_properties(
6072 &self,
6073 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
6074 self.cortical_areas
6075 .keys()
6076 .filter_map(|id| self.get_cortical_area_properties(id))
6077 .collect()
6078 }
6079
6080 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
6091 self.brain_regions
6092 .get_all_region_ids()
6093 .into_iter()
6094 .cloned()
6095 .collect()
6096 }
6097
6098 pub fn get_brain_region_names(&self) -> Vec<String> {
6105 self.brain_regions
6106 .get_all_region_ids()
6107 .iter()
6108 .filter_map(|id| {
6109 self.brain_regions
6110 .get_region(id)
6111 .map(|region| region.name.clone())
6112 })
6113 .collect()
6114 }
6115
6116 pub fn get_brain_region_properties(
6127 &self,
6128 region_id: &str,
6129 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6130 let region = self.brain_regions.get_region(region_id)?;
6131
6132 let mut properties = std::collections::HashMap::new();
6133 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
6134 properties.insert("name".to_string(), serde_json::json!(region.name));
6135 properties.insert(
6136 "region_type".to_string(),
6137 serde_json::json!(format!("{:?}", region.region_type)),
6138 );
6139 properties.insert(
6140 "cortical_areas".to_string(),
6141 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
6142 );
6143
6144 properties.extend(region.properties.clone());
6146
6147 Some(properties)
6148 }
6149
6150 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
6161 self.cortical_areas.contains_key(cortical_id)
6162 }
6163
6164 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6175 self.brain_regions.get_region(region_id).is_some()
6176 }
6177
6178 pub fn get_brain_region_count(&self) -> usize {
6185 self.brain_regions.region_count()
6186 }
6187
6188 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6199 self.get_neurons_in_area(cortical_id)
6203 }
6204}
6205
6206impl std::fmt::Debug for ConnectomeManager {
6208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6209 f.debug_struct("ConnectomeManager")
6210 .field("cortical_areas", &self.cortical_areas.len())
6211 .field("next_cortical_idx", &self.next_cortical_idx)
6212 .field("brain_regions", &self.brain_regions)
6213 .field(
6214 "npu",
6215 &if self.npu.is_some() {
6216 "Connected"
6217 } else {
6218 "Not connected"
6219 },
6220 )
6221 .field("initialized", &self.initialized)
6222 .finish()
6223 }
6224}
6225
6226#[cfg(test)]
6227mod tests {
6228 use super::*;
6229 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6230
6231 #[test]
6232 fn test_singleton_instance() {
6233 let instance1 = ConnectomeManager::instance();
6234 let instance2 = ConnectomeManager::instance();
6235
6236 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6238 }
6239
6240 #[test]
6241 fn test_add_cortical_area() {
6242 ConnectomeManager::reset_for_testing();
6243
6244 let instance = ConnectomeManager::instance();
6245 let mut manager = instance.write();
6246
6247 use feagi_structures::genomic::cortical_area::{
6248 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6249 };
6250 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6252 let area = CorticalArea::new(
6253 cortical_id,
6254 0,
6255 "Visual Input".to_string(),
6256 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6257 (0, 0, 0).into(),
6258 cortical_type,
6259 )
6260 .unwrap();
6261
6262 let initial_count = manager.get_cortical_area_count();
6263 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6264
6265 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6266 assert!(manager.has_cortical_area(&cortical_id));
6267 assert!(manager.is_initialized());
6268 }
6269
6270 #[test]
6271 fn test_cortical_area_lookups() {
6272 ConnectomeManager::reset_for_testing();
6273
6274 let instance = ConnectomeManager::instance();
6275 let mut manager = instance.write();
6276
6277 use feagi_structures::genomic::cortical_area::{
6278 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6279 };
6280 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6282 let area = CorticalArea::new(
6283 cortical_id,
6284 0,
6285 "Test Area".to_string(),
6286 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6287 (0, 0, 0).into(),
6288 cortical_type,
6289 )
6290 .unwrap();
6291
6292 let cortical_idx = manager.add_cortical_area(area).unwrap();
6293
6294 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6296
6297 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6299
6300 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6302 assert_eq!(retrieved_area.name, "Test Area");
6303 }
6304
6305 #[test]
6306 fn test_remove_cortical_area() {
6307 ConnectomeManager::reset_for_testing();
6308
6309 let instance = ConnectomeManager::instance();
6310 let mut manager = instance.write();
6311
6312 use feagi_structures::genomic::cortical_area::{
6313 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6314 };
6315 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6316
6317 if manager.has_cortical_area(&cortical_id) {
6319 manager.remove_cortical_area(&cortical_id).unwrap();
6320 }
6321
6322 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6323 let area = CorticalArea::new(
6324 cortical_id,
6325 0,
6326 "Test".to_string(),
6327 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6328 (0, 0, 0).into(),
6329 cortical_type,
6330 )
6331 .unwrap();
6332
6333 let initial_count = manager.get_cortical_area_count();
6334 manager.add_cortical_area(area).unwrap();
6335 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6336
6337 manager.remove_cortical_area(&cortical_id).unwrap();
6338 assert_eq!(manager.get_cortical_area_count(), initial_count);
6339 assert!(!manager.has_cortical_area(&cortical_id));
6340 }
6341
6342 #[test]
6343 fn test_duplicate_area_error() {
6344 ConnectomeManager::reset_for_testing();
6345
6346 let instance = ConnectomeManager::instance();
6347 let mut manager = instance.write();
6348
6349 use feagi_structures::genomic::cortical_area::{
6350 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6351 };
6352 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
6355 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6356 let area1 = CorticalArea::new(
6357 cortical_id,
6358 0,
6359 "First".to_string(),
6360 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6361 (0, 0, 0).into(),
6362 cortical_type,
6363 )
6364 .unwrap();
6365
6366 let area2 = CorticalArea::new(
6367 cortical_id, 1,
6369 "Second".to_string(),
6370 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6371 (0, 0, 0).into(),
6372 cortical_type,
6373 )
6374 .unwrap();
6375
6376 manager.add_cortical_area(area1).unwrap();
6377 let result = manager.add_cortical_area(area2);
6378
6379 assert!(result.is_err());
6380 }
6381
6382 #[test]
6383 fn test_brain_region_management() {
6384 ConnectomeManager::reset_for_testing();
6385
6386 let instance = ConnectomeManager::instance();
6387 let mut manager = instance.write();
6388
6389 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
6390 let region_id_str = region_id.to_string();
6391 let root = BrainRegion::new(
6392 region_id,
6393 "Root".to_string(),
6394 feagi_structures::genomic::brain_regions::RegionType::Undefined,
6395 )
6396 .unwrap();
6397
6398 let initial_count = manager.get_brain_region_ids().len();
6399 manager.add_brain_region(root, None).unwrap();
6400
6401 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
6402 assert!(manager.get_brain_region(®ion_id_str).is_some());
6403 }
6404
6405 #[test]
6406 fn test_synapse_operations() {
6407 use feagi_npu_burst_engine::npu::RustNPU;
6408 use feagi_npu_burst_engine::TracingMutex;
6409 use std::sync::Arc;
6410
6411 use feagi_npu_burst_engine::backend::CPUBackend;
6413 use feagi_npu_burst_engine::DynamicNPU;
6414 use feagi_npu_runtime::StdRuntime;
6415
6416 let runtime = StdRuntime;
6417 let backend = CPUBackend::new();
6418 let npu_result =
6419 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
6420 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
6421 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
6422
6423 use feagi_structures::genomic::cortical_area::{
6425 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6426 };
6427 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6429 let area = CorticalArea::new(
6430 cortical_id,
6431 0, "Test Area".to_string(),
6433 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
6434 (0, 0, 0).into(), cortical_type,
6436 )
6437 .unwrap();
6438 let cortical_idx = manager.add_cortical_area(area).unwrap();
6439
6440 if let Some(npu_arc) = manager.get_npu() {
6442 if let Ok(mut npu_guard) = npu_arc.try_lock() {
6443 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
6444 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
6445 }
6446 }
6447 }
6448
6449 let neuron1_id = manager
6451 .add_neuron(
6452 &cortical_id,
6453 0,
6454 0,
6455 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
6467 .unwrap();
6468
6469 let neuron2_id = manager
6470 .add_neuron(
6471 &cortical_id,
6472 1,
6473 0,
6474 0, 100.0,
6476 f32::MAX, 0.1,
6478 -60.0,
6479 0,
6480 2,
6481 1.0,
6482 5,
6483 10,
6484 false,
6485 )
6486 .unwrap();
6487
6488 manager
6490 .create_synapse(
6491 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
6495 .unwrap();
6496
6497 println!("✅ Synapse creation test passed");
6500 }
6501
6502 #[test]
6503 fn test_apply_cortical_mapping_missing_rules_is_ok() {
6504 let mut manager = ConnectomeManager::new_for_testing();
6507
6508 use feagi_structures::genomic::cortical_area::{
6509 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6510 };
6511
6512 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
6513 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
6514
6515 let src_area = CorticalArea::new(
6516 src_id,
6517 0,
6518 "src".to_string(),
6519 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6520 (0, 0, 0).into(),
6521 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6522 )
6523 .unwrap();
6524
6525 let dst_area = CorticalArea::new(
6526 dst_id,
6527 1,
6528 "dst".to_string(),
6529 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6530 (0, 0, 0).into(),
6531 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6532 )
6533 .unwrap();
6534
6535 manager.add_cortical_area(src_area).unwrap();
6536 manager.add_cortical_area(dst_area).unwrap();
6537
6538 let count = manager
6540 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6541 .unwrap();
6542 assert_eq!(count, 0);
6543
6544 manager
6546 .update_cortical_mapping(
6547 &src_id,
6548 &dst_id,
6549 vec![serde_json::json!({"morphology_id":"m1"})],
6550 )
6551 .unwrap();
6552 manager
6553 .update_cortical_mapping(&src_id, &dst_id, vec![])
6554 .unwrap();
6555
6556 let count2 = manager
6557 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
6558 .unwrap();
6559 assert_eq!(count2, 0);
6560 }
6561
6562 #[test]
6563 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
6564 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6565 let mapping_dst = serde_json::json!({
6566 "csrc0002": [
6567 {"morphology_id": "m1"}
6568 ]
6569 });
6570 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
6571
6572 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
6573 .expect("legacy destination key should resolve");
6574 assert_eq!(rules.len(), 1);
6575 assert_eq!(
6576 rules[0].get("morphology_id").and_then(|v| v.as_str()),
6577 Some("m1")
6578 );
6579 }
6580
6581 #[test]
6582 fn test_mapping_deletion_prunes_synapses_between_areas() {
6583 use feagi_npu_burst_engine::backend::CPUBackend;
6584 use feagi_npu_burst_engine::RustNPU;
6585 use feagi_npu_burst_engine::TracingMutex;
6586 use feagi_npu_runtime::StdRuntime;
6587 use feagi_structures::genomic::cortical_area::{
6588 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6589 };
6590 use std::sync::Arc;
6591
6592 let runtime = StdRuntime;
6594 let backend = CPUBackend::new();
6595 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6596 let dyn_npu = Arc::new(TracingMutex::new(
6597 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6598 "TestNPU",
6599 ));
6600 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6601
6602 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
6604 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
6605
6606 let src_area = CorticalArea::new(
6607 src_id,
6608 0,
6609 "src".to_string(),
6610 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6611 (0, 0, 0).into(),
6612 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6613 )
6614 .unwrap();
6615 let dst_area = CorticalArea::new(
6616 dst_id,
6617 1,
6618 "dst".to_string(),
6619 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6620 (0, 0, 0).into(),
6621 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6622 )
6623 .unwrap();
6624
6625 manager.add_cortical_area(src_area).unwrap();
6626 manager.add_cortical_area(dst_area).unwrap();
6627
6628 let s0 = manager
6630 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6631 .unwrap();
6632 let s1 = manager
6633 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6634 .unwrap();
6635 let t0 = manager
6636 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6637 .unwrap();
6638 let t1 = manager
6639 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6640 .unwrap();
6641
6642 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
6644 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
6645
6646 {
6648 let mut npu = dyn_npu.lock().unwrap();
6649 npu.rebuild_synapse_index();
6650 assert_eq!(npu.get_synapse_count(), 2);
6651 }
6652
6653 manager
6655 .update_cortical_mapping(&src_id, &dst_id, vec![])
6656 .unwrap();
6657 let created = manager
6658 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6659 .unwrap();
6660 assert_eq!(created, 0);
6661
6662 {
6664 let mut npu = dyn_npu.lock().unwrap();
6665 npu.rebuild_synapse_index();
6667 assert_eq!(npu.get_synapse_count(), 0);
6668 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6669 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6670 }
6671 }
6672
6673 #[test]
6674 fn test_mapping_update_prunes_synapses_between_areas() {
6675 use feagi_npu_burst_engine::backend::CPUBackend;
6676 use feagi_npu_burst_engine::RustNPU;
6677 use feagi_npu_burst_engine::TracingMutex;
6678 use feagi_npu_runtime::StdRuntime;
6679 use feagi_structures::genomic::cortical_area::{
6680 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
6681 };
6682 use std::sync::Arc;
6683
6684 let runtime = StdRuntime;
6686 let backend = CPUBackend::new();
6687 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6688 let dyn_npu = Arc::new(TracingMutex::new(
6689 feagi_npu_burst_engine::DynamicNPU::F32(npu),
6690 "TestNPU",
6691 ));
6692 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6693
6694 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6696
6697 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
6700 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
6701
6702 let src_area = CorticalArea::new(
6703 src_id,
6704 0,
6705 "src".to_string(),
6706 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6707 (0, 0, 0).into(),
6708 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
6709 )
6710 .unwrap();
6711 let dst_area = CorticalArea::new(
6712 dst_id,
6713 0,
6714 "dst".to_string(),
6715 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6716 (0, 0, 0).into(),
6717 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
6718 )
6719 .unwrap();
6720
6721 manager.add_cortical_area(src_area).unwrap();
6722 manager.add_cortical_area(dst_area).unwrap();
6723
6724 let s0 = manager
6726 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6727 .unwrap();
6728 let s1 = manager
6729 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6730 .unwrap();
6731 let t0 = manager
6732 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6733 .unwrap();
6734 let t1 = manager
6735 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6736 .unwrap();
6737
6738 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
6740 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
6741
6742 {
6744 let mut npu = dyn_npu.lock().unwrap();
6745 npu.rebuild_synapse_index();
6746 assert_eq!(npu.get_synapse_count(), 2);
6747 }
6748
6749 manager
6755 .update_cortical_mapping(
6756 &src_id,
6757 &dst_id,
6758 vec![serde_json::json!({
6759 "morphology_id": "episodic_memory",
6760 "morphology_scalar": [1],
6761 "postSynapticCurrent_multiplier": 1,
6762 "plasticity_flag": false,
6763 "plasticity_constant": 0,
6764 "ltp_multiplier": 0,
6765 "ltd_multiplier": 0,
6766 "plasticity_window": 0,
6767 })],
6768 )
6769 .unwrap();
6770 let created = manager
6771 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6772 .unwrap();
6773 assert_eq!(created, 0);
6774
6775 {
6777 let mut npu = dyn_npu.lock().unwrap();
6778 npu.rebuild_synapse_index();
6780 assert_eq!(npu.get_synapse_count(), 0);
6781 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
6782 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
6783 }
6784 }
6785
6786 #[test]
6787 fn test_upstream_area_tracking() {
6788 use crate::models::cortical_area::CorticalArea;
6790 use feagi_npu_burst_engine::backend::CPUBackend;
6791 use feagi_npu_burst_engine::TracingMutex;
6792 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6793 use feagi_npu_runtime::StdRuntime;
6794 use feagi_structures::genomic::cortical_area::{
6795 CorticalAreaDimensions, CorticalAreaType, CorticalID,
6796 };
6797
6798 let runtime = StdRuntime;
6800 let backend = CPUBackend::new();
6801 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6802 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6803 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6804
6805 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6808
6809 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
6811 let src_area = CorticalArea::new(
6812 src_id,
6813 0,
6814 "Source Area".to_string(),
6815 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6816 (0, 0, 0).into(),
6817 CorticalAreaType::Custom(
6818 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6819 ),
6820 )
6821 .unwrap();
6822 let src_idx = manager.add_cortical_area(src_area).unwrap();
6823
6824 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
6826 let dst_area = CorticalArea::new(
6827 dst_id,
6828 0,
6829 "Dest Area".to_string(),
6830 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
6831 (0, 0, 0).into(),
6832 CorticalAreaType::Custom(
6833 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6834 ),
6835 )
6836 .unwrap();
6837 manager.add_cortical_area(dst_area).unwrap();
6838
6839 {
6841 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
6842 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
6843 assert!(
6844 upstream.as_array().unwrap().is_empty(),
6845 "Upstream areas should be empty initially"
6846 );
6847 }
6848
6849 let mapping_data = vec![serde_json::json!({
6851 "morphology_id": "episodic_memory",
6852 "morphology_scalar": 1,
6853 "postSynapticCurrent_multiplier": 1.0,
6854 })];
6855 manager
6856 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
6857 .unwrap();
6858 manager
6859 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6860 .unwrap();
6861
6862 {
6864 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6865 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
6866 assert_eq!(
6867 upstream_areas[0], src_idx,
6868 "Upstream area should be src_idx"
6869 );
6870 }
6871
6872 manager
6874 .update_cortical_mapping(&src_id, &dst_id, vec![])
6875 .unwrap();
6876 manager
6877 .regenerate_synapses_for_mapping(&src_id, &dst_id)
6878 .unwrap();
6879
6880 {
6882 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
6883 assert_eq!(
6884 upstream_areas.len(),
6885 0,
6886 "Should have 0 upstream areas after deletion"
6887 );
6888 }
6889 }
6890
6891 #[test]
6892 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
6893 use crate::models::cortical_area::CorticalArea;
6894 use feagi_npu_burst_engine::backend::CPUBackend;
6895 use feagi_npu_burst_engine::TracingMutex;
6896 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
6897 use feagi_npu_runtime::StdRuntime;
6898 use feagi_structures::genomic::cortical_area::{
6899 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
6900 };
6901 use std::sync::Arc;
6902
6903 let runtime = StdRuntime;
6904 let backend = CPUBackend::new();
6905 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
6906 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
6907 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
6908 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
6909
6910 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
6911 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
6912 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
6913 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
6914
6915 let a1_area = CorticalArea::new(
6916 a1_id,
6917 0,
6918 "A1".to_string(),
6919 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6920 (0, 0, 0).into(),
6921 CorticalAreaType::Custom(
6922 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6923 ),
6924 )
6925 .unwrap();
6926 let a2_area = CorticalArea::new(
6927 a2_id,
6928 0,
6929 "A2".to_string(),
6930 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6931 (0, 0, 0).into(),
6932 CorticalAreaType::Custom(
6933 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
6934 ),
6935 )
6936 .unwrap();
6937
6938 let mut m1_area = CorticalArea::new(
6939 m1_id,
6940 0,
6941 "M1".to_string(),
6942 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6943 (0, 0, 0).into(),
6944 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6945 )
6946 .unwrap();
6947 m1_area
6948 .properties
6949 .insert("is_mem_type".to_string(), serde_json::json!(true));
6950 m1_area
6951 .properties
6952 .insert("temporal_depth".to_string(), serde_json::json!(1));
6953
6954 let mut m2_area = CorticalArea::new(
6955 m2_id,
6956 0,
6957 "M2".to_string(),
6958 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
6959 (0, 0, 0).into(),
6960 CorticalAreaType::Memory(MemoryCorticalType::Memory),
6961 )
6962 .unwrap();
6963 m2_area
6964 .properties
6965 .insert("is_mem_type".to_string(), serde_json::json!(true));
6966 m2_area
6967 .properties
6968 .insert("temporal_depth".to_string(), serde_json::json!(1));
6969
6970 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
6971 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
6972 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
6973 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
6974
6975 manager
6976 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6977 .unwrap();
6978 manager
6979 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
6980 .unwrap();
6981
6982 let episodic_mapping = vec![serde_json::json!({
6983 "morphology_id": "episodic_memory",
6984 "morphology_scalar": 1,
6985 "postSynapticCurrent_multiplier": 1.0,
6986 })];
6987 manager
6988 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
6989 .unwrap();
6990 manager
6991 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
6992 .unwrap();
6993 manager
6994 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
6995 .unwrap();
6996 manager
6997 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
6998 .unwrap();
6999
7000 let assoc_mapping = vec![serde_json::json!({
7001 "morphology_id": "associative_memory",
7002 "morphology_scalar": 1,
7003 "postSynapticCurrent_multiplier": 1.0,
7004 "plasticity_flag": true,
7005 "plasticity_constant": 1,
7006 "ltp_multiplier": 1,
7007 "ltd_multiplier": 1,
7008 "plasticity_window": 5,
7009 })];
7010 manager
7011 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
7012 .unwrap();
7013 manager
7014 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7015 .unwrap();
7016 manager
7018 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
7019 .unwrap();
7020 manager
7021 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
7022 .unwrap();
7023
7024 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7025 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7026 assert_eq!(
7027 upstream_m1.len(),
7028 2,
7029 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
7030 );
7031 assert_eq!(
7032 upstream_m2.len(),
7033 2,
7034 "M2 should have A2 and M1 as upstreams"
7035 );
7036
7037 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
7038 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
7039
7040 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7041 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7042 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
7043 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
7044 assert!(upstream_m1.contains(&a1_idx));
7045 assert!(upstream_m1.contains(&m2_idx));
7046 assert!(upstream_m2.contains(&a2_idx));
7047 assert!(upstream_m2.contains(&m1_idx));
7048
7049 {
7051 let mut npu_lock = dyn_npu.lock().unwrap();
7052 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
7053 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
7054 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
7055 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
7056 npu_lock.process_burst().expect("Burst processing failed");
7057 }
7058
7059 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7060 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7061 assert_eq!(
7062 upstream_m1.len(),
7063 2,
7064 "M1 should keep 2 upstreams after firing"
7065 );
7066 assert_eq!(
7067 upstream_m2.len(),
7068 2,
7069 "M2 should keep 2 upstreams after firing"
7070 );
7071 }
7072
7073 #[test]
7074 fn test_memory_twin_created_for_memory_mapping() {
7075 use crate::models::cortical_area::CorticalArea;
7076 use feagi_npu_burst_engine::backend::CPUBackend;
7077 use feagi_npu_burst_engine::TracingMutex;
7078 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7079 use feagi_npu_runtime::StdRuntime;
7080 use feagi_structures::genomic::cortical_area::{
7081 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7082 MemoryCorticalType,
7083 };
7084 use std::sync::Arc;
7085
7086 let runtime = StdRuntime;
7087 let backend = CPUBackend::new();
7088 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7089 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7090 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7091 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7092
7093 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
7094 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
7095
7096 let src_area = CorticalArea::new(
7097 src_id,
7098 0,
7099 "Source Area".to_string(),
7100 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7101 (0, 0, 0).into(),
7102 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7103 )
7104 .unwrap();
7105 let mut dst_area = CorticalArea::new(
7106 dst_id,
7107 0,
7108 "Memory Area".to_string(),
7109 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7110 (0, 0, 0).into(),
7111 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7112 )
7113 .unwrap();
7114 dst_area
7115 .properties
7116 .insert("is_mem_type".to_string(), serde_json::json!(true));
7117 dst_area
7118 .properties
7119 .insert("temporal_depth".to_string(), serde_json::json!(1));
7120
7121 manager.add_cortical_area(src_area).unwrap();
7122 manager.add_cortical_area(dst_area).unwrap();
7123
7124 let mapping_data = vec![serde_json::json!({
7125 "morphology_id": "episodic_memory",
7126 "morphology_scalar": 1,
7127 "postSynapticCurrent_multiplier": 1.0,
7128 })];
7129 manager
7130 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7131 .unwrap();
7132 manager
7133 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7134 .unwrap();
7135
7136 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7137 let twin_map = memory_area
7138 .properties
7139 .get("memory_twin_areas")
7140 .and_then(|v| v.as_object())
7141 .expect("memory_twin_areas should be set");
7142 let twin_id_str = twin_map
7143 .get(&src_id.as_base_64())
7144 .and_then(|v| v.as_str())
7145 .expect("Missing twin entry for upstream area");
7146 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7147 let mapping = memory_area
7148 .properties
7149 .get("cortical_mapping_dst")
7150 .and_then(|v| v.as_object())
7151 .and_then(|map| map.get(&twin_id.as_base_64()))
7152 .and_then(|v| v.as_array())
7153 .expect("Missing memory replay mapping for twin area");
7154 let uses_replay = mapping.iter().any(|rule| {
7155 rule.get("morphology_id")
7156 .and_then(|v| v.as_str())
7157 .is_some_and(|id| id == "memory_replay")
7158 });
7159 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7160
7161 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7162 assert!(matches!(
7163 twin_area.cortical_type,
7164 CorticalAreaType::Custom(_)
7165 ));
7166 assert_eq!(
7167 twin_area
7168 .properties
7169 .get("memory_twin_of")
7170 .and_then(|v| v.as_str()),
7171 Some(src_id.as_base_64().as_str())
7172 );
7173 assert_eq!(
7174 twin_area
7175 .properties
7176 .get("memory_twin_for")
7177 .and_then(|v| v.as_str()),
7178 Some(dst_id.as_base_64().as_str())
7179 );
7180 }
7181
7182 #[test]
7183 fn test_associative_memory_between_memory_areas_creates_synapses() {
7184 use crate::models::cortical_area::CorticalArea;
7185 use feagi_npu_burst_engine::backend::CPUBackend;
7186 use feagi_npu_burst_engine::TracingMutex;
7187 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7188 use feagi_npu_runtime::StdRuntime;
7189 use feagi_structures::genomic::cortical_area::{
7190 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7191 };
7192 use std::sync::Arc;
7193
7194 let runtime = StdRuntime;
7195 let backend = CPUBackend::new();
7196 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7197 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7198 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7199 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7200
7201 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
7202 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
7203
7204 let mut m1_area = CorticalArea::new(
7205 m1_id,
7206 0,
7207 "Memory M1".to_string(),
7208 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7209 (0, 0, 0).into(),
7210 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7211 )
7212 .unwrap();
7213 m1_area
7214 .properties
7215 .insert("is_mem_type".to_string(), serde_json::json!(true));
7216 m1_area
7217 .properties
7218 .insert("temporal_depth".to_string(), serde_json::json!(1));
7219
7220 let mut m2_area = CorticalArea::new(
7221 m2_id,
7222 0,
7223 "Memory M2".to_string(),
7224 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7225 (0, 0, 0).into(),
7226 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7227 )
7228 .unwrap();
7229 m2_area
7230 .properties
7231 .insert("is_mem_type".to_string(), serde_json::json!(true));
7232 m2_area
7233 .properties
7234 .insert("temporal_depth".to_string(), serde_json::json!(1));
7235
7236 manager.add_cortical_area(m1_area).unwrap();
7237 manager.add_cortical_area(m2_area).unwrap();
7238
7239 manager
7240 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7241 .unwrap();
7242 manager
7243 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7244 .unwrap();
7245
7246 let mapping_data = vec![serde_json::json!({
7247 "morphology_id": "associative_memory",
7248 "morphology_scalar": 1,
7249 "postSynapticCurrent_multiplier": 1.0,
7250 "plasticity_flag": true,
7251 "plasticity_constant": 1,
7252 "ltp_multiplier": 1,
7253 "ltd_multiplier": 1,
7254 "plasticity_window": 5,
7255 })];
7256 manager
7257 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
7258 .unwrap();
7259 let created = manager
7260 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7261 .unwrap();
7262 assert!(
7263 created > 0,
7264 "Expected associative memory mapping between memory areas to create synapses"
7265 );
7266 let npu_guard = dyn_npu.lock().unwrap();
7267 let assoc_tagged =
7268 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
7269 assert!(
7270 assoc_tagged >= 1,
7271 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
7272 );
7273 }
7274
7275 #[test]
7276 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7277 use crate::models::cortical_area::CorticalArea;
7278 use feagi_npu_burst_engine::backend::CPUBackend;
7279 use feagi_npu_burst_engine::TracingMutex;
7280 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7281 use feagi_npu_runtime::StdRuntime;
7282 use feagi_structures::genomic::cortical_area::{
7283 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7284 MemoryCorticalType,
7285 };
7286 use std::sync::Arc;
7287
7288 let runtime = StdRuntime;
7289 let backend = CPUBackend::new();
7290 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7291 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7292 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7293 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7294
7295 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7296 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7297
7298 let src_area = CorticalArea::new(
7299 src_id,
7300 0,
7301 "Source Area".to_string(),
7302 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7303 (0, 0, 0).into(),
7304 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7305 )
7306 .unwrap();
7307 let mut mem_area = CorticalArea::new(
7308 mem_id,
7309 0,
7310 "Memory Area".to_string(),
7311 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7312 (0, 0, 0).into(),
7313 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7314 )
7315 .unwrap();
7316 mem_area
7317 .properties
7318 .insert("is_mem_type".to_string(), serde_json::json!(true));
7319 mem_area
7320 .properties
7321 .insert("temporal_depth".to_string(), serde_json::json!(1));
7322
7323 manager.add_cortical_area(src_area).unwrap();
7324 manager.add_cortical_area(mem_area).unwrap();
7325
7326 let twin_id = manager
7327 .build_memory_twin_id(&mem_id, &src_id)
7328 .expect("Failed to build twin id");
7329 let twin_area = CorticalArea::new(
7330 twin_id,
7331 0,
7332 "Source Area_twin".to_string(),
7333 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7334 (0, 0, 0).into(),
7335 CorticalAreaType::Custom(
7336 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7337 ),
7338 )
7339 .unwrap();
7340 manager.add_cortical_area(twin_area).unwrap();
7341
7342 let repaired = manager
7343 .ensure_memory_twin_area(&mem_id, &src_id)
7344 .expect("Failed to repair twin");
7345 assert_eq!(repaired, twin_id);
7346
7347 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
7348 let twin_map = mem_area
7349 .properties
7350 .get("memory_twin_areas")
7351 .and_then(|v| v.as_object())
7352 .expect("memory_twin_areas should be set");
7353 let twin_id_str = twin_map
7354 .get(&src_id.as_base_64())
7355 .and_then(|v| v.as_str())
7356 .expect("Missing twin entry for upstream area");
7357 assert_eq!(twin_id_str, twin_id.as_base_64());
7358
7359 let replay_map = mem_area
7360 .properties
7361 .get("cortical_mapping_dst")
7362 .and_then(|v| v.as_object())
7363 .and_then(|map| map.get(&twin_id.as_base_64()))
7364 .and_then(|v| v.as_array())
7365 .expect("Missing memory replay mapping for twin area");
7366 let uses_replay = replay_map.iter().any(|rule| {
7367 rule.get("morphology_id")
7368 .and_then(|v| v.as_str())
7369 .is_some_and(|id| id == "memory_replay")
7370 });
7371 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7372
7373 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7374 assert_eq!(
7375 twin_area
7376 .properties
7377 .get("memory_twin_of")
7378 .and_then(|v| v.as_str()),
7379 Some(src_id.as_base_64().as_str())
7380 );
7381 assert_eq!(
7382 twin_area
7383 .properties
7384 .get("memory_twin_for")
7385 .and_then(|v| v.as_str()),
7386 Some(mem_id.as_base_64().as_str())
7387 );
7388 }
7389}