1use once_cell::sync::Lazy;
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35use std::hash::Hasher;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::sync::{Arc, Mutex};
38use tracing::{debug, error, info, trace, warn};
39use xxhash_rust::xxh64::Xxh64;
40
41pub type BrainRegionIoRegistry = HashMap<String, (Vec<String>, Vec<String>)>;
43
44use crate::models::{BrainRegion, BrainRegionHierarchy, CorticalArea, CorticalAreaDimensions};
45use crate::types::{BduError, BduResult};
46use feagi_npu_neural::synapse::SYNAPSE_EDGE_ASSOCIATIVE_MEMORY;
47use feagi_npu_neural::types::NeuronId;
48use feagi_structures::genomic::cortical_area::{
49 CoreCorticalType, CorticalAreaType, CorticalID, CustomCorticalType,
50};
51use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
52
53use feagi_state_manager::StateManager;
56
57const DATA_HASH_SEED: u64 = 0;
58const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
60
61static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
66 Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct ConnectomeConfig {
71 pub max_neurons: usize,
73
74 pub max_synapses: usize,
76
77 pub backend: String,
79}
80
81impl Default for ConnectomeConfig {
82 fn default() -> Self {
83 Self {
84 max_neurons: 10_000_000,
85 max_synapses: 100_000_000,
86 backend: "cpu".to_string(),
87 }
88 }
89}
90
91pub struct ConnectomeManager {
113 cortical_areas: HashMap<CorticalID, CorticalArea>,
115
116 cortical_id_to_idx: HashMap<CorticalID, u32>,
118
119 cortical_idx_to_id: HashMap<u32, CorticalID>,
121
122 next_cortical_idx: u32,
124
125 brain_regions: BrainRegionHierarchy,
127
128 morphology_registry: feagi_evolutionary::MorphologyRegistry,
130
131 config: ConnectomeConfig,
133
134 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
140
141 #[cfg(feature = "plasticity")]
143 plasticity_executor:
144 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
145
146 cached_neuron_count: Arc<AtomicUsize>,
149
150 cached_synapse_count: Arc<AtomicUsize>,
153
154 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
157
158 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
161
162 initialized: bool,
164
165 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
167}
168
169type NeuronData = (
171 u32,
172 u32,
173 u32,
174 f32,
175 f32,
176 f32,
177 f32,
178 i32,
179 u16,
180 f32,
181 u16,
182 u16,
183 bool,
184);
185
186impl ConnectomeManager {
187 fn get_mapping_rules_for_destination<'a>(
188 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
189 dst_area_id: &CorticalID,
190 ) -> Option<&'a Vec<serde_json::Value>> {
191 if let Some(rules) = mapping_dst
192 .get(&dst_area_id.as_base_64())
193 .and_then(|value| value.as_array())
194 {
195 return Some(rules);
196 }
197
198 for (raw_dst_key, rules_value) in mapping_dst {
201 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
202 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
203 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
204 continue;
205 }
206 if let Some(rules) = rules_value.as_array() {
207 return Some(rules);
208 }
209 }
210
211 None
212 }
213
214 fn new() -> Self {
216 Self {
217 cortical_areas: HashMap::new(),
218 cortical_id_to_idx: HashMap::new(),
219 cortical_idx_to_id: HashMap::new(),
220 next_cortical_idx: 7, brain_regions: BrainRegionHierarchy::new(),
223 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
224 config: ConnectomeConfig::default(),
225 npu: None,
226 #[cfg(feature = "plasticity")]
227 plasticity_executor: None,
228 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
229 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
230 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
231 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
232 initialized: false,
233 last_fatigue_calculation: Arc::new(Mutex::new(
234 std::time::Instant::now() - std::time::Duration::from_secs(10),
235 )), }
237 }
238
239 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
256 Arc::clone(&*INSTANCE)
259 }
260
261 pub fn new_for_testing() -> Self {
289 Self {
290 cortical_areas: HashMap::new(),
291 cortical_id_to_idx: HashMap::new(),
292 cortical_idx_to_id: HashMap::new(),
293 next_cortical_idx: 0,
294 brain_regions: BrainRegionHierarchy::new(),
295 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
296 config: ConnectomeConfig::default(),
297 npu: None,
298 #[cfg(feature = "plasticity")]
299 plasticity_executor: None,
300 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
301 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
302 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
303 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
304 initialized: false,
305 last_fatigue_calculation: Arc::new(Mutex::new(
306 std::time::Instant::now() - std::time::Duration::from_secs(10),
307 )),
308 }
309 }
310
311 pub fn new_for_testing_with_npu(
327 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
328 ) -> Self {
329 Self {
330 cortical_areas: HashMap::new(),
331 cortical_id_to_idx: HashMap::new(),
332 cortical_idx_to_id: HashMap::new(),
333 next_cortical_idx: 7,
334 brain_regions: BrainRegionHierarchy::new(),
335 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
336 config: ConnectomeConfig::default(),
337 npu: Some(npu),
338 #[cfg(feature = "plasticity")]
339 plasticity_executor: None,
340 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
341 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
342 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
343 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
344 initialized: false,
345 last_fatigue_calculation: Arc::new(Mutex::new(
346 std::time::Instant::now() - std::time::Duration::from_secs(10),
347 )),
348 }
349 }
350
351 pub fn setup_core_morphologies_for_testing(&mut self) {
361 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
362 }
363
364 #[cfg(test)]
373 pub fn reset_for_testing() {
374 let mut instance = INSTANCE.write();
375 *instance = Self::new();
376 }
377
378 fn update_state_hashes(
384 &self,
385 brain_regions: Option<u64>,
386 cortical_areas: Option<u64>,
387 brain_geometry: Option<u64>,
388 morphologies: Option<u64>,
389 cortical_mappings: Option<u64>,
390 ) {
391 let state_manager = StateManager::instance();
392 let state_manager = state_manager.read();
393 if let Some(value) = brain_regions {
394 state_manager.set_brain_regions_hash(value);
395 }
396 if let Some(value) = cortical_areas {
397 state_manager.set_cortical_areas_hash(value);
398 }
399 if let Some(value) = brain_geometry {
400 state_manager.set_brain_geometry_hash(value);
401 }
402 if let Some(value) = morphologies {
403 state_manager.set_morphologies_hash(value);
404 }
405 if let Some(value) = cortical_mappings {
406 state_manager.set_cortical_mappings_hash(value);
407 }
408 }
409
410 fn refresh_brain_regions_hash(&self) {
412 let hash = self.compute_brain_regions_hash();
413 self.update_state_hashes(Some(hash), None, None, None, None);
414 }
415
416 #[allow(dead_code)]
417 fn refresh_cortical_areas_hash(&self) {
419 let hash = self.compute_cortical_areas_hash();
420 self.update_state_hashes(None, Some(hash), None, None, None);
421 }
422
423 #[allow(dead_code)]
424 fn refresh_brain_geometry_hash(&self) {
426 let hash = self.compute_brain_geometry_hash();
427 self.update_state_hashes(None, None, Some(hash), None, None);
428 }
429
430 fn refresh_morphologies_hash(&self) {
432 let hash = self.compute_morphologies_hash();
433 self.update_state_hashes(None, None, None, Some(hash), None);
434 }
435
436 fn refresh_cortical_mappings_hash(&self) {
438 let hash = self.compute_cortical_mappings_hash();
439 self.update_state_hashes(None, None, None, None, Some(hash));
440 }
441
442 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
444 let cortical_hash = if properties_changed {
445 Some(self.compute_cortical_areas_hash())
446 } else {
447 None
448 };
449 let geometry_hash = if geometry_changed {
450 Some(self.compute_brain_geometry_hash())
451 } else {
452 None
453 };
454 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
455 }
456
457 fn compute_brain_regions_hash(&self) -> u64 {
459 let mut hasher = Xxh64::new(DATA_HASH_SEED);
460 let mut region_ids: Vec<String> = self
461 .brain_regions
462 .get_all_region_ids()
463 .into_iter()
464 .cloned()
465 .collect();
466 region_ids.sort();
467
468 for region_id in region_ids {
469 let Some(region) = self.brain_regions.get_region(®ion_id) else {
470 continue;
471 };
472 Self::hash_str(&mut hasher, ®ion_id);
473 Self::hash_str(&mut hasher, ®ion.name);
474 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
475 let parent_id = self.brain_regions.get_parent(®ion_id);
476 match parent_id {
477 Some(parent) => Self::hash_str(&mut hasher, parent),
478 None => Self::hash_str(&mut hasher, "null"),
479 }
480
481 let mut cortical_ids: Vec<String> = region
482 .cortical_areas
483 .iter()
484 .map(|id| id.as_base_64())
485 .collect();
486 cortical_ids.sort();
487 for cortical_id in cortical_ids {
488 Self::hash_str(&mut hasher, &cortical_id);
489 }
490
491 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
492 }
493
494 hasher.finish() & HASH_SAFE_MASK
495 }
496
497 fn compute_cortical_areas_hash(&self) -> u64 {
499 let mut hasher = Xxh64::new(DATA_HASH_SEED);
500 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
501 areas.sort_by_key(|area| area.cortical_id.as_base_64());
502
503 for area in areas {
504 let cortical_id = area.cortical_id.as_base_64();
505 Self::hash_str(&mut hasher, &cortical_id);
506 hasher.write_u32(area.cortical_idx);
507 Self::hash_str(&mut hasher, &area.name);
508 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
509
510 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
511 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
512 }
513
514 hasher.finish() & HASH_SAFE_MASK
515 }
516
517 fn compute_brain_geometry_hash(&self) -> u64 {
519 let mut hasher = Xxh64::new(DATA_HASH_SEED);
520 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
521 areas.sort_by_key(|area| area.cortical_id.as_base_64());
522
523 for area in areas {
524 let cortical_id = area.cortical_id.as_base_64();
525 Self::hash_str(&mut hasher, &cortical_id);
526
527 Self::hash_i32(&mut hasher, area.position.x);
528 Self::hash_i32(&mut hasher, area.position.y);
529 Self::hash_i32(&mut hasher, area.position.z);
530
531 Self::hash_u32(&mut hasher, area.dimensions.width);
532 Self::hash_u32(&mut hasher, area.dimensions.height);
533 Self::hash_u32(&mut hasher, area.dimensions.depth);
534
535 let coord_2d = area
536 .properties
537 .get("coordinate_2d")
538 .or_else(|| area.properties.get("coordinates_2d"));
539 match coord_2d {
540 Some(value) => Self::hash_json_value(&mut hasher, value),
541 None => Self::hash_str(&mut hasher, "null"),
542 }
543 }
544
545 hasher.finish() & HASH_SAFE_MASK
546 }
547
548 fn compute_morphologies_hash(&self) -> u64 {
550 let mut hasher = Xxh64::new(DATA_HASH_SEED);
551 let mut morphology_ids = self.morphology_registry.morphology_ids();
552 morphology_ids.sort();
553
554 for morphology_id in morphology_ids {
555 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
556 Self::hash_str(&mut hasher, &morphology_id);
557 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
558 Self::hash_str(&mut hasher, &morphology.class);
559 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
560 Self::hash_json_value(&mut hasher, &value);
561 }
562 }
563 }
564
565 hasher.finish() & HASH_SAFE_MASK
566 }
567
568 fn compute_cortical_mappings_hash(&self) -> u64 {
570 let mut hasher = Xxh64::new(DATA_HASH_SEED);
571 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
572 areas.sort_by_key(|area| area.cortical_id.as_base_64());
573
574 for area in areas {
575 let cortical_id = area.cortical_id.as_base_64();
576 Self::hash_str(&mut hasher, &cortical_id);
577 if let Some(serde_json::Value::Object(map)) =
578 area.properties.get("cortical_mapping_dst")
579 {
580 let mut dest_ids: Vec<&String> = map.keys().collect();
581 dest_ids.sort();
582 for dest_id in dest_ids {
583 Self::hash_str(&mut hasher, dest_id);
584 if let Some(value) = map.get(dest_id) {
585 Self::hash_json_value(&mut hasher, value);
586 }
587 }
588 } else {
589 Self::hash_str(&mut hasher, "null");
590 }
591 }
592
593 hasher.finish() & HASH_SAFE_MASK
594 }
595
596 fn hash_str(hasher: &mut Xxh64, value: &str) {
598 hasher.write(value.as_bytes());
599 hasher.write_u8(0);
600 }
601
602 fn hash_i32(hasher: &mut Xxh64, value: i32) {
604 hasher.write(&value.to_le_bytes());
605 }
606
607 fn hash_u32(hasher: &mut Xxh64, value: u32) {
609 hasher.write(&value.to_le_bytes());
610 }
611
612 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
614 match value {
615 serde_json::Value::Null => {
616 hasher.write_u8(0);
617 }
618 serde_json::Value::Bool(val) => {
619 hasher.write_u8(1);
620 hasher.write_u8(*val as u8);
621 }
622 serde_json::Value::Number(num) => {
623 hasher.write_u8(2);
624 Self::hash_str(hasher, &num.to_string());
625 }
626 serde_json::Value::String(val) => {
627 hasher.write_u8(3);
628 Self::hash_str(hasher, val);
629 }
630 serde_json::Value::Array(items) => {
631 hasher.write_u8(4);
632 for item in items {
633 Self::hash_json_value(hasher, item);
634 }
635 }
636 serde_json::Value::Object(map) => {
637 hasher.write_u8(5);
638 let mut keys: Vec<&String> = map.keys().collect();
639 keys.sort();
640 for key in keys {
641 Self::hash_str(hasher, key);
642 if let Some(val) = map.get(key) {
643 Self::hash_json_value(hasher, val);
644 }
645 }
646 }
647 }
648 }
649
650 fn hash_properties_filtered(
652 hasher: &mut Xxh64,
653 properties: &HashMap<String, serde_json::Value>,
654 excluded_keys: &[&str],
655 ) {
656 let mut keys: Vec<&String> = properties.keys().collect();
657 keys.sort();
658 for key in keys {
659 if excluded_keys.contains(&key.as_str()) {
660 continue;
661 }
662 Self::hash_str(hasher, key);
663 if let Some(value) = properties.get(key) {
664 Self::hash_json_value(hasher, value);
665 }
666 }
667 }
668
669 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
690 if self.cortical_areas.contains_key(&area.cortical_id) {
692 return Err(BduError::InvalidArea(format!(
693 "Cortical area {} already exists",
694 area.cortical_id
695 )));
696 }
697
698 use feagi_structures::genomic::cortical_area::CoreCorticalType;
701
702 let death_id = CoreCorticalType::Death.to_cortical_id();
703 let power_id = CoreCorticalType::Power.to_cortical_id();
704 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
705 let pain_id = CoreCorticalType::Pain.to_cortical_id();
706 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
707 let fear_id = CoreCorticalType::Fear.to_cortical_id();
708 let hope_id = CoreCorticalType::Hope.to_cortical_id();
709
710 let is_death_area = area.cortical_id == death_id;
711 let is_power_area = area.cortical_id == power_id;
712 let is_fatigue_area = area.cortical_id == fatigue_id;
713 let is_pain_area = area.cortical_id == pain_id;
714 let is_pleasure_area = area.cortical_id == pleasure_id;
715 let is_fear_area = area.cortical_id == fear_id;
716 let is_hope_area = area.cortical_id == hope_id;
717
718 if is_death_area {
719 trace!(
720 target: "feagi-bdu",
721 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
722 area.cortical_id
723 );
724 area.cortical_idx = 0;
725 } else if is_power_area {
726 trace!(
727 target: "feagi-bdu",
728 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
729 area.cortical_id
730 );
731 area.cortical_idx = 1;
732 } else if is_fatigue_area {
733 trace!(
734 target: "feagi-bdu",
735 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
736 area.cortical_id
737 );
738 area.cortical_idx = 2;
739 } else if is_pain_area {
740 trace!(
741 target: "feagi-bdu",
742 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
743 area.cortical_id
744 );
745 area.cortical_idx = 3;
746 } else if is_pleasure_area {
747 trace!(
748 target: "feagi-bdu",
749 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
750 area.cortical_id
751 );
752 area.cortical_idx = 4;
753 } else if is_fear_area {
754 trace!(
755 target: "feagi-bdu",
756 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
757 area.cortical_id
758 );
759 area.cortical_idx = 5;
760 } else if is_hope_area {
761 trace!(
762 target: "feagi-bdu",
763 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
764 area.cortical_id
765 );
766 area.cortical_idx = 6;
767 } else {
768 if area.cortical_idx == 0 {
770 area.cortical_idx = self.next_cortical_idx;
771 self.next_cortical_idx += 1;
772 trace!(
773 target: "feagi-bdu",
774 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
775 area.cortical_idx,
776 area.cortical_id.as_base_64()
777 );
778 } else {
779 if area.cortical_idx <= 6 {
781 warn!(
782 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
783 area.cortical_id, area.cortical_idx);
784 area.cortical_idx = self.next_cortical_idx;
785 self.next_cortical_idx += 1;
786 info!(
787 " Reassigned '{}' to cortical_idx={}",
788 area.cortical_id, area.cortical_idx
789 );
790 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
791 return Err(BduError::InvalidArea(format!(
792 "Cortical index {} is already in use",
793 area.cortical_idx
794 )));
795 }
796
797 if area.cortical_idx >= self.next_cortical_idx {
799 self.next_cortical_idx = area.cortical_idx + 1;
800 }
801 }
802 }
803
804 let cortical_id = area.cortical_id;
805 let cortical_idx = area.cortical_idx;
806
807 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
809 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
810
811 area.properties
813 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
814
815 let parent_region_id = area
824 .properties
825 .get("parent_region_id")
826 .and_then(|v| v.as_str())
827 .map(|s| s.to_string());
828
829 self.cortical_areas.insert(cortical_id, area);
831
832 if let Some(region_id) = parent_region_id {
834 let region = self
835 .brain_regions
836 .get_region_mut(®ion_id)
837 .ok_or_else(|| {
838 BduError::InvalidArea(format!(
839 "Unknown parent_region_id '{}' for cortical area {}",
840 region_id,
841 cortical_id.as_base_64()
842 ))
843 })?;
844 region.add_area(cortical_id);
845 }
846
847 {
850 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
851 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
852 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
853 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
854 }
855 let state_manager = StateManager::instance();
857 let state_manager = state_manager.read();
858 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
859
860 if let Some(ref npu) = self.npu {
864 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
865 if let Ok(mut npu_lock) = npu.lock() {
866 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
867 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
868 trace!(
869 target: "feagi-bdu",
870 "Registered cortical area idx={} -> '{}' in NPU",
871 cortical_idx,
872 cortical_id.as_base_64()
873 );
874 }
875 }
876
877 self.sync_cortical_area_flags_to_npu()?;
879
880 self.initialized = true;
881
882 self.refresh_cortical_area_hashes(true, true);
883 self.refresh_brain_regions_hash();
884
885 Ok(cortical_idx)
886 }
887
888 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
903 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
904 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
905 })?;
906
907 self.cortical_id_to_idx.remove(cortical_id);
909 self.cortical_idx_to_id.remove(&area.cortical_idx);
910
911 self.refresh_cortical_area_hashes(true, true);
912 Ok(())
913 }
914
915 pub fn rename_cortical_area_id(
919 &mut self,
920 old_id: &CorticalID,
921 new_id: CorticalID,
922 new_cortical_type: CorticalAreaType,
923 ) -> BduResult<()> {
924 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
925 }
926
927 pub fn rename_cortical_area_id_with_options(
929 &mut self,
930 old_id: &CorticalID,
931 new_id: CorticalID,
932 new_cortical_type: CorticalAreaType,
933 update_npu_registry: bool,
934 ) -> BduResult<()> {
935 if !self.cortical_areas.contains_key(old_id) {
936 return Err(BduError::InvalidArea(format!(
937 "Cortical area {} does not exist",
938 old_id
939 )));
940 }
941 if self.cortical_areas.contains_key(&new_id) {
942 return Err(BduError::InvalidArea(format!(
943 "Cortical area {} already exists",
944 new_id
945 )));
946 }
947
948 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
949 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
950 })?;
951 let cortical_idx = area.cortical_idx;
952 area.cortical_id = new_id;
953 area.cortical_type = new_cortical_type;
954
955 self.cortical_areas.insert(new_id, area);
956 self.cortical_id_to_idx.remove(old_id);
957 self.cortical_id_to_idx.insert(new_id, cortical_idx);
958 self.cortical_idx_to_id.insert(cortical_idx, new_id);
959
960 {
962 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
963 if let Some(value) = neuron_cache.remove(old_id) {
964 neuron_cache.insert(new_id, value);
965 }
966 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
967 if let Some(value) = synapse_cache.remove(old_id) {
968 synapse_cache.insert(new_id, value);
969 }
970 }
971
972 self.brain_regions.rename_cortical_area_id(old_id, new_id);
974
975 let old_id_str = old_id.as_base_64();
977 let new_id_str = new_id.as_base_64();
978 for area in self.cortical_areas.values_mut() {
979 if let Some(mapping) = area
980 .properties
981 .get_mut("cortical_mapping_dst")
982 .and_then(|v| v.as_object_mut())
983 {
984 if let Some(value) = mapping.remove(&old_id_str) {
985 mapping.insert(new_id_str.clone(), value);
986 }
987 }
988 }
989
990 if update_npu_registry {
992 if let Some(ref npu) = self.npu {
993 if let Ok(mut npu_lock) = npu.lock() {
994 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
995 }
996 }
997 }
998
999 self.refresh_cortical_area_hashes(true, true);
1000 self.refresh_brain_regions_hash();
1001 self.refresh_cortical_mappings_hash();
1002
1003 Ok(())
1004 }
1005
1006 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1008 self.cortical_areas.get(cortical_id)
1009 }
1010
1011 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1013 self.cortical_areas.get_mut(cortical_id)
1014 }
1015
1016 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1018 self.cortical_id_to_idx.get(cortical_id).copied()
1019 }
1020
1021 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1033 self.brain_regions.find_region_containing_area(cortical_id)
1034 }
1035
1036 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1039 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1040 return false;
1041 };
1042 let Some(src_area) = self.cortical_areas.get(area) else {
1043 return false;
1044 };
1045 let Some(dst_obj) = src_area
1046 .properties
1047 .get("cortical_mapping_dst")
1048 .and_then(|v| v.as_object())
1049 else {
1050 return false;
1051 };
1052 for dst_key in dst_obj.keys() {
1053 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1054 continue;
1055 };
1056 match self.brain_regions.find_region_containing_area(&dst_id) {
1057 None => return true,
1058 Some(rid) if rid != my_region => return true,
1059 _ => {}
1060 }
1061 }
1062 false
1063 }
1064
1065 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1067 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1068 return false;
1069 };
1070 let my_b64 = area.as_base_64();
1071 for (src_id, src_area) in &self.cortical_areas {
1072 if src_id == area {
1073 continue;
1074 }
1075 let Some(dst_map) = src_area
1076 .properties
1077 .get("cortical_mapping_dst")
1078 .and_then(|v| v.as_object())
1079 else {
1080 continue;
1081 };
1082 if !dst_map.contains_key(&my_b64) {
1083 continue;
1084 }
1085 match self.brain_regions.find_region_containing_area(src_id) {
1086 None => return true,
1087 Some(rid) if rid != my_region => return true,
1088 _ => {}
1089 }
1090 }
1091 false
1092 }
1093
1094 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1105 use std::collections::HashSet;
1106
1107 let region_ids: Vec<String> = self
1108 .brain_regions
1109 .get_all_region_ids()
1110 .into_iter()
1111 .cloned()
1112 .collect();
1113
1114 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1115 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1116
1117 for rid in ®ion_ids {
1119 inputs_by_region.insert(rid.clone(), HashSet::new());
1120 outputs_by_region.insert(rid.clone(), HashSet::new());
1121 }
1122
1123 for (src_id, src_area) in &self.cortical_areas {
1124 let Some(dstmap) = src_area
1125 .properties
1126 .get("cortical_mapping_dst")
1127 .and_then(|v| v.as_object())
1128 else {
1129 continue;
1130 };
1131
1132 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1133 warn!(
1134 target: "feagi-bdu",
1135 "Skipping region IO for source area {} (not in any region)",
1136 src_id.as_base_64()
1137 );
1138 continue;
1139 };
1140
1141 for dst_id_str in dstmap.keys() {
1142 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1143 BduError::InvalidArea(format!(
1144 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1145 dst_id_str,
1146 src_id.as_base_64(),
1147 e
1148 ))
1149 })?;
1150
1151 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1152 else {
1153 warn!(
1154 target: "feagi-bdu",
1155 "Skipping region IO for destination area {} (not in any region)",
1156 dst_id.as_base_64()
1157 );
1158 continue;
1159 };
1160
1161 if src_region_id == dst_region_id {
1162 continue;
1163 }
1164
1165 outputs_by_region
1166 .entry(src_region_id.clone())
1167 .or_default()
1168 .insert(src_id.as_base_64());
1169 inputs_by_region
1170 .entry(dst_region_id.clone())
1171 .or_default()
1172 .insert(dst_id.as_base_64());
1173 }
1174 }
1175
1176 for rid in ®ion_ids {
1178 let Some(region) = self.brain_regions.get_region(rid) else {
1179 continue;
1180 };
1181 let in_ids = crate::region_io_designation::parse_designated_id_list(
1182 region
1183 .properties
1184 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1185 )?;
1186 let out_ids = crate::region_io_designation::parse_designated_id_list(
1187 region
1188 .properties
1189 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1190 )?;
1191 for id in in_ids {
1192 inputs_by_region
1193 .entry(rid.clone())
1194 .or_default()
1195 .insert(id.as_base_64());
1196 }
1197 for id in out_ids {
1198 outputs_by_region
1199 .entry(rid.clone())
1200 .or_default()
1201 .insert(id.as_base_64());
1202 }
1203 }
1204
1205 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1206 for rid in region_ids {
1207 let mut inputs: Vec<String> = inputs_by_region
1208 .remove(&rid)
1209 .unwrap_or_default()
1210 .into_iter()
1211 .collect();
1212 let mut outputs: Vec<String> = outputs_by_region
1213 .remove(&rid)
1214 .unwrap_or_default()
1215 .into_iter()
1216 .collect();
1217
1218 inputs.sort();
1219 outputs.sort();
1220
1221 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1222 BduError::InvalidArea(format!(
1223 "Unable to recompute region IO: region '{}' not found in hierarchy",
1224 rid
1225 ))
1226 })?;
1227
1228 if inputs.is_empty() {
1229 region.properties.remove("inputs");
1230 } else {
1231 region
1232 .properties
1233 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1234 }
1235
1236 if outputs.is_empty() {
1237 region.properties.remove("outputs");
1238 } else {
1239 region
1240 .properties
1241 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1242 }
1243
1244 computed.insert(rid, (inputs, outputs));
1245 }
1246
1247 self.refresh_brain_regions_hash();
1248
1249 Ok(computed)
1250 }
1251
1252 pub fn get_root_region_id(&self) -> Option<String> {
1258 self.brain_regions.get_root_region_id()
1259 }
1260
1261 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1263 self.cortical_idx_to_id.get(&cortical_idx)
1264 }
1265
1266 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1269 self.cortical_idx_to_id
1270 .iter()
1271 .map(|(idx, id)| (*idx, id.as_base_64()))
1272 .collect()
1273 }
1274
1275 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1280 let mut granularities = ahash::AHashMap::new();
1281 for (cortical_id, area) in &self.cortical_areas {
1282 let cortical_idx = self
1283 .cortical_id_to_idx
1284 .get(cortical_id)
1285 .copied()
1286 .unwrap_or(0);
1287
1288 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1291 if let Some(arr) = granularity_json.as_array() {
1292 if arr.len() == 3 {
1293 let x_opt = arr[0]
1294 .as_u64()
1295 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1296 let y_opt = arr[1]
1297 .as_u64()
1298 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1299 let z_opt = arr[2]
1300 .as_u64()
1301 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1302
1303 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1304 let granularity = (x as u32, y as u32, z as u32);
1305 if granularity != (1, 1, 1) {
1307 granularities.insert(cortical_idx, granularity);
1308 }
1309 }
1310 }
1311 }
1312 }
1313 }
1314 granularities
1315 }
1316
1317 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1319 self.cortical_areas.keys().collect()
1320 }
1321
1322 pub fn get_cortical_area_count(&self) -> usize {
1324 self.cortical_areas.len()
1325 }
1326
1327 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1341 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1342 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1343 if let Some(upstream_array) = upstream_prop.as_array() {
1344 return upstream_array
1345 .iter()
1346 .filter_map(|v| v.as_u64().map(|n| n as u32))
1347 .collect();
1348 }
1349 }
1350
1351 warn!(target: "feagi-bdu",
1353 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1354 target_cortical_id.as_base_64()
1355 );
1356 }
1357
1358 Vec::new()
1359 }
1360
1361 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1363 upstream
1364 .iter()
1365 .filter_map(|idx| {
1366 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1367 let area = self.cortical_areas.get(cortical_id)?;
1368 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1369 None
1370 } else {
1371 Some(*idx)
1372 }
1373 })
1374 .collect()
1375 }
1376
1377 pub fn refresh_upstream_cortical_areas_from_mappings(
1381 &mut self,
1382 target_cortical_id: &CorticalID,
1383 ) -> Vec<u32> {
1384 use std::collections::HashSet;
1385 let target_id_str = target_cortical_id.as_base_64();
1386 let mut upstream_idxs = HashSet::new();
1387 for (src_id, src_area) in &self.cortical_areas {
1388 if src_id == target_cortical_id {
1389 continue;
1390 }
1391 if let Some(mapping) = src_area
1392 .properties
1393 .get("cortical_mapping_dst")
1394 .and_then(|v| v.as_object())
1395 {
1396 if mapping.contains_key(&target_id_str) {
1397 upstream_idxs.insert(src_area.cortical_idx);
1398 }
1399 }
1400 }
1401
1402 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1403 upstream_list.sort_unstable();
1404
1405 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1406 target_area.properties.insert(
1407 "upstream_cortical_areas".to_string(),
1408 serde_json::json!(upstream_list),
1409 );
1410 }
1411
1412 self.get_upstream_cortical_areas(target_cortical_id)
1413 }
1414
1415 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1425 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1426 let upstream_array = area
1427 .properties
1428 .entry("upstream_cortical_areas".to_string())
1429 .or_insert_with(|| serde_json::json!([]));
1430
1431 if let Some(arr) = upstream_array.as_array_mut() {
1432 let src_value = serde_json::json!(src_cortical_idx);
1433 if !arr.contains(&src_value) {
1434 arr.push(src_value);
1435 info!(target: "feagi-bdu",
1436 "✓ Added upstream area idx={} to cortical area '{}'",
1437 src_cortical_idx, target_cortical_id.as_base_64()
1438 );
1439 }
1440 }
1441 }
1442 }
1443
1444 pub fn get_memory_twin_for_upstream_idx(
1446 &self,
1447 memory_area_idx: u32,
1448 upstream_idx: u32,
1449 ) -> Option<CorticalID> {
1450 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1451 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1452 let area = self.cortical_areas.get(memory_id)?;
1453 let mapping = area
1454 .properties
1455 .get("memory_twin_areas")
1456 .and_then(|v| v.as_object())?;
1457 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1458 CorticalID::try_from_base_64(twin_b64).ok()
1459 }
1460
1461 pub fn ensure_memory_twin_area(
1463 &mut self,
1464 memory_area_id: &CorticalID,
1465 upstream_area_id: &CorticalID,
1466 ) -> BduResult<CorticalID> {
1467 use crate::models::CorticalAreaExt;
1468
1469 let register_replay_mapping = |manager: &mut ConnectomeManager,
1470 twin_id: &CorticalID|
1471 -> BduResult<()> {
1472 let Some(npu) = manager.npu.as_ref() else {
1473 return Ok(());
1474 };
1475 let memory_area_idx =
1476 *manager
1477 .cortical_id_to_idx
1478 .get(memory_area_id)
1479 .ok_or_else(|| {
1480 BduError::InvalidArea(format!(
1481 "Memory area idx missing for {}",
1482 memory_area_id.as_base_64()
1483 ))
1484 })?;
1485 let upstream_area_idx = *manager
1486 .cortical_id_to_idx
1487 .get(upstream_area_id)
1488 .ok_or_else(|| {
1489 BduError::InvalidArea(format!(
1490 "Upstream area idx missing for {}",
1491 upstream_area_id.as_base_64()
1492 ))
1493 })?;
1494 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1495 BduError::InvalidArea(format!(
1496 "Twin area idx missing for {}",
1497 twin_id.as_base_64()
1498 ))
1499 })?;
1500 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1501 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1502 })?;
1503 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1504 if let Ok(mut npu_lock) = npu.lock() {
1505 npu_lock.register_memory_twin_mapping(
1506 memory_area_idx,
1507 upstream_area_idx,
1508 twin_area_idx,
1509 potential,
1510 );
1511 }
1512 Ok(())
1513 };
1514
1515 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1516 BduError::InvalidArea(format!(
1517 "Memory area {} not found",
1518 memory_area_id.as_base_64()
1519 ))
1520 })?;
1521 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1522 BduError::InvalidArea(format!(
1523 "Upstream area {} not found",
1524 upstream_area_id.as_base_64()
1525 ))
1526 })?;
1527
1528 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1529 return Err(BduError::InvalidArea(format!(
1530 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1531 upstream_area_id.as_base_64()
1532 )));
1533 }
1534
1535 if let Some(existing) = memory_area
1536 .properties
1537 .get("memory_twin_areas")
1538 .and_then(|v| v.as_object())
1539 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1540 .and_then(|v| v.as_str())
1541 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1542 {
1543 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1544 register_replay_mapping(self, &existing)?;
1545 self.refresh_cortical_mappings_hash();
1546 return Ok(existing);
1547 }
1548
1549 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1550 if self.cortical_areas.contains_key(&twin_id) {
1551 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1552 let expected_source = upstream_area_id.as_base_64();
1553 let expected_target = memory_area_id.as_base_64();
1554 let existing_source = existing
1555 .properties
1556 .get("memory_twin_of")
1557 .and_then(|v| v.as_str());
1558 let existing_target = existing
1559 .properties
1560 .get("memory_twin_for")
1561 .and_then(|v| v.as_str());
1562 if existing_source != Some(expected_source.as_str())
1563 || existing_target != Some(expected_target.as_str())
1564 {
1565 warn!(
1566 target: "feagi-bdu",
1567 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1568 upstream_area_id.as_base_64(),
1569 memory_area_id.as_base_64()
1570 );
1571 existing.properties.insert(
1572 "memory_twin_of".to_string(),
1573 serde_json::json!(expected_source),
1574 );
1575 existing.properties.insert(
1576 "memory_twin_for".to_string(),
1577 serde_json::json!(expected_target),
1578 );
1579 }
1580 }
1581 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1582 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1583 register_replay_mapping(self, &twin_id)?;
1584 self.refresh_cortical_mappings_hash();
1585 return Ok(twin_id);
1586 }
1587
1588 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1589 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1590 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1591 let mut twin_area = CorticalArea::new(
1592 twin_id,
1593 0,
1594 twin_name,
1595 upstream_area.dimensions,
1596 twin_position,
1597 twin_type,
1598 )?;
1599 twin_area.properties = self.build_memory_twin_properties(
1600 memory_area,
1601 upstream_area,
1602 memory_area_id,
1603 upstream_area_id,
1604 );
1605
1606 let _twin_idx = self.add_cortical_area(twin_area)?;
1607 let _ = self.create_neurons_for_area(&twin_id);
1608
1609 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1610 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1611 register_replay_mapping(self, &twin_id)?;
1612 self.refresh_cortical_mappings_hash();
1613 Ok(twin_id)
1614 }
1615
1616 fn build_memory_twin_position(
1617 &self,
1618 memory_area: &CorticalArea,
1619 upstream_area: &CorticalArea,
1620 ) -> GenomeCoordinate3D {
1621 let memory_parent = memory_area
1622 .properties
1623 .get("parent_region_id")
1624 .and_then(|v| v.as_str());
1625 let upstream_parent = upstream_area
1626 .properties
1627 .get("parent_region_id")
1628 .and_then(|v| v.as_str());
1629 let same_region = memory_parent.is_some() && memory_parent == upstream_parent;
1630
1631 if !same_region {
1632 return GenomeCoordinate3D::new(
1633 memory_area.position.x + 20,
1634 memory_area.position.y,
1635 memory_area.position.z,
1636 );
1637 }
1638
1639 let width = upstream_area.dimensions.width as f32;
1640 let margin = (width * 0.25).ceil() as i32;
1641 let offset = upstream_area.dimensions.width as i32 + margin;
1642 GenomeCoordinate3D::new(
1643 upstream_area.position.x + offset,
1644 upstream_area.position.y,
1645 upstream_area.position.z,
1646 )
1647 }
1648
1649 fn build_memory_twin_id(
1650 &self,
1651 memory_area_id: &CorticalID,
1652 upstream_area_id: &CorticalID,
1653 ) -> BduResult<CorticalID> {
1654 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1655 hasher.write(memory_area_id.as_base_64().as_bytes());
1656 hasher.write(upstream_area_id.as_base_64().as_bytes());
1657 hasher.write(b"memory_twin");
1658 let hash = hasher.finish();
1659 let mut bytes = hash.to_be_bytes();
1660 bytes[0] = b'c';
1661 CorticalID::try_from_bytes(&bytes)
1662 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1663 }
1664
1665 fn build_memory_twin_properties(
1666 &self,
1667 memory_area: &CorticalArea,
1668 upstream_area: &CorticalArea,
1669 memory_area_id: &CorticalID,
1670 upstream_area_id: &CorticalID,
1671 ) -> HashMap<String, serde_json::Value> {
1672 let mut props = upstream_area.properties.clone();
1673 props.remove("cortical_mapping_dst");
1674 props.remove("upstream_cortical_areas");
1675 props.remove("parent_region_id");
1676 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1677 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1678 props.insert(
1679 "memory_twin_of".to_string(),
1680 serde_json::json!(upstream_area_id.as_base_64()),
1681 );
1682 props.insert(
1683 "memory_twin_for".to_string(),
1684 serde_json::json!(memory_area_id.as_base_64()),
1685 );
1686 if let Some(parent_region_id) = memory_area
1687 .properties
1688 .get("parent_region_id")
1689 .and_then(|v| v.as_str())
1690 {
1691 props.insert(
1692 "parent_region_id".to_string(),
1693 serde_json::json!(parent_region_id),
1694 );
1695 }
1696 props
1697 }
1698
1699 fn set_memory_twin_mapping(
1700 &mut self,
1701 memory_area_id: &CorticalID,
1702 upstream_area_id: &CorticalID,
1703 twin_id: &CorticalID,
1704 ) {
1705 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1706 let mapping = memory_area
1707 .properties
1708 .entry("memory_twin_areas".to_string())
1709 .or_insert_with(|| serde_json::json!({}));
1710 if let Some(map) = mapping.as_object_mut() {
1711 map.insert(
1712 upstream_area_id.as_base_64(),
1713 serde_json::json!(twin_id.as_base_64()),
1714 );
1715 }
1716 }
1717 }
1718
1719 fn ensure_memory_replay_mapping(
1720 &mut self,
1721 memory_area_id: &CorticalID,
1722 twin_id: &CorticalID,
1723 ) -> BduResult<()> {
1724 if !self.morphology_registry.contains("memory_replay") {
1725 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1726 }
1727 self.refresh_morphologies_hash();
1728 let mapping_data = vec![serde_json::json!({
1729 "morphology_id": "memory_replay",
1730 "morphology_scalar": [1, 1, 1],
1731 "postSynapticCurrent_multiplier": 1,
1732 "plasticity_flag": false,
1733 "plasticity_constant": 0,
1734 "ltp_multiplier": 0,
1735 "ltd_multiplier": 0,
1736 "plasticity_window": 0,
1737 })];
1738 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1739 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1740 self.refresh_cortical_area_hashes(true, false);
1742 Ok(())
1743 }
1744
1745 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1755 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1756 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1757 if let Some(arr) = upstream_prop.as_array_mut() {
1758 let src_value = serde_json::json!(src_cortical_idx);
1759 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1760 arr.remove(pos);
1761 debug!(target: "feagi-bdu",
1762 "Removed upstream area idx={} from cortical area '{}'",
1763 src_cortical_idx, target_cortical_id.as_base_64()
1764 );
1765 }
1766 }
1767 }
1768 }
1769 }
1770
1771 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1773 self.cortical_areas.contains_key(cortical_id)
1774 }
1775
1776 pub fn is_initialized(&self) -> bool {
1778 self.initialized && !self.cortical_areas.is_empty()
1779 }
1780
1781 pub fn add_brain_region(
1787 &mut self,
1788 region: BrainRegion,
1789 parent_id: Option<String>,
1790 ) -> BduResult<()> {
1791 self.brain_regions.add_region(region, parent_id)?;
1792 self.refresh_brain_regions_hash();
1793 Ok(())
1794 }
1795
1796 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1798 self.brain_regions.remove_region(region_id)?;
1799 self.refresh_brain_regions_hash();
1800 Ok(())
1801 }
1802
1803 pub fn change_brain_region_parent(
1805 &mut self,
1806 region_id: &str,
1807 new_parent_id: &str,
1808 ) -> BduResult<()> {
1809 self.brain_regions.change_parent(region_id, new_parent_id)?;
1810 self.refresh_brain_regions_hash();
1811 Ok(())
1812 }
1813
1814 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
1816 self.brain_regions.get_region(region_id)
1817 }
1818
1819 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
1821 self.brain_regions.get_region_mut(region_id)
1822 }
1823
1824 pub fn get_brain_region_ids(&self) -> Vec<&String> {
1826 self.brain_regions.get_all_region_ids()
1827 }
1828
1829 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
1831 &self.brain_regions
1832 }
1833
1834 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
1840 &self.morphology_registry
1841 }
1842
1843 pub fn get_morphology_count(&self) -> usize {
1845 self.morphology_registry.count()
1846 }
1847
1848 pub fn upsert_morphology(
1853 &mut self,
1854 morphology_id: String,
1855 morphology: feagi_evolutionary::Morphology,
1856 ) {
1857 self.morphology_registry
1858 .add_morphology(morphology_id, morphology);
1859 self.refresh_morphologies_hash();
1860 }
1861
1862 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
1868 let removed = self.morphology_registry.remove_morphology(morphology_id);
1869 if removed {
1870 self.refresh_morphologies_hash();
1871 }
1872 removed
1873 }
1874
1875 pub fn update_cortical_mapping(
1893 &mut self,
1894 src_area_id: &CorticalID,
1895 dst_area_id: &CorticalID,
1896 mapping_data: Vec<serde_json::Value>,
1897 ) -> BduResult<()> {
1898 use tracing::info;
1899
1900 crate::region_io_designation::validate_cross_region_mapping_proposal(
1901 self,
1902 src_area_id,
1903 dst_area_id,
1904 &mapping_data,
1905 )?;
1906
1907 info!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
1908
1909 {
1910 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
1912 crate::types::BduError::InvalidArea(format!(
1913 "Source area not found: {}",
1914 src_area_id
1915 ))
1916 })?;
1917
1918 let cortical_mapping_dst =
1920 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
1921 existing.as_object_mut().ok_or_else(|| {
1922 crate::types::BduError::InvalidMorphology(
1923 "cortical_mapping_dst is not an object".to_string(),
1924 )
1925 })?
1926 } else {
1927 src_area
1929 .properties
1930 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
1931 src_area
1932 .properties
1933 .get_mut("cortical_mapping_dst")
1934 .unwrap()
1935 .as_object_mut()
1936 .unwrap()
1937 };
1938
1939 if mapping_data.is_empty() {
1941 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
1943 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
1944 } else {
1945 cortical_mapping_dst.insert(
1946 dst_area_id.as_base_64(),
1947 serde_json::Value::Array(mapping_data.clone()),
1948 );
1949 info!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
1950 src_area_id, dst_area_id, mapping_data.len());
1951 }
1952 }
1953
1954 self.refresh_cortical_mappings_hash();
1955
1956 Ok(())
1957 }
1958
1959 pub fn regenerate_synapses_for_mapping(
1972 &mut self,
1973 src_area_id: &CorticalID,
1974 dst_area_id: &CorticalID,
1975 ) -> BduResult<usize> {
1976 use tracing::info;
1977
1978 info!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
1979
1980 let mapping_rules_len = self
1981 .cortical_areas
1982 .get(src_area_id)
1983 .and_then(|area| area.properties.get("cortical_mapping_dst"))
1984 .and_then(|v| v.as_object())
1985 .and_then(|map| map.get(&dst_area_id.as_base_64()))
1986 .and_then(|v| v.as_array())
1987 .map(|arr| arr.len())
1988 .unwrap_or(0);
1989 tracing::debug!(
1990 target: "feagi-bdu",
1991 "Mapping rules for {} -> {}: {}",
1992 src_area_id,
1993 dst_area_id,
1994 mapping_rules_len
1995 );
1996
1997 let Some(npu_arc) = self.npu.clone() else {
1999 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
2000 return Ok(0);
2001 };
2002
2003 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2013 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
2014 })?;
2015 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2016 BduError::InvalidArea(format!(
2017 "No cortical idx for destination area {}",
2018 dst_area_id
2019 ))
2020 })?;
2021
2022 let mut pruned_synapse_count: usize = 0;
2025 use std::time::Instant;
2026 let start = Instant::now();
2027
2028 let (sources, targets) = {
2034 let lock_start = std::time::Instant::now();
2035 let npu = npu_arc.lock().unwrap();
2036 let lock_wait = lock_start.elapsed();
2037 tracing::debug!(
2038 target: "feagi-bdu",
2039 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
2040 lock_wait.as_secs_f64() * 1000.0,
2041 src_area_id,
2042 dst_area_id
2043 );
2044 let sources: Vec<NeuronId> = npu
2045 .get_neurons_in_cortical_area(src_idx)
2046 .into_iter()
2047 .map(NeuronId)
2048 .collect();
2049 let targets: Vec<NeuronId> = npu
2050 .get_neurons_in_cortical_area(dst_idx)
2051 .into_iter()
2052 .map(NeuronId)
2053 .collect();
2054 (sources, targets)
2055 };
2056
2057 tracing::debug!(
2058 target: "feagi-bdu",
2059 "Prune synapses: {} sources, {} targets",
2060 sources.len(),
2061 targets.len()
2062 );
2063
2064 if !sources.is_empty() && !targets.is_empty() {
2065 let remove_start = Instant::now();
2066 pruned_synapse_count = {
2067 let lock_start = std::time::Instant::now();
2068 let mut npu = npu_arc.lock().unwrap();
2069 let lock_wait = lock_start.elapsed();
2070 tracing::debug!(
2071 target: "feagi-bdu",
2072 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
2073 lock_wait.as_secs_f64() * 1000.0,
2074 src_area_id,
2075 dst_area_id
2076 );
2077 npu.remove_synapses_between(sources, targets)
2081 };
2082 let remove_time = remove_start.elapsed();
2083 let total_time = start.elapsed();
2084
2085 info!(
2086 target: "feagi-bdu",
2087 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2088 pruned_synapse_count,
2089 src_area_id,
2090 dst_area_id,
2091 total_time.as_millis(),
2092 remove_time.as_millis()
2093 );
2094
2095 if pruned_synapse_count > 0 {
2097 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2098 BduError::Internal(format!(
2099 "Pruned synapse count overflow (usize -> u32): {}",
2100 pruned_synapse_count
2101 ))
2102 })?;
2103 let state_manager = StateManager::instance();
2104 let state_manager = state_manager.read();
2105 let core_state = state_manager.get_core_state();
2106 core_state.subtract_synapse_count(pruned_u32);
2107 state_manager.subtract_cortical_area_outgoing_synapses(
2108 &src_area_id.as_base_64(),
2109 pruned_synapse_count,
2110 );
2111 state_manager.subtract_cortical_area_incoming_synapses(
2112 &dst_area_id.as_base_64(),
2113 pruned_synapse_count,
2114 );
2115
2116 {
2120 let mut cache = self.cached_synapse_counts_per_area.write();
2121 let entry = cache
2122 .entry(*src_area_id)
2123 .or_insert_with(|| AtomicUsize::new(0));
2124 let mut current = entry.load(Ordering::Relaxed);
2125 loop {
2126 let next = current.saturating_sub(pruned_synapse_count);
2127 match entry.compare_exchange(
2128 current,
2129 next,
2130 Ordering::Relaxed,
2131 Ordering::Relaxed,
2132 ) {
2133 Ok(_) => break,
2134 Err(v) => current = v,
2135 }
2136 }
2137 }
2138 }
2139 }
2140
2141 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2148 tracing::debug!(
2149 target: "feagi-bdu",
2150 "Synaptogenesis created {} synapses for {} -> {}",
2151 synapse_count,
2152 src_area_id,
2153 dst_area_id
2154 );
2155
2156 if synapse_count > 0 {
2159 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2160 BduError::Internal(format!(
2161 "Created synapse count overflow (usize -> u32): {}",
2162 synapse_count
2163 ))
2164 })?;
2165
2166 {
2168 let mut cache = self.cached_synapse_counts_per_area.write();
2169 cache
2170 .entry(*src_area_id)
2171 .or_insert_with(|| AtomicUsize::new(0))
2172 .fetch_add(synapse_count, Ordering::Relaxed);
2173 }
2174
2175 let state_manager = StateManager::instance();
2177 let state_manager = state_manager.read();
2178 let core_state = state_manager.get_core_state();
2179 core_state.add_synapse_count(created_u32);
2180 state_manager
2181 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2182 state_manager
2183 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2184 }
2185
2186 let src_idx_for_upstream = src_idx;
2189
2190 let has_mapping = self
2192 .cortical_areas
2193 .get(src_area_id)
2194 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2195 .and_then(|v| v.as_object())
2196 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2197 .is_some();
2198
2199 info!(target: "feagi-bdu",
2200 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2201 synapse_count,
2202 src_area_id.as_base_64(),
2203 dst_area_id.as_base_64(),
2204 has_mapping,
2205 if has_mapping { "" } else { "NOT " }
2206 );
2207
2208 if has_mapping {
2209 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2211
2212 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2213 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2214 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2215 warn!(
2216 target: "feagi-bdu",
2217 "Failed to ensure memory twin for {} -> {}: {}",
2218 src_area_id.as_base_64(),
2219 dst_area_id.as_base_64(),
2220 e
2221 );
2222 }
2223 }
2224 }
2225
2226 #[cfg(feature = "plasticity")]
2228 if let Some(ref executor) = self.plasticity_executor {
2229 use feagi_evolutionary::extract_memory_properties;
2230
2231 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2232 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2233 let upstream_areas = self.get_upstream_cortical_areas(dst_area_id);
2234 let upstream_non_memory =
2235 self.filter_non_memory_upstream_areas(&upstream_areas);
2236 debug!(
2237 target: "feagi-bdu",
2238 "Registering memory area idx={} id={} upstream={} depth={}",
2239 dst_area.cortical_idx,
2240 dst_area_id.as_base_64(),
2241 upstream_areas.len(),
2242 mem_props.temporal_depth
2243 );
2244
2245 if let Some(ref npu_arc) = self.npu {
2248 if let Ok(mut npu) = npu_arc.lock() {
2249 let existing_configs = npu.get_all_fire_ledger_configs();
2250 for &upstream_idx in &upstream_areas {
2251 let existing = existing_configs
2252 .iter()
2253 .find(|(idx, _)| *idx == upstream_idx)
2254 .map(|(_, w)| *w)
2255 .unwrap_or(0);
2256
2257 let desired = mem_props.temporal_depth as usize;
2258 let resolved = existing.max(desired);
2259 if resolved != existing {
2260 if let Err(e) =
2261 npu.configure_fire_ledger_window(upstream_idx, resolved)
2262 {
2263 warn!(
2264 target: "feagi-bdu",
2265 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2266 upstream_idx,
2267 resolved,
2268 e
2269 );
2270 }
2271 }
2272 }
2273 } else {
2274 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2275 }
2276 }
2277
2278 if let Ok(exec) = executor.lock() {
2279 use feagi_npu_plasticity::{
2280 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2281 };
2282
2283 let lifecycle_config = MemoryNeuronLifecycleConfig {
2284 initial_lifespan: mem_props.init_lifespan,
2285 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2286 longterm_threshold: mem_props.longterm_threshold,
2287 max_reactivations: 1000,
2288 };
2289
2290 exec.register_memory_area(
2291 dst_area.cortical_idx,
2292 dst_area_id.as_base_64(),
2293 mem_props.temporal_depth,
2294 upstream_non_memory,
2295 Some(lifecycle_config),
2296 mem_props.mp_learning_enabled,
2297 );
2298 } else {
2299 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2300 }
2301 } else {
2302 debug!(
2303 target: "feagi-bdu",
2304 "Skipping plasticity registration: no memory properties for area {}",
2305 dst_area_id.as_base_64()
2306 );
2307 }
2308 } else {
2309 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2310 }
2311 } else {
2312 warn!(
2313 target: "feagi-bdu",
2314 "PlasticityExecutor not available; memory area {} not registered",
2315 dst_area_id.as_base_64()
2316 );
2317 }
2318
2319 #[cfg(not(feature = "plasticity"))]
2320 {
2321 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2322 }
2323 } else {
2324 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2326
2327 let mut npu = npu_arc.lock().unwrap();
2329 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2330 }
2331
2332 info!(
2333 target: "feagi-bdu",
2334 "Created {} new synapses: {} -> {}",
2335 synapse_count,
2336 src_area_id,
2337 dst_area_id
2338 );
2339
2340 if pruned_synapse_count > 0 || synapse_count == 0 {
2343 let mut npu = npu_arc.lock().unwrap();
2344 npu.rebuild_synapse_index();
2345 info!(
2346 target: "feagi-bdu",
2347 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2348 src_area_id,
2349 dst_area_id,
2350 pruned_synapse_count,
2351 synapse_count
2352 );
2353 } else {
2354 info!(
2355 target: "feagi-bdu",
2356 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2357 src_area_id,
2358 dst_area_id,
2359 synapse_count
2360 );
2361 }
2362
2363 {
2365 let npu = npu_arc.lock().unwrap();
2366 let fresh_count = npu.get_synapse_count();
2367 self.cached_synapse_count
2368 .store(fresh_count, Ordering::Relaxed);
2369 }
2370
2371 Ok(synapse_count)
2372 }
2373
2374 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2376 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2377 }
2378
2379 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2380 v.as_u64()
2381 .map(|n| n as usize)
2382 .or_else(|| v.as_f64().map(|f| f as usize))
2383 }
2384
2385 #[allow(clippy::too_many_arguments)]
2387 fn register_stdp_mapping_for_rule(
2388 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2389 src_area_id: &CorticalID,
2390 dst_area_id: &CorticalID,
2391 src_cortical_idx: u32,
2392 dst_cortical_idx: u32,
2393 rule_obj: &serde_json::Map<String, serde_json::Value>,
2394 bidirectional_stdp: bool,
2395 synapse_psp: f32,
2396 synapse_type: feagi_npu_neural::SynapseType,
2397 ) -> BduResult<()> {
2398 let plasticity_window = rule_obj
2399 .get("plasticity_window")
2400 .and_then(Self::json_number_as_usize_for_stdp)
2401 .ok_or_else(|| {
2402 BduError::Internal(format!(
2403 "Missing plasticity_window in plastic mapping rule {} -> {}",
2404 src_area_id, dst_area_id
2405 ))
2406 })?;
2407 let plasticity_constant = rule_obj
2408 .get("plasticity_constant")
2409 .and_then(Self::json_number_as_i64_for_stdp)
2410 .ok_or_else(|| {
2411 BduError::Internal(format!(
2412 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2413 src_area_id, dst_area_id
2414 ))
2415 })?;
2416 let ltp_i64 = rule_obj
2417 .get("ltp_multiplier")
2418 .and_then(Self::json_number_as_i64_for_stdp)
2419 .ok_or_else(|| {
2420 BduError::Internal(format!(
2421 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2422 src_area_id, dst_area_id
2423 ))
2424 })?;
2425 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
2426 BduError::Internal(format!(
2427 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2428 i8::MIN,
2429 i8::MAX,
2430 ltp_i64,
2431 src_area_id,
2432 dst_area_id
2433 ))
2434 })?;
2435 let ltd_i64 = rule_obj
2436 .get("ltd_multiplier")
2437 .and_then(Self::json_number_as_i64_for_stdp)
2438 .ok_or_else(|| {
2439 BduError::Internal(format!(
2440 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2441 src_area_id, dst_area_id
2442 ))
2443 })?;
2444 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
2445 BduError::Internal(format!(
2446 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2447 i8::MIN,
2448 i8::MAX,
2449 ltd_i64,
2450 src_area_id,
2451 dst_area_id
2452 ))
2453 })?;
2454
2455 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
2459 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
2460 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2461 }
2462 Some(s) if s.eq_ignore_ascii_case("stdp") => {
2463 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
2464 }
2465 Some(s) if s.eq_ignore_ascii_case("off") => {
2466 feagi_npu_burst_engine::npu::PlasticityMode::Off
2467 }
2468 Some(other) => {
2469 return Err(BduError::Internal(format!(
2470 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
2471 other, src_area_id, dst_area_id
2472 )));
2473 }
2474 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
2475 };
2476
2477 let eligibility_decay_bursts = rule_obj
2479 .get("eligibility_decay_bursts")
2480 .and_then(|v| v.as_u64())
2481 .map(|n| n as u32)
2482 .unwrap_or(0);
2483 let reward_source_area_id = rule_obj
2484 .get("reward_source_area")
2485 .and_then(|v| v.as_str())
2486 .map(str::to_string);
2487 let punishment_source_area_id = rule_obj
2488 .get("punishment_source_area")
2489 .and_then(|v| v.as_str())
2490 .map(str::to_string);
2491
2492 let max_weight_provided = rule_obj.get("max_weight").is_some()
2497 && !rule_obj
2498 .get("max_weight")
2499 .map(|v| v.is_null())
2500 .unwrap_or(true);
2501 let max_weight: f32 = if max_weight_provided {
2502 let raw = rule_obj
2503 .get("max_weight")
2504 .and_then(|v| v.as_f64())
2505 .ok_or_else(|| {
2506 BduError::Internal(format!(
2507 "max_weight must be a number on mapping {} -> {}",
2508 src_area_id, dst_area_id
2509 ))
2510 })?;
2511 if raw.is_nan() || raw <= 0.0 {
2512 return Err(BduError::Internal(format!(
2513 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
2514 raw, src_area_id, dst_area_id
2515 )));
2516 }
2517 raw as f32
2518 } else {
2519 f32::INFINITY
2520 };
2521
2522 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
2525 && !rule_obj
2526 .get("plasticity_eta")
2527 .map(|v| v.is_null())
2528 .unwrap_or(true);
2529 let plasticity_eta: f32 = if plasticity_eta_provided {
2530 let raw = rule_obj
2531 .get("plasticity_eta")
2532 .and_then(|v| v.as_f64())
2533 .ok_or_else(|| {
2534 BduError::Internal(format!(
2535 "plasticity_eta must be a number on mapping {} -> {}",
2536 src_area_id, dst_area_id
2537 ))
2538 })?;
2539 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
2540 return Err(BduError::Internal(format!(
2541 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
2542 raw, src_area_id, dst_area_id
2543 )));
2544 }
2545 raw as f32
2546 } else {
2547 1.0
2548 };
2549
2550 if !matches!(
2552 plasticity_mode,
2553 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2554 ) && (reward_source_area_id.is_some()
2555 || punishment_source_area_id.is_some()
2556 || eligibility_decay_bursts != 0)
2557 {
2558 return Err(BduError::Internal(format!(
2559 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
2560 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
2561 src_area_id, dst_area_id
2562 )));
2563 }
2564
2565 if matches!(
2569 plasticity_mode,
2570 feagi_npu_burst_engine::npu::PlasticityMode::Off
2571 ) && max_weight_provided
2572 {
2573 return Err(BduError::Internal(format!(
2574 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2575 src_area_id, dst_area_id
2576 )));
2577 }
2578
2579 if matches!(
2580 plasticity_mode,
2581 feagi_npu_burst_engine::npu::PlasticityMode::Off
2582 ) && plasticity_eta_provided
2583 {
2584 return Err(BduError::Internal(format!(
2585 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2586 src_area_id, dst_area_id
2587 )));
2588 }
2589
2590 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2591 let mut npu_lock = npu
2592 .lock()
2593 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2594 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2595
2596 let resolve_optional_area =
2601 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
2602 let Some(name) = name_opt else {
2603 return Ok(None);
2604 };
2605 match npu_lock.get_cortical_area_id(name.as_str()) {
2606 Some(idx) => Ok(Some(idx)),
2607 None => Err(BduError::Internal(format!(
2608 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
2609 label, name, src_area_id, dst_area_id
2610 ))),
2611 }
2612 };
2613 let reward_source_area =
2614 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
2615 let punishment_source_area =
2616 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
2617
2618 if matches!(
2620 plasticity_mode,
2621 feagi_npu_burst_engine::npu::PlasticityMode::Off
2622 ) {
2623 return Ok(());
2624 }
2625
2626 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2627 plasticity_window,
2628 plasticity_constant,
2629 ltp_multiplier,
2630 ltd_multiplier,
2631 bidirectional_stdp,
2632 synapse_psp,
2633 synapse_type,
2634 plasticity_mode,
2635 eligibility_decay_bursts,
2636 reward_source_area,
2637 punishment_source_area,
2638 max_weight,
2639 plasticity_eta,
2640 };
2641
2642 npu_lock
2643 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2644 .map_err(|e| {
2645 BduError::Internal(format!(
2646 "Failed to register STDP mapping {} -> {}: {}",
2647 src_area_id, dst_area_id, e
2648 ))
2649 })?;
2650
2651 let mut areas_to_track: Vec<(u32, usize)> = vec![
2655 (src_cortical_idx, plasticity_window),
2656 (dst_cortical_idx, plasticity_window),
2657 ];
2658 if let Some(area) = reward_source_area {
2659 areas_to_track.push((area, 1));
2660 }
2661 if let Some(area) = punishment_source_area {
2662 areas_to_track.push((area, 1));
2663 }
2664
2665 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2666 for (area_idx, required_depth) in areas_to_track {
2667 let existing = existing_configs
2668 .iter()
2669 .find(|(idx, _)| *idx == area_idx)
2670 .map(|(_, w)| *w)
2671 .unwrap_or(0);
2672 let resolved = existing.max(required_depth);
2673 if resolved != existing {
2674 npu_lock
2675 .configure_fire_ledger_window(area_idx, resolved)
2676 .map_err(|e| {
2677 BduError::Internal(format!(
2678 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2679 area_idx, resolved, e
2680 ))
2681 })?;
2682 }
2683 }
2684
2685 Ok(())
2686 }
2687
2688 fn resolve_synapse_params_for_rule(
2690 &self,
2691 src_area_id: &CorticalID,
2692 rule: &serde_json::Value,
2693 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
2694 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2696 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2697 })?;
2698
2699 let (weight, synapse_type) = {
2701 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
2702 if let Some(i) = v.as_i64() {
2703 return Some(i as f64);
2704 }
2705 v.as_f64()
2706 };
2707
2708 let mult: f64 = if let Some(obj) = rule.as_object() {
2709 obj.get("postSynapticCurrent_multiplier")
2710 .and_then(parse_f64)
2711 .unwrap_or(1.0)
2712 } else if let Some(arr) = rule.as_array() {
2713 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
2714 } else {
2715 128.0
2716 };
2717
2718 if mult < 0.0 {
2719 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
2720 } else {
2721 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
2722 }
2723 };
2724
2725 use crate::models::cortical_area::CorticalAreaExt;
2727 let psp_f32 = src_area.postsynaptic_current();
2728
2729 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
2730 obj.get("synaptic_delay_bursts")
2731 .and_then(|v| v.as_u64())
2732 .map(|d| u8::try_from(d).unwrap_or(1))
2733 .unwrap_or(1)
2734 } else if let Some(arr) = rule.as_array() {
2735 arr.get(8)
2736 .and_then(|v| v.as_u64())
2737 .map(|d| u8::try_from(d).unwrap_or(1))
2738 .unwrap_or(1)
2739 } else {
2740 1
2741 };
2742 if delay_bursts < 1 {
2743 return Err(crate::types::BduError::Internal(format!(
2744 "synaptic_delay_bursts must be >= 1 (src area {})",
2745 src_area_id.as_base_64()
2746 )));
2747 }
2748
2749 tracing::debug!(
2750 target: "feagi-bdu",
2751 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
2752 src_area_id.as_base_64(),
2753 weight,
2754 psp_f32,
2755 synapse_type,
2756 delay_bursts
2757 );
2758
2759 Ok((weight, psp_f32, synapse_type, delay_bursts))
2760 }
2761
2762 fn apply_cortical_mapping_for_pair(
2764 &mut self,
2765 src_area_id: &CorticalID,
2766 dst_area_id: &CorticalID,
2767 ) -> BduResult<usize> {
2768 let rules = {
2774 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2775 crate::types::BduError::InvalidArea(format!(
2776 "Source area not found: {}",
2777 src_area_id
2778 ))
2779 })?;
2780
2781 let Some(mapping_dst) = src_area
2782 .properties
2783 .get("cortical_mapping_dst")
2784 .and_then(|v| v.as_object())
2785 else {
2786 return Ok(0);
2787 };
2788
2789 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
2790 else {
2791 return Ok(0);
2792 };
2793
2794 rules.clone()
2795 }; if rules.is_empty() {
2798 return Ok(0);
2799 }
2800
2801 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2803 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2804 })?;
2805 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2806 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2807 })?;
2808
2809 let npu_arc = self
2811 .npu
2812 .as_ref()
2813 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2814 .clone();
2815
2816 tracing::debug!(
2817 target: "feagi-bdu",
2818 "Applying {} mapping rule(s) for {} -> {}",
2819 rules.len(),
2820 src_area_id,
2821 dst_area_id
2822 );
2823 let mut total_synapses = 0;
2825 for rule in &rules {
2826 let morphology_id = if let Some(rule_obj) = rule.as_object() {
2827 rule_obj
2828 .get("morphology_id")
2829 .and_then(|v| v.as_str())
2830 .unwrap_or("unknown")
2831 .to_string()
2832 } else if let Some(rule_arr) = rule.as_array() {
2833 rule_arr
2834 .first()
2835 .and_then(|v| v.as_str())
2836 .unwrap_or("unknown")
2837 .to_string()
2838 } else {
2839 "unknown".to_string()
2840 };
2841
2842 let rule_keys: Vec<String> = rule
2843 .as_object()
2844 .map(|obj| obj.keys().cloned().collect())
2845 .unwrap_or_default();
2846
2847 let mut plasticity_flag = rule
2849 .as_object()
2850 .and_then(|obj| obj.get("plasticity_flag"))
2851 .and_then(|v| v.as_bool())
2852 .unwrap_or(false);
2853 if morphology_id == "associative_memory" {
2854 plasticity_flag = true;
2855 }
2856 if plasticity_flag {
2857 let Some(rule_obj) = rule.as_object() else {
2858 return Err(crate::types::BduError::InvalidMorphology(
2859 "Plasticity mapping rule must be an object format".to_string(),
2860 ));
2861 };
2862 let (_weight, psp, synapse_type, _delay_bursts) =
2863 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2864 let bidirectional_stdp = morphology_id == "associative_memory";
2865 if let Err(e) = Self::register_stdp_mapping_for_rule(
2866 &npu_arc,
2867 src_area_id,
2868 dst_area_id,
2869 src_cortical_idx,
2870 dst_cortical_idx,
2871 rule_obj,
2872 bidirectional_stdp,
2873 psp,
2874 synapse_type,
2875 ) {
2876 tracing::error!(
2877 target: "feagi-bdu",
2878 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2879 src_area_id,
2880 dst_area_id,
2881 morphology_id,
2882 rule_keys,
2883 e
2884 );
2885 return Err(e);
2886 }
2887 }
2888
2889 if let Some(gate_area_str) = rule
2893 .as_object()
2894 .and_then(|obj| obj.get("gate_source_area"))
2895 .and_then(|v| v.as_str())
2896 {
2897 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
2898 crate::types::BduError::Internal(format!(
2899 "Invalid gate_source_area '{}' on mapping {} -> {}",
2900 gate_area_str, src_area_id, dst_area_id
2901 ))
2902 })?;
2903 let gate_cortical_idx =
2904 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
2905 crate::types::BduError::Internal(format!(
2906 "Unknown gate_source_area '{}' on mapping {} -> {}",
2907 gate_area_str, src_area_id, dst_area_id
2908 ))
2909 })?;
2910
2911 let mut npu_lock = npu_arc.lock().map_err(|_| {
2912 crate::types::BduError::Internal(
2913 "Failed to acquire NPU lock for gate registration".to_string(),
2914 )
2915 })?;
2916 if let Err(e) = npu_lock.register_gate_mapping(
2917 src_cortical_idx,
2918 dst_cortical_idx,
2919 *gate_cortical_idx,
2920 ) {
2921 tracing::error!(
2922 target: "feagi-bdu",
2923 "Gate mapping registration failed for {} -> {} (gate={}): {}",
2924 src_area_id,
2925 dst_area_id,
2926 gate_area_str,
2927 e
2928 );
2929 return Err(crate::types::BduError::Internal(format!(
2930 "Gate registration failed: {}",
2931 e
2932 )));
2933 }
2934 }
2935
2936 let synapse_count = match self.apply_single_morphology_rule(
2938 src_area_id,
2939 dst_area_id,
2940 rule,
2941 ) {
2942 Ok(count) => count,
2943 Err(e) => {
2944 tracing::error!(
2945 target: "feagi-bdu",
2946 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2947 src_area_id,
2948 dst_area_id,
2949 morphology_id,
2950 rule_keys,
2951 e
2952 );
2953 return Err(e);
2954 }
2955 };
2956 total_synapses += synapse_count;
2957 tracing::debug!(
2958 target: "feagi-bdu",
2959 "Rule {} created {} synapses for {} -> {}",
2960 morphology_id,
2961 synapse_count,
2962 src_area_id,
2963 dst_area_id
2964 );
2965 }
2966
2967 Ok(total_synapses)
2968 }
2969
2970 #[allow(clippy::too_many_arguments)]
2984 fn apply_function_morphology(
2985 &self,
2986 morphology_id: &str,
2987 rule: &serde_json::Value,
2988 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2989 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2990 src_area_id: &CorticalID,
2991 dst_area_id: &CorticalID,
2992 src_idx: u32,
2993 dst_idx: u32,
2994 weight: f32,
2995 psp: f32,
2996 synapse_attractivity: u8,
2997 synapse_type: feagi_npu_neural::SynapseType,
2998 delay_bursts: u8,
2999 ) -> BduResult<usize> {
3000 match morphology_id {
3001 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
3002 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3004 crate::types::BduError::InvalidArea(format!(
3005 "Source area not found: {}",
3006 src_area_id
3007 ))
3008 })?;
3009 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3010 crate::types::BduError::InvalidArea(format!(
3011 "Destination area not found: {}",
3012 dst_area_id
3013 ))
3014 })?;
3015
3016 let src_dimensions = (
3017 src_area.dimensions.width as usize,
3018 src_area.dimensions.height as usize,
3019 src_area.dimensions.depth as usize,
3020 );
3021 let dst_dimensions = (
3022 dst_area.dimensions.width as usize,
3023 dst_area.dimensions.height as usize,
3024 dst_area.dimensions.depth as usize,
3025 );
3026
3027 let transpose = match morphology_id {
3030 "transpose_xy" => Some((1, 0, 2)),
3031 "transpose_yz" => Some((0, 2, 1)),
3032 "transpose_xz" => Some((2, 1, 0)),
3033 _ => None,
3034 };
3035
3036 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3037 let count = apply_projector_morphology_with_dimensions(
3038 npu,
3039 src_idx,
3040 dst_idx,
3041 src_dimensions,
3042 dst_dimensions,
3043 transpose,
3044 None, weight,
3046 psp,
3047 synapse_attractivity,
3048 synapse_type,
3049 0,
3050 delay_bursts,
3051 )?;
3052 npu.rebuild_synapse_index();
3054 Ok(count as usize)
3055 }
3056 "centered_projector" => {
3057 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3058 crate::types::BduError::InvalidArea(format!(
3059 "Source area not found: {}",
3060 src_area_id
3061 ))
3062 })?;
3063 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3064 crate::types::BduError::InvalidArea(format!(
3065 "Destination area not found: {}",
3066 dst_area_id
3067 ))
3068 })?;
3069
3070 let src_dimensions = (
3071 src_area.dimensions.width as usize,
3072 src_area.dimensions.height as usize,
3073 src_area.dimensions.depth as usize,
3074 );
3075 let dst_dimensions = (
3076 dst_area.dimensions.width as usize,
3077 dst_area.dimensions.height as usize,
3078 dst_area.dimensions.depth as usize,
3079 );
3080
3081 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
3082 npu,
3083 src_idx,
3084 dst_idx,
3085 src_dimensions,
3086 dst_dimensions,
3087 weight,
3088 psp,
3089 synapse_attractivity,
3090 synapse_type,
3091 delay_bursts,
3092 )?;
3093 if count > 0 {
3094 npu.rebuild_synapse_index();
3095 }
3096 Ok(count as usize)
3097 }
3098 "episodic_memory" => {
3099 use tracing::trace;
3102 trace!(
3103 target: "feagi-bdu",
3104 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
3105 src_idx, dst_idx
3106 );
3107 Ok(0)
3108 }
3109 "memory_replay" => {
3110 use tracing::trace;
3112 trace!(
3113 target: "feagi-bdu",
3114 "Memory replay morphology: {} -> {} (no physical synapses)",
3115 src_idx, dst_idx
3116 );
3117 Ok(0)
3118 }
3119 "associative_memory" => {
3120 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3124 crate::types::BduError::InvalidArea(format!(
3125 "Source area not found: {}",
3126 src_area_id
3127 ))
3128 })?;
3129 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3130 crate::types::BduError::InvalidArea(format!(
3131 "Destination area not found: {}",
3132 dst_area_id
3133 ))
3134 })?;
3135
3136 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3137 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3138 {
3139 let src_dimensions = (
3140 src_area.dimensions.width as usize,
3141 src_area.dimensions.height as usize,
3142 src_area.dimensions.depth as usize,
3143 );
3144 let dst_dimensions = (
3145 dst_area.dimensions.width as usize,
3146 dst_area.dimensions.height as usize,
3147 dst_area.dimensions.depth as usize,
3148 );
3149 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3150 let count = apply_projector_morphology_with_dimensions(
3151 npu,
3152 src_idx,
3153 dst_idx,
3154 src_dimensions,
3155 dst_dimensions,
3156 None,
3157 None,
3158 weight,
3159 psp,
3160 synapse_attractivity,
3161 synapse_type,
3162 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
3163 delay_bursts,
3164 )?;
3165 npu.rebuild_synapse_index();
3166 Ok(count as usize)
3167 } else {
3168 Ok(0)
3169 }
3170 }
3171 "block_to_block" => {
3172 tracing::warn!(
3173 target: "feagi-bdu",
3174 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
3175 src_idx, dst_idx
3176 );
3177 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3179 crate::types::BduError::InvalidArea(format!(
3180 "Source area not found: {}",
3181 src_area_id
3182 ))
3183 })?;
3184 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3185 crate::types::BduError::InvalidArea(format!(
3186 "Destination area not found: {}",
3187 dst_area_id
3188 ))
3189 })?;
3190
3191 let src_dimensions = (
3192 src_area.dimensions.width as usize,
3193 src_area.dimensions.height as usize,
3194 src_area.dimensions.depth as usize,
3195 );
3196 let dst_dimensions = (
3197 dst_area.dimensions.width as usize,
3198 dst_area.dimensions.height as usize,
3199 dst_area.dimensions.depth as usize,
3200 );
3201
3202 let scalar = if let Some(obj) = rule.as_object() {
3204 if let Some(scalar_arr) =
3206 obj.get("morphology_scalar").and_then(|v| v.as_array())
3207 {
3208 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
3210 } else {
3211 1 }
3213 } else if let Some(arr) = rule.as_array() {
3214 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
3216 } else {
3217 1 };
3219
3220 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
3223 let count = if estimated_neurons > 100_000 {
3224 let _ = npu;
3226
3227 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
3228 npu_arc,
3229 src_idx,
3230 dst_idx,
3231 src_dimensions,
3232 dst_dimensions,
3233 scalar, weight,
3235 psp,
3236 synapse_attractivity,
3237 synapse_type,
3238 delay_bursts,
3239 )? as usize
3240 } else {
3241 tracing::warn!(
3243 target: "feagi-bdu",
3244 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
3245 src_idx, dst_idx, src_dimensions, dst_dimensions
3246 );
3247 let count =
3248 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
3249 npu,
3250 src_idx,
3251 dst_idx,
3252 src_dimensions,
3253 dst_dimensions,
3254 scalar, weight,
3256 psp,
3257 synapse_attractivity,
3258 synapse_type,
3259 delay_bursts,
3260 )? as usize;
3261 tracing::warn!(
3262 target: "feagi-bdu",
3263 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
3264 count
3265 );
3266 if count > 0 {
3268 npu.rebuild_synapse_index();
3269 }
3270 count
3271 };
3272
3273 if count > 0 && estimated_neurons > 100_000 {
3275 let mut npu_lock = npu_arc.lock().unwrap();
3276 npu_lock.rebuild_synapse_index();
3277 }
3278
3279 Ok(count)
3280 }
3281 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
3282 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
3283 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3284 crate::types::BduError::InvalidArea(format!(
3285 "Source area not found: {}",
3286 src_area_id
3287 ))
3288 })?;
3289 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3290 crate::types::BduError::InvalidArea(format!(
3291 "Destination area not found: {}",
3292 dst_area_id
3293 ))
3294 })?;
3295
3296 let src_dimensions = (
3297 src_area.dimensions.width as usize,
3298 src_area.dimensions.height as usize,
3299 src_area.dimensions.depth as usize,
3300 );
3301 let dst_dimensions = (
3302 dst_area.dimensions.width as usize,
3303 dst_area.dimensions.height as usize,
3304 dst_area.dimensions.depth as usize,
3305 );
3306
3307 let (axis, mode) = match morphology_id {
3308 "bitmask_encoder_x" => (
3309 crate::connectivity::core_morphologies::BitmaskAxis::X,
3310 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3311 ),
3312 "bitmask_encoder_y" => (
3313 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3314 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3315 ),
3316 "bitmask_encoder_z" => (
3317 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3318 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3319 ),
3320 "bitmask_decoder_x" => (
3321 crate::connectivity::core_morphologies::BitmaskAxis::X,
3322 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3323 ),
3324 "bitmask_decoder_y" => (
3325 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3326 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3327 ),
3328 "bitmask_decoder_z" => (
3329 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3330 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3331 ),
3332 _ => unreachable!("matched bitmask morphology above"),
3333 };
3334
3335 let count =
3336 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
3337 npu,
3338 src_idx,
3339 dst_idx,
3340 src_dimensions,
3341 dst_dimensions,
3342 axis,
3343 mode,
3344 weight,
3345 psp,
3346 synapse_attractivity,
3347 synapse_type,
3348 delay_bursts,
3349 )?;
3350 if count > 0 {
3351 npu.rebuild_synapse_index();
3352 }
3353 Ok(count as usize)
3354 }
3355 "sweeper" => {
3356 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3357 crate::types::BduError::InvalidArea(format!(
3358 "Destination area not found: {}",
3359 dst_area_id
3360 ))
3361 })?;
3362 let dst_dimensions = (
3363 dst_area.dimensions.width as usize,
3364 dst_area.dimensions.height as usize,
3365 dst_area.dimensions.depth as usize,
3366 );
3367
3368 let count =
3369 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3370 npu,
3371 src_idx,
3372 dst_idx,
3373 dst_dimensions,
3374 weight,
3375 psp,
3376 synapse_attractivity,
3377 synapse_type,
3378 delay_bursts,
3379 )?;
3380 if count > 0 {
3381 npu.rebuild_synapse_index();
3382 }
3383 Ok(count as usize)
3384 }
3385 "last_to_first" => {
3386 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3387 crate::types::BduError::InvalidArea(format!(
3388 "Source area not found: {}",
3389 src_area_id
3390 ))
3391 })?;
3392 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3393 crate::types::BduError::InvalidArea(format!(
3394 "Destination area not found: {}",
3395 dst_area_id
3396 ))
3397 })?;
3398 let src_dimensions = (
3399 src_area.dimensions.width as usize,
3400 src_area.dimensions.height as usize,
3401 src_area.dimensions.depth as usize,
3402 );
3403 let dst_dimensions = (
3404 dst_area.dimensions.width as usize,
3405 dst_area.dimensions.height as usize,
3406 dst_area.dimensions.depth as usize,
3407 );
3408
3409 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3410 npu,
3411 src_idx,
3412 dst_idx,
3413 src_dimensions,
3414 dst_dimensions,
3415 weight,
3416 psp,
3417 synapse_attractivity,
3418 synapse_type,
3419 delay_bursts,
3420 )?;
3421 if count > 0 {
3422 npu.rebuild_synapse_index();
3423 }
3424 Ok(count as usize)
3425 }
3426 "first_to_last" => {
3427 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3428 crate::types::BduError::InvalidArea(format!(
3429 "Source area not found: {}",
3430 src_area_id
3431 ))
3432 })?;
3433 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3434 crate::types::BduError::InvalidArea(format!(
3435 "Destination area not found: {}",
3436 dst_area_id
3437 ))
3438 })?;
3439 let src_dimensions = (
3440 src_area.dimensions.width as usize,
3441 src_area.dimensions.height as usize,
3442 src_area.dimensions.depth as usize,
3443 );
3444 let dst_dimensions = (
3445 dst_area.dimensions.width as usize,
3446 dst_area.dimensions.height as usize,
3447 dst_area.dimensions.depth as usize,
3448 );
3449
3450 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
3451 npu,
3452 src_idx,
3453 dst_idx,
3454 src_dimensions,
3455 dst_dimensions,
3456 weight,
3457 psp,
3458 synapse_attractivity,
3459 synapse_type,
3460 delay_bursts,
3461 )?;
3462 if count > 0 {
3463 npu.rebuild_synapse_index();
3464 }
3465 Ok(count as usize)
3466 }
3467 "rotator_z" => {
3468 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3469 crate::types::BduError::InvalidArea(format!(
3470 "Source area not found: {}",
3471 src_area_id
3472 ))
3473 })?;
3474 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3475 crate::types::BduError::InvalidArea(format!(
3476 "Destination area not found: {}",
3477 dst_area_id
3478 ))
3479 })?;
3480 let src_dimensions = (
3481 src_area.dimensions.width as usize,
3482 src_area.dimensions.height as usize,
3483 src_area.dimensions.depth as usize,
3484 );
3485 let dst_dimensions = (
3486 dst_area.dimensions.width as usize,
3487 dst_area.dimensions.height as usize,
3488 dst_area.dimensions.depth as usize,
3489 );
3490
3491 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3492 npu,
3493 src_idx,
3494 dst_idx,
3495 src_dimensions,
3496 dst_dimensions,
3497 weight,
3498 psp,
3499 synapse_attractivity,
3500 synapse_type,
3501 delay_bursts,
3502 )?;
3503 if count > 0 {
3504 npu.rebuild_synapse_index();
3505 }
3506 Ok(count as usize)
3507 }
3508 _ => {
3509 use tracing::debug;
3512 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3513 Ok(0)
3514 }
3515 }
3516 }
3517
3518 fn apply_single_morphology_rule(
3520 &mut self,
3521 src_area_id: &CorticalID,
3522 dst_area_id: &CorticalID,
3523 rule: &serde_json::Value,
3524 ) -> BduResult<usize> {
3525 let morphology_id = if let Some(arr) = rule.as_array() {
3527 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3528 } else if let Some(obj) = rule.as_object() {
3529 obj.get("morphology_id")
3530 .and_then(|v| v.as_str())
3531 .unwrap_or("")
3532 } else {
3533 return Ok(0);
3534 };
3535
3536 if morphology_id.is_empty() {
3537 return Ok(0);
3538 }
3539
3540 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3542 crate::types::BduError::InvalidMorphology(format!(
3543 "Morphology not found: {}",
3544 morphology_id
3545 ))
3546 })?;
3547
3548 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3550 crate::types::BduError::InvalidArea(format!(
3551 "Source area ID not found: {}",
3552 src_area_id
3553 ))
3554 })?;
3555 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3556 crate::types::BduError::InvalidArea(format!(
3557 "Destination area ID not found: {}",
3558 dst_area_id
3559 ))
3560 })?;
3561
3562 if let Some(ref npu_arc) = self.npu {
3564 let lock_start = std::time::Instant::now();
3565 let mut npu = npu_arc.lock().unwrap();
3566 let lock_wait = lock_start.elapsed();
3567 tracing::debug!(
3568 target: "feagi-bdu",
3569 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3570 lock_wait.as_secs_f64() * 1000.0,
3571 src_area_id,
3572 dst_area_id,
3573 morphology_id
3574 );
3575
3576 let (weight, psp, synapse_type, delay_bursts) =
3577 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3578
3579 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3581 obj.get("synapse_attractivity")
3582 .and_then(|v| v.as_u64())
3583 .unwrap_or(100) as u8
3584 } else {
3585 100 };
3587
3588 match morphology.morphology_type {
3589 feagi_evolutionary::MorphologyType::Functions => {
3590 tracing::warn!(
3591 target: "feagi-bdu",
3592 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3593 morphology_id
3594 );
3595 self.apply_function_morphology(
3598 morphology_id,
3599 rule,
3600 npu_arc,
3601 &mut npu,
3602 src_area_id,
3603 dst_area_id,
3604 *src_idx,
3605 *dst_idx,
3606 weight,
3607 psp,
3608 synapse_attractivity,
3609 synapse_type,
3610 delay_bursts,
3611 )
3612 }
3613 feagi_evolutionary::MorphologyType::Vectors => {
3614 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3615
3616 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3618 crate::types::BduError::InvalidArea(format!(
3619 "Destination area not found: {}",
3620 dst_area_id
3621 ))
3622 })?;
3623
3624 let dst_dimensions = (
3625 dst_area.dimensions.width as usize,
3626 dst_area.dimensions.height as usize,
3627 dst_area.dimensions.depth as usize,
3628 );
3629
3630 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3631 morphology.parameters
3632 {
3633 let vectors_tuples: Vec<(i32, i32, i32)> =
3635 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3636
3637 let count = apply_vectors_morphology_with_dimensions(
3638 &mut npu,
3639 *src_idx,
3640 *dst_idx,
3641 vectors_tuples,
3642 dst_dimensions,
3643 weight, psp, synapse_attractivity, synapse_type,
3647 delay_bursts,
3648 )?;
3649 npu.rebuild_synapse_index();
3652 Ok(count as usize)
3653 } else {
3654 Ok(0)
3655 }
3656 }
3657 feagi_evolutionary::MorphologyType::Patterns => {
3658 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3659 use crate::connectivity::rules::patterns::{
3660 Pattern3D, PatternElement as RulePatternElement,
3661 };
3662 use feagi_evolutionary::PatternElement as EvoPatternElement;
3663
3664 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3665 morphology.parameters
3666 else {
3667 return Ok(0);
3668 };
3669
3670 let convert_element =
3671 |element: &EvoPatternElement|
3672 -> crate::types::BduResult<RulePatternElement> {
3673 match element {
3674 EvoPatternElement::Value(value) => {
3675 if *value < 0 {
3676 return Err(crate::types::BduError::InvalidMorphology(
3677 format!(
3678 "Pattern morphology {} contains negative voxel coordinate {}",
3679 morphology_id, value
3680 ),
3681 ));
3682 }
3683 Ok(RulePatternElement::Exact(*value))
3684 }
3685 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3686 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3687 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3688 EvoPatternElement::DirectionPositive => {
3689 Ok(RulePatternElement::DirectionExclusive(
3690 crate::connectivity::rules::patterns::Direction::Positive,
3691 ))
3692 }
3693 EvoPatternElement::DirectionNegative => {
3694 Ok(RulePatternElement::DirectionExclusive(
3695 crate::connectivity::rules::patterns::Direction::Negative,
3696 ))
3697 }
3698 EvoPatternElement::DirectionPositiveInclusive => {
3699 Ok(RulePatternElement::DirectionInclusive(
3700 crate::connectivity::rules::patterns::Direction::Positive,
3701 ))
3702 }
3703 EvoPatternElement::DirectionNegativeInclusive => {
3704 Ok(RulePatternElement::DirectionInclusive(
3705 crate::connectivity::rules::patterns::Direction::Negative,
3706 ))
3707 }
3708 EvoPatternElement::Offset(off) => {
3709 Ok(RulePatternElement::Offset(*off))
3710 }
3711 EvoPatternElement::Range(lo, hi) => {
3712 Ok(RulePatternElement::Range(*lo, *hi))
3713 }
3714 }
3715 };
3716
3717 let mut converted_patterns = Vec::with_capacity(patterns.len());
3718 for pattern_pair in patterns {
3719 if pattern_pair.len() != 2 {
3720 return Err(crate::types::BduError::InvalidMorphology(format!(
3721 "Pattern morphology {} must contain [src, dst] pairs",
3722 morphology_id
3723 )));
3724 }
3725
3726 let src_pattern = &pattern_pair[0];
3727 let dst_pattern = &pattern_pair[1];
3728
3729 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3730 return Err(crate::types::BduError::InvalidMorphology(format!(
3731 "Pattern morphology {} requires 3-axis patterns",
3732 morphology_id
3733 )));
3734 }
3735
3736 let src: Pattern3D = (
3737 convert_element(&src_pattern[0])?,
3738 convert_element(&src_pattern[1])?,
3739 convert_element(&src_pattern[2])?,
3740 );
3741 let dst: Pattern3D = (
3742 convert_element(&dst_pattern[0])?,
3743 convert_element(&dst_pattern[1])?,
3744 convert_element(&dst_pattern[2])?,
3745 );
3746
3747 converted_patterns.push((src, dst));
3748 }
3749
3750 let count = apply_patterns_morphology(
3751 &mut npu,
3752 *src_idx,
3753 *dst_idx,
3754 converted_patterns,
3755 weight,
3756 psp,
3757 synapse_attractivity,
3758 synapse_type,
3759 delay_bursts,
3760 )?;
3761 if count > 0 {
3762 npu.rebuild_synapse_index();
3763 }
3764 Ok(count as usize)
3765 }
3766 feagi_evolutionary::MorphologyType::Composite => {
3767 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3768 morphology.parameters
3769 else {
3770 return Ok(0);
3771 };
3772
3773 if morphology_id != "tile" {
3774 use tracing::debug;
3775 debug!(
3776 target: "feagi-bdu",
3777 "Composite morphology {} not yet implemented",
3778 morphology_id
3779 );
3780 return Ok(0);
3781 }
3782
3783 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3784 crate::types::BduError::InvalidArea(format!(
3785 "Source area not found: {}",
3786 src_area_id
3787 ))
3788 })?;
3789 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3790 crate::types::BduError::InvalidArea(format!(
3791 "Destination area not found: {}",
3792 dst_area_id
3793 ))
3794 })?;
3795 let src_dimensions = (
3796 src_area.dimensions.width as usize,
3797 src_area.dimensions.height as usize,
3798 src_area.dimensions.depth as usize,
3799 );
3800 let dst_dimensions = (
3801 dst_area.dimensions.width as usize,
3802 dst_area.dimensions.height as usize,
3803 dst_area.dimensions.depth as usize,
3804 );
3805
3806 let count =
3807 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3808 &mut npu,
3809 *src_idx,
3810 *dst_idx,
3811 src_dimensions,
3812 dst_dimensions,
3813 weight,
3814 psp,
3815 synapse_attractivity,
3816 synapse_type,
3817 delay_bursts,
3818 )?;
3819 if count > 0 {
3820 npu.rebuild_synapse_index();
3821 }
3822 Ok(count as usize)
3823 }
3824 }
3825 } else {
3826 Ok(0) }
3828 }
3829
3830 pub fn set_npu(
3843 &mut self,
3844 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3845 ) {
3846 self.npu = Some(Arc::clone(&npu));
3847 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3848
3849 #[cfg(not(feature = "wasm"))]
3852 {
3853 use feagi_state_manager::StateManager;
3854 let state_manager = StateManager::instance();
3855 let state_manager = state_manager.read();
3856 let core_state = state_manager.get_core_state();
3857 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3859 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3860 info!(
3861 target: "feagi-bdu",
3862 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3863 self.config.max_neurons, self.config.max_synapses
3864 );
3865 }
3866
3867 let existing_area_count = self.cortical_id_to_idx.len();
3874 if existing_area_count > 0 {
3875 match npu.lock() {
3876 Ok(mut npu_lock) => {
3877 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3878 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3879 }
3880 info!(
3881 target: "feagi-bdu",
3882 "🔁 Backfilled {} cortical area registrations into NPU",
3883 existing_area_count
3884 );
3885 }
3886 Err(e) => {
3887 warn!(
3888 target: "feagi-bdu",
3889 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3890 e
3891 );
3892 }
3893 }
3894 }
3895
3896 self.update_all_cached_stats();
3898 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3899 self.get_neuron_count(), self.get_synapse_count());
3900 }
3901
3902 pub fn has_npu(&self) -> bool {
3904 self.npu.is_some()
3905 }
3906
3907 pub fn get_npu(
3914 &self,
3915 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3916 {
3917 self.npu.as_ref()
3918 }
3919
3920 #[cfg(feature = "plasticity")]
3923 pub fn set_plasticity_executor(
3924 &mut self,
3925 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3926 ) {
3927 self.plasticity_executor = Some(executor);
3928 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3929 }
3930
3931 #[cfg(feature = "plasticity")]
3933 pub fn get_plasticity_executor(
3934 &self,
3935 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3936 self.plasticity_executor.as_ref()
3937 }
3938
3939 pub fn get_neuron_capacity(&self) -> usize {
3951 self.config.max_neurons
3953 }
3954
3955 pub fn get_synapse_capacity(&self) -> usize {
3967 self.config.max_synapses
3969 }
3970
3971 pub fn update_fatigue_index(&self) -> Option<u8> {
3986 let mut last_calc = match self.last_fatigue_calculation.lock() {
3988 Ok(guard) => guard,
3989 Err(_) => return None, };
3991
3992 let now = std::time::Instant::now();
3993 if now.duration_since(*last_calc).as_secs() < 2 {
3994 return None; }
3996 *last_calc = now;
3997 drop(last_calc);
3998
3999 let regular_neuron_count = self.get_neuron_count();
4001 let regular_neuron_capacity = self.get_neuron_capacity();
4002 let regular_neuron_util = if regular_neuron_capacity > 0 {
4003 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
4004 } else {
4005 0
4006 };
4007
4008 let memory_neuron_util = match StateManager::instance().try_read() {
4012 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
4013 None => {
4014 return None;
4016 }
4017 };
4018
4019 let synapse_count = self.get_synapse_count();
4021 let synapse_capacity = self.get_synapse_capacity();
4022 let synapse_util = if synapse_capacity > 0 {
4023 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
4024 } else {
4025 0
4026 };
4027
4028 let fatigue_index = regular_neuron_util
4030 .max(memory_neuron_util)
4031 .max(synapse_util);
4032
4033 let current_fatigue_active = {
4035 StateManager::instance()
4037 .try_read()
4038 .map(|m| m.get_core_state().is_fatigue_active())
4039 .unwrap_or(false)
4040 };
4041
4042 let new_fatigue_active = if fatigue_index >= 85 {
4043 true
4044 } else if fatigue_index < 80 {
4045 false
4046 } else {
4047 current_fatigue_active };
4049
4050 if let Some(state_manager) = StateManager::instance().try_write() {
4054 let core_state = state_manager.get_core_state();
4055 core_state.set_fatigue_index(fatigue_index);
4056 core_state.set_fatigue_active(new_fatigue_active);
4057 core_state.set_regular_neuron_util(regular_neuron_util);
4058 core_state.set_memory_neuron_util(memory_neuron_util);
4059 core_state.set_synapse_util(synapse_util);
4060 } else {
4061 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
4063 }
4064
4065 if let Some(ref npu) = self.npu {
4067 if let Ok(mut npu_lock) = npu.lock() {
4068 npu_lock.set_fatigue_active(new_fatigue_active);
4069 }
4070 }
4071
4072 trace!(
4073 target: "feagi-bdu",
4074 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
4075 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
4076 );
4077
4078 Some(fatigue_index)
4079 }
4080
4081 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
4098 let area = self
4100 .cortical_areas
4101 .get(cortical_id)
4102 .ok_or_else(|| {
4103 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
4104 })?
4105 .clone();
4106
4107 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
4109 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
4110 })?;
4111
4112 let npu = self
4114 .npu
4115 .as_ref()
4116 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4117
4118 use crate::models::CorticalAreaExt;
4121 let per_voxel_cnt = area.neurons_per_voxel();
4122 let firing_threshold = area.firing_threshold();
4123 let firing_threshold_increment_x = area.firing_threshold_increment_x();
4124 let firing_threshold_increment_y = area.firing_threshold_increment_y();
4125 let firing_threshold_increment_z = area.firing_threshold_increment_z();
4126 let firing_threshold_limit_raw = area.firing_threshold_limit();
4128 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
4129 f32::MAX } else {
4131 firing_threshold_limit_raw
4132 };
4133
4134 if firing_threshold_increment_x != 0.0
4136 || firing_threshold_increment_y != 0.0
4137 || firing_threshold_increment_z != 0.0
4138 {
4139 info!(
4140 target: "feagi-bdu",
4141 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
4142 cortical_id.as_base_64(),
4143 firing_threshold_increment_x,
4144 firing_threshold_increment_y,
4145 firing_threshold_increment_z
4146 );
4147 } else {
4148 if area.properties.contains_key("firing_threshold_increment_x")
4150 || area.properties.contains_key("firing_threshold_increment_y")
4151 || area.properties.contains_key("firing_threshold_increment_z")
4152 {
4153 info!(
4154 target: "feagi-bdu",
4155 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
4156 cortical_id.as_base_64(),
4157 area.properties.get("firing_threshold_increment_x"),
4158 area.properties.get("firing_threshold_increment_y"),
4159 area.properties.get("firing_threshold_increment_z")
4160 );
4161 }
4162 }
4163
4164 let leak_coefficient = area.leak_coefficient();
4165 let excitability = area.neuron_excitability();
4166 let refractory_period = area.refractory_period();
4167 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
4169 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
4170 u16::MAX } else {
4172 consecutive_fire_limit_raw
4173 };
4174 let snooze_length = area.snooze_period();
4175 let mp_charge_accumulation = area.mp_charge_accumulation();
4176
4177 let voxels = area.dimensions.width as usize
4179 * area.dimensions.height as usize
4180 * area.dimensions.depth as usize;
4181 let expected_neurons = voxels * per_voxel_cnt as usize;
4182
4183 trace!(
4184 target: "feagi-bdu",
4185 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
4186 cortical_id.as_base_64(),
4187 area.dimensions.width,
4188 area.dimensions.height,
4189 area.dimensions.depth,
4190 per_voxel_cnt,
4191 expected_neurons
4192 );
4193
4194 let neuron_count: u32 = {
4199 let mut npu_lock = npu
4200 .lock()
4201 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4202 npu_lock
4203 .create_cortical_area_neurons(
4204 *cortical_idx,
4205 area.dimensions.width,
4206 area.dimensions.height,
4207 area.dimensions.depth,
4208 per_voxel_cnt,
4209 firing_threshold,
4210 firing_threshold_increment_x,
4211 firing_threshold_increment_y,
4212 firing_threshold_increment_z,
4213 firing_threshold_limit,
4214 leak_coefficient,
4215 0.0, 0, refractory_period,
4218 excitability,
4219 consecutive_fire_limit,
4220 snooze_length,
4221 mp_charge_accumulation,
4222 )
4223 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
4224 };
4225
4226 trace!(
4227 target: "feagi-bdu",
4228 "Created {} neurons for area {} via NPU",
4229 neuron_count,
4230 cortical_id.as_base_64()
4231 );
4232
4233 {
4236 let mut cache = self.cached_neuron_counts_per_area.write();
4237 cache
4238 .entry(*cortical_id)
4239 .or_insert_with(|| AtomicUsize::new(0))
4240 .store(neuron_count as usize, Ordering::Relaxed);
4241 }
4242
4243 let state_manager = StateManager::instance();
4245 let state_manager = state_manager.read();
4246 state_manager
4247 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
4248
4249 self.cached_neuron_count
4251 .fetch_add(neuron_count as usize, Ordering::Relaxed);
4252
4253 let state_manager = StateManager::instance();
4255 let state_manager = state_manager.read();
4256 let core_state = state_manager.get_core_state();
4257 core_state.add_neuron_count(neuron_count);
4258 core_state.add_regular_neuron_count(neuron_count);
4259
4260 if let Some(npu) = &self.npu {
4262 if let Ok(mut npl) = npu.lock() {
4263 if let Some(v) = area.properties.get("rate_modulated_leak") {
4264 use crate::models::CorticalAreaExt;
4265 let idxs: Vec<usize> = npl
4266 .get_neurons_in_cortical_area(*cortical_idx)
4267 .into_iter()
4268 .map(|id| id as usize)
4269 .collect();
4270 npl.sync_rate_modulated_leak_from_cortical_property(
4271 *cortical_idx,
4272 v,
4273 area.leak_coefficient(),
4274 idxs,
4275 );
4276 } else {
4277 npl.remove_rate_modulated_leak(*cortical_idx);
4278 }
4279 }
4280 }
4281
4282 Ok(neuron_count)
4290 }
4291
4292 #[allow(clippy::too_many_arguments)]
4316 pub fn add_neuron(
4317 &mut self,
4318 cortical_id: &CorticalID,
4319 x: u32,
4320 y: u32,
4321 z: u32,
4322 firing_threshold: f32,
4323 firing_threshold_limit: f32,
4324 leak_coefficient: f32,
4325 resting_potential: f32,
4326 neuron_type: u8,
4327 refractory_period: u16,
4328 excitability: f32,
4329 consecutive_fire_limit: u16,
4330 snooze_length: u16,
4331 mp_charge_accumulation: bool,
4332 ) -> BduResult<u64> {
4333 if !self.cortical_areas.contains_key(cortical_id) {
4335 return Err(BduError::InvalidArea(format!(
4336 "Cortical area {} not found",
4337 cortical_id
4338 )));
4339 }
4340
4341 let cortical_idx = *self
4342 .cortical_id_to_idx
4343 .get(cortical_id)
4344 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
4345
4346 let npu = self
4348 .npu
4349 .as_ref()
4350 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4351
4352 let mut npu_lock = npu
4353 .lock()
4354 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4355
4356 let neuron_id = npu_lock
4358 .add_neuron(
4359 firing_threshold,
4360 firing_threshold_limit,
4361 leak_coefficient,
4362 resting_potential,
4363 neuron_type as i32,
4364 refractory_period,
4365 excitability,
4366 consecutive_fire_limit,
4367 snooze_length,
4368 mp_charge_accumulation,
4369 cortical_idx,
4370 x,
4371 y,
4372 z,
4373 )
4374 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
4375
4376 trace!(
4377 target: "feagi-bdu",
4378 "Created neuron {} in area {} at ({}, {}, {})",
4379 neuron_id.0,
4380 cortical_id,
4381 x,
4382 y,
4383 z
4384 );
4385
4386 let state_manager = StateManager::instance();
4388 let state_manager = state_manager.read();
4389 let core_state = state_manager.get_core_state();
4390 core_state.add_neuron_count(1);
4391 core_state.add_regular_neuron_count(1);
4392 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4393
4394 Ok(neuron_id.0 as u64)
4395 }
4396
4397 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
4408 let npu = self
4410 .npu
4411 .as_ref()
4412 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4413
4414 let mut npu_lock = npu
4415 .lock()
4416 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4417
4418 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
4419 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
4420
4421 let deleted = npu_lock.delete_neuron(neuron_id as u32);
4422
4423 if deleted {
4424 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
4425
4426 let state_manager = StateManager::instance();
4428 let state_manager = state_manager.read();
4429 let core_state = state_manager.get_core_state();
4430 core_state.subtract_neuron_count(1);
4431 core_state.subtract_regular_neuron_count(1);
4432 if let Some(cortical_id) = cortical_id {
4433 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4434 }
4435
4436 }
4440
4441 Ok(deleted)
4442 }
4443
4444 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
4458 let src_area = self
4460 .cortical_areas
4461 .get(src_cortical_id)
4462 .ok_or_else(|| {
4463 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
4464 })?
4465 .clone();
4466
4467 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
4469 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
4470 _ => return Ok(0), };
4472
4473 let src_cortical_idx = *self
4474 .cortical_id_to_idx
4475 .get(src_cortical_id)
4476 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
4477
4478 let mut total_synapses = 0u32;
4479 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
4483 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
4485 Ok(id) => id,
4486 Err(_) => {
4487 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
4488 continue;
4489 }
4490 };
4491
4492 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
4494 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
4495 continue;
4496 }
4497
4498 let synapse_count =
4500 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
4501 total_synapses += synapse_count as u32;
4502
4503 upstream_updates.push((dst_cortical_id, src_cortical_idx));
4506 }
4507
4508 for (dst_id, src_idx) in upstream_updates {
4510 self.add_upstream_area(&dst_id, src_idx);
4511 }
4512
4513 trace!(
4514 target: "feagi-bdu",
4515 "Created {} synapses for area {} via NPU",
4516 total_synapses,
4517 src_cortical_id
4518 );
4519
4520 if total_synapses > 0 {
4523 let mut cache = self.cached_synapse_counts_per_area.write();
4524 cache
4525 .entry(*src_cortical_id)
4526 .or_insert_with(|| AtomicUsize::new(0))
4527 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4528 }
4529
4530 self.cached_synapse_count
4532 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4533
4534 if total_synapses > 0 {
4536 let state_manager = StateManager::instance();
4537 let state_manager = state_manager.read();
4538 let core_state = state_manager.get_core_state();
4539 core_state.add_synapse_count(total_synapses);
4540 }
4541
4542 Ok(total_synapses)
4543 }
4544
4545 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4564 if let Some(ref npu) = self.npu {
4565 if let Ok(npu_lock) = npu.lock() {
4566 npu_lock.is_neuron_valid(neuron_id as u32)
4568 } else {
4569 false
4570 }
4571 } else {
4572 false
4573 }
4574 }
4575
4576 pub fn get_neuron_count(&self) -> usize {
4588 if let Some(ref npu) = self.npu {
4590 if let Ok(npu_lock) = npu.try_lock() {
4591 let fresh_count = npu_lock.get_neuron_count();
4592 self.cached_neuron_count
4593 .store(fresh_count, Ordering::Relaxed);
4594 }
4595 }
4597
4598 self.cached_neuron_count.load(Ordering::Relaxed)
4600 }
4601
4602 pub fn update_cached_neuron_count(&self) {
4608 if let Some(ref npu) = self.npu {
4609 if let Ok(npu_lock) = npu.try_lock() {
4610 let count = npu_lock.get_neuron_count();
4611 self.cached_neuron_count.store(count, Ordering::Relaxed);
4612 }
4613 }
4614 }
4615
4616 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4620 let npu = self.npu.as_ref()?;
4621 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4622 let npu_lock = npu.lock().ok()?;
4623 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4624 drop(npu_lock);
4625
4626 let mut cache = self.cached_neuron_counts_per_area.write();
4627 cache
4628 .entry(*cortical_id)
4629 .or_insert_with(|| AtomicUsize::new(0))
4630 .store(count, Ordering::Relaxed);
4631
4632 let state_manager = StateManager::instance();
4634 let state_manager = state_manager.read();
4635 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4636
4637 self.update_cached_neuron_count();
4638
4639 Some(count)
4640 }
4641
4642 pub fn get_synapse_count(&self) -> usize {
4654 if let Some(ref npu) = self.npu {
4656 if let Ok(npu_lock) = npu.try_lock() {
4657 let fresh_count = npu_lock.get_synapse_count();
4658 self.cached_synapse_count
4659 .store(fresh_count, Ordering::Relaxed);
4660 }
4661 }
4663
4664 self.cached_synapse_count.load(Ordering::Relaxed)
4666 }
4667
4668 pub fn update_cached_synapse_count(&self) {
4674 if let Some(ref npu) = self.npu {
4675 if let Ok(npu_lock) = npu.try_lock() {
4676 let count = npu_lock.get_synapse_count();
4677 self.cached_synapse_count.store(count, Ordering::Relaxed);
4678 }
4679 }
4680 }
4681
4682 pub fn update_all_cached_stats(&self) {
4688 self.update_cached_neuron_count();
4689 self.update_cached_synapse_count();
4690 }
4691
4692 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4703 #[cfg(feature = "plasticity")]
4708 {
4709 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4710 return (0, 0, 0);
4711 }
4712 }
4713 if let Some(ref npu) = self.npu {
4714 if let Ok(npu_lock) = npu.lock() {
4715 npu_lock
4716 .get_neuron_coordinates(neuron_id as u32)
4717 .unwrap_or((0, 0, 0))
4718 } else {
4719 (0, 0, 0)
4720 }
4721 } else {
4722 (0, 0, 0)
4723 }
4724 }
4725
4726 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
4737 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
4738 }
4739
4740 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
4746 #[cfg(feature = "plasticity")]
4747 {
4748 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4749 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
4750 }
4751 }
4752 if let Some(ref npu) = self.npu {
4753 if let Ok(npu_lock) = npu.lock() {
4754 npu_lock.get_neuron_cortical_area(neuron_id as u32)
4755 } else {
4756 None
4757 }
4758 } else {
4759 None
4760 }
4761 }
4762
4763 #[cfg(feature = "plasticity")]
4765 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
4766 let exec = self.get_plasticity_executor()?;
4767 let guard = exec.lock().ok()?;
4768 guard
4769 .memory_neuron_detail(neuron_id)
4770 .map(|d| d.cortical_area_idx)
4771 }
4772
4773 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4784 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4786 Some(idx) => *idx,
4787 None => return Vec::new(),
4788 };
4789
4790 if let Some(ref npu) = self.npu {
4791 if let Ok(npu_lock) = npu.lock() {
4792 npu_lock
4794 .get_neurons_in_cortical_area(cortical_idx)
4795 .into_iter()
4796 .map(|id| id as u64)
4797 .collect()
4798 } else {
4799 Vec::new()
4800 }
4801 } else {
4802 Vec::new()
4803 }
4804 }
4805
4806 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4817 if let Some(ref npu) = self.npu {
4818 if let Ok(npu_lock) = npu.lock() {
4819 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4820 } else {
4821 Vec::new()
4822 }
4823 } else {
4824 Vec::new()
4825 }
4826 }
4827
4828 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4839 if let Some(ref npu) = self.npu {
4840 if let Ok(npu_lock) = npu.lock() {
4841 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4842 } else {
4843 Vec::new()
4844 }
4845 } else {
4846 Vec::new()
4847 }
4848 }
4849
4850 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4876 let cache = self.cached_neuron_counts_per_area.read();
4878 let base_count = cache
4879 .get(cortical_id)
4880 .map(|count| count.load(Ordering::Relaxed))
4881 .unwrap_or(0);
4882
4883 let memory_count = self
4885 .cortical_areas
4886 .get(cortical_id)
4887 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4888 .and_then(|_| {
4889 StateManager::instance()
4890 .try_read()
4891 .and_then(|state_manager| {
4892 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4893 })
4894 })
4895 .map(|stats| stats.neuron_count)
4896 .unwrap_or(0);
4897
4898 base_count.saturating_add(memory_count)
4899 }
4900
4901 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4908 let mut result = Vec::new();
4909
4910 for cortical_id in self.cortical_areas.keys() {
4911 let count = self.get_neuron_count_in_area(cortical_id);
4912 if count > 0 {
4913 result.push((cortical_id.to_string(), count));
4914 }
4915 }
4916
4917 result
4918 }
4919
4920 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4931 self.get_neuron_count_in_area(cortical_id) > 0
4932 }
4933
4934 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4950 let cache = self.cached_synapse_counts_per_area.read();
4952 cache
4953 .get(cortical_id)
4954 .map(|count| count.load(Ordering::Relaxed))
4955 .unwrap_or(0)
4956 }
4957
4958 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4968 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4969 return 0;
4970 }
4971
4972 if let Some(state_manager) = StateManager::instance().try_read() {
4973 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4974 return stats.incoming_synapse_count;
4975 }
4976 }
4977
4978 0
4979 }
4980
4981 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4991 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4992 return 0;
4993 }
4994
4995 if let Some(state_manager) = StateManager::instance().try_read() {
4996 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4997 return stats.outgoing_synapse_count;
4998 }
4999 }
5000
5001 0
5002 }
5003
5004 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
5016 let synapses = self.get_outgoing_synapses(source_neuron_id);
5017 synapses
5018 .iter()
5019 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
5020 }
5021
5022 pub fn get_connection_weight(
5034 &self,
5035 source_neuron_id: u64,
5036 target_neuron_id: u64,
5037 ) -> Option<f32> {
5038 let synapses = self.get_outgoing_synapses(source_neuron_id);
5039 synapses
5040 .iter()
5041 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
5042 .map(|(_, weight, _, _)| *weight)
5043 }
5044
5045 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
5056 let neurons = self.get_neurons_in_area(cortical_id);
5057 let neuron_count = neurons.len();
5058
5059 if neuron_count == 0 {
5060 return (0, 0, 0.0);
5061 }
5062
5063 let mut total_synapses = 0;
5064 for neuron_id in neurons {
5065 total_synapses += self.get_outgoing_synapses(neuron_id).len();
5066 }
5067
5068 let avg_synapses = total_synapses as f32 / neuron_count as f32;
5069
5070 (neuron_count, total_synapses, avg_synapses)
5071 }
5072
5073 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
5084 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
5085 self.cortical_idx_to_id.get(&cortical_idx).copied()
5086 }
5087
5088 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
5099 let area = match self.cortical_areas.get(cortical_id) {
5100 Some(a) => a,
5101 None => return 0.0,
5102 };
5103
5104 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5105 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5106
5107 if volume == 0 {
5108 return 0.0;
5109 }
5110
5111 neuron_count as f32 / volume as f32
5112 }
5113
5114 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
5121 let mut stats = Vec::new();
5122
5123 for cortical_id in self.cortical_areas.keys() {
5124 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5125 let synapse_count = self.get_synapse_count_in_area(cortical_id);
5126 let density = self.get_neuron_density(cortical_id);
5127
5128 stats.push((
5129 cortical_id.to_string(),
5130 neuron_count,
5131 synapse_count,
5132 density,
5133 ));
5134 }
5135
5136 stats
5137 }
5138
5139 pub fn get_config(&self) -> &ConnectomeConfig {
5145 &self.config
5146 }
5147
5148 pub fn set_config(&mut self, config: ConnectomeConfig) {
5150 self.config = config;
5151 }
5152
5153 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
5176 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
5177
5178 use feagi_structures::genomic::cortical_area::{
5179 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
5180 };
5181
5182 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
5184 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
5185 })?;
5186
5187 let core_position = (0, 0, 0).into();
5189
5190 let death_id = CoreCorticalType::Death.to_cortical_id();
5192 if !self.cortical_areas.contains_key(&death_id) {
5193 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
5194 let death_area = CorticalArea::new(
5195 death_id,
5196 0, "_death".to_string(),
5198 core_dimensions,
5199 core_position,
5200 CorticalAreaType::Core(CoreCorticalType::Death),
5201 )
5202 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
5203 match self.add_cortical_area(death_area) {
5204 Ok(idx) => {
5205 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
5206 }
5207 Err(e) => {
5208 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
5209 return Err(e);
5210 }
5211 }
5212 } else {
5213 info!(target: "feagi-bdu", " ✓ _death area already exists");
5214 }
5215
5216 let power_id = CoreCorticalType::Power.to_cortical_id();
5218 if !self.cortical_areas.contains_key(&power_id) {
5219 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
5220 let power_area = CorticalArea::new(
5221 power_id,
5222 1, "_power".to_string(),
5224 core_dimensions,
5225 core_position,
5226 CorticalAreaType::Core(CoreCorticalType::Power),
5227 )
5228 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
5229 match self.add_cortical_area(power_area) {
5230 Ok(idx) => {
5231 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
5232 }
5233 Err(e) => {
5234 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
5235 return Err(e);
5236 }
5237 }
5238 } else {
5239 info!(target: "feagi-bdu", " ✓ _power area already exists");
5240 }
5241
5242 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
5244 let pain_id = CoreCorticalType::Pain.to_cortical_id();
5245 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
5246 let fear_id = CoreCorticalType::Fear.to_cortical_id();
5247 let hope_id = CoreCorticalType::Hope.to_cortical_id();
5248 if !self.cortical_areas.contains_key(&fatigue_id) {
5249 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
5250 let fatigue_area = CorticalArea::new(
5251 fatigue_id,
5252 2, "_fatigue".to_string(),
5254 core_dimensions,
5255 core_position,
5256 CorticalAreaType::Core(CoreCorticalType::Fatigue),
5257 )
5258 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
5259 match self.add_cortical_area(fatigue_area) {
5260 Ok(idx) => {
5261 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
5262 }
5263 Err(e) => {
5264 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
5265 return Err(e);
5266 }
5267 }
5268 } else {
5269 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
5270 }
5271
5272 if !self.cortical_areas.contains_key(&pain_id) {
5274 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
5275 let pain_area = CorticalArea::new(
5276 pain_id,
5277 3, "_pain".to_string(),
5279 core_dimensions,
5280 core_position,
5281 CorticalAreaType::Core(CoreCorticalType::Pain),
5282 )
5283 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
5284 match self.add_cortical_area(pain_area) {
5285 Ok(idx) => {
5286 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
5287 }
5288 Err(e) => {
5289 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
5290 return Err(e);
5291 }
5292 }
5293 } else {
5294 info!(target: "feagi-bdu", " ✓ _pain area already exists");
5295 }
5296
5297 if !self.cortical_areas.contains_key(&pleasure_id) {
5299 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
5300 let pleasure_area = CorticalArea::new(
5301 pleasure_id,
5302 4, "_pleasure".to_string(),
5304 core_dimensions,
5305 core_position,
5306 CorticalAreaType::Core(CoreCorticalType::Pleasure),
5307 )
5308 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
5309 match self.add_cortical_area(pleasure_area) {
5310 Ok(idx) => {
5311 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
5312 }
5313 Err(e) => {
5314 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
5315 return Err(e);
5316 }
5317 }
5318 } else {
5319 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
5320 }
5321
5322 if !self.cortical_areas.contains_key(&fear_id) {
5324 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
5325 let fear_area = CorticalArea::new(
5326 fear_id,
5327 5, "_fear".to_string(),
5329 core_dimensions,
5330 core_position,
5331 CorticalAreaType::Core(CoreCorticalType::Fear),
5332 )
5333 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
5334 match self.add_cortical_area(fear_area) {
5335 Ok(idx) => {
5336 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
5337 }
5338 Err(e) => {
5339 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
5340 return Err(e);
5341 }
5342 }
5343 } else {
5344 info!(target: "feagi-bdu", " ✓ _fear area already exists");
5345 }
5346
5347 if !self.cortical_areas.contains_key(&hope_id) {
5349 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
5350 let hope_area = CorticalArea::new(
5351 hope_id,
5352 6, "_hope".to_string(),
5354 core_dimensions,
5355 core_position,
5356 CorticalAreaType::Core(CoreCorticalType::Hope),
5357 )
5358 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
5359 match self.add_cortical_area(hope_area) {
5360 Ok(idx) => {
5361 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
5362 }
5363 Err(e) => {
5364 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
5365 return Err(e);
5366 }
5367 }
5368 } else {
5369 info!(target: "feagi-bdu", " ✓ _hope area already exists");
5370 }
5371
5372 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
5373 Ok(())
5374 }
5375
5376 #[deprecated(
5393 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
5394 )]
5395 #[allow(deprecated)]
5396 pub fn save_genome_to_json(
5397 &self,
5398 genome_id: Option<String>,
5399 genome_title: Option<String>,
5400 ) -> BduResult<String> {
5401 let mut brain_regions_with_parents = std::collections::HashMap::new();
5403
5404 for region_id in self.brain_regions.get_all_region_ids() {
5405 if let Some(region) = self.brain_regions.get_region(region_id) {
5406 let parent_id = self
5407 .brain_regions
5408 .get_parent(region_id)
5409 .map(|s| s.to_string());
5410 brain_regions_with_parents
5411 .insert(region_id.to_string(), (region.clone(), parent_id));
5412 }
5413 }
5414
5415 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
5417 &self.cortical_areas,
5418 &brain_regions_with_parents,
5419 genome_id,
5420 genome_title,
5421 )?)
5422 }
5423
5424 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
5455 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
5456
5457 self.cortical_areas.clear();
5459 self.cortical_id_to_idx.clear();
5460 self.cortical_idx_to_id.clear();
5461 self.next_cortical_idx = 7;
5463 info!("🔧 [BRAIN-RESET] Cortical mapping cleared, next_cortical_idx reset to 7 (reserves 0=_death, 1=_power, 2=_fatigue, 3=_pain, 4=_pleasure, 5=_fear, 6=_hope)");
5464
5465 self.brain_regions = BrainRegionHierarchy::new();
5467
5468 if let Some(ref npu) = self.npu {
5470 let mut npu_lock = npu
5471 .lock()
5472 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5473 npu_lock
5474 .reset_for_new_genome()
5475 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
5476 }
5477
5478 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
5479 Ok(())
5480 }
5481
5482 pub fn resize_for_genome(
5492 &mut self,
5493 genome: &feagi_evolutionary::RuntimeGenome,
5494 ) -> BduResult<()> {
5495 self.morphology_registry = genome.morphologies.clone();
5497 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
5498
5499 let required_neurons = genome.stats.innate_neuron_count;
5501 let required_synapses = genome.stats.innate_synapse_count;
5502
5503 info!(target: "feagi-bdu",
5504 "Genome requires: {} neurons, {} synapses",
5505 required_neurons,
5506 required_synapses
5507 );
5508
5509 let mut total_voxels = 0;
5511 for area in genome.cortical_areas.values() {
5512 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5513 }
5514
5515 info!(target: "feagi-bdu",
5516 "Genome has {} cortical areas with {} total voxels",
5517 genome.cortical_areas.len(),
5518 total_voxels
5519 );
5520
5521 Ok(())
5526 }
5527
5528 pub fn create_synapse(
5547 &mut self,
5548 source_neuron_id: u64,
5549 target_neuron_id: u64,
5550 weight: f32,
5551 psp: f32,
5552 synapse_type: u8,
5553 ) -> BduResult<()> {
5554 let npu = self
5556 .npu
5557 .as_ref()
5558 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5559
5560 let mut npu_lock = npu
5561 .lock()
5562 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5563
5564 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5566 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5567
5568 if !source_exists {
5569 return Err(BduError::InvalidNeuron(format!(
5570 "Source neuron {} not found",
5571 source_neuron_id
5572 )));
5573 }
5574 if !target_exists {
5575 return Err(BduError::InvalidNeuron(format!(
5576 "Target neuron {} not found",
5577 target_neuron_id
5578 )));
5579 }
5580
5581 let syn_type = if synapse_type == 0 {
5583 feagi_npu_neural::synapse::SynapseType::Excitatory
5584 } else {
5585 feagi_npu_neural::synapse::SynapseType::Inhibitory
5586 };
5587
5588 let synapse_idx = npu_lock
5589 .add_synapse(
5590 NeuronId(source_neuron_id as u32),
5591 NeuronId(target_neuron_id as u32),
5592 feagi_npu_neural::types::SynapticWeight(weight),
5593 feagi_npu_neural::types::SynapticPsp(psp),
5594 syn_type,
5595 0,
5596 1,
5597 )
5598 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
5599
5600 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
5601 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
5602
5603 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5604 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5605 let source_cortical_id =
5606 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5607 let target_cortical_id =
5608 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5609
5610 let state_manager = StateManager::instance();
5611 let state_manager = state_manager.read();
5612 let core_state = state_manager.get_core_state();
5613 core_state.add_synapse_count(1);
5614 if let Some(cortical_id) = source_cortical_id {
5615 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5616 }
5617 if let Some(cortical_id) = target_cortical_id {
5618 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5619 }
5620
5621 Ok(())
5626 }
5627
5628 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
5631 if let Some(ref npu) = self.npu {
5632 if let Ok(mut npu_lock) = npu.lock() {
5633 let mut psp_uniform_flags = ahash::AHashMap::new();
5635 let mut mp_driven_psp_flags = ahash::AHashMap::new();
5636 let mut postsynaptic_current_flags = ahash::AHashMap::new();
5637 let mut degeneration_flags = ahash::AHashMap::new();
5638
5639 for (cortical_id, area) in &self.cortical_areas {
5640 let default_psp_uniform = *cortical_id
5643 == CoreCorticalType::Power.to_cortical_id()
5644 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
5645 let psp_uniform = area
5646 .get_property("psp_uniform_distribution")
5647 .and_then(|v| v.as_bool())
5648 .unwrap_or(default_psp_uniform);
5649 psp_uniform_flags.insert(*cortical_id, psp_uniform);
5650
5651 let mp_driven_psp = area
5653 .get_property("mp_driven_psp")
5654 .and_then(|v| v.as_bool())
5655 .unwrap_or(false);
5656 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
5657
5658 let postsynaptic_current = area
5660 .get_property("postsynaptic_current")
5661 .and_then(|v| v.as_f64())
5662 .unwrap_or(1.0) as f32;
5663 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
5664
5665 let degeneration = area
5667 .get_property("degeneration")
5668 .and_then(|v| v.as_f64())
5669 .unwrap_or(0.0) as f32;
5670 if degeneration > 0.0 {
5671 degeneration_flags.insert(*cortical_id, degeneration);
5672 }
5673 }
5674
5675 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
5677 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5678 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
5679 npu_lock.set_degeneration_flags(degeneration_flags);
5680
5681 trace!(
5682 target: "feagi-bdu",
5683 "Synchronized cortical area flags to NPU ({} areas)",
5684 self.cortical_areas.len()
5685 );
5686 }
5687 }
5688
5689 Ok(())
5690 }
5691
5692 pub fn get_synapse(
5704 &self,
5705 source_neuron_id: u64,
5706 target_neuron_id: u64,
5707 ) -> Option<(f32, f32, u8)> {
5708 let npu = self.npu.as_ref()?;
5710 let npu_lock = npu.lock().ok()?;
5711
5712 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
5715
5716 for (source_id, weight, psp, synapse_type) in incoming {
5718 if source_id == source_neuron_id as u32 {
5719 return Some((weight, psp, synapse_type));
5720 }
5721 }
5722
5723 None
5724 }
5725
5726 pub fn update_synapse_weight(
5739 &mut self,
5740 source_neuron_id: u64,
5741 target_neuron_id: u64,
5742 new_weight: f32,
5743 ) -> BduResult<()> {
5744 let npu = self
5746 .npu
5747 .as_ref()
5748 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5749
5750 let mut npu_lock = npu
5751 .lock()
5752 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5753
5754 let updated = npu_lock.update_synapse_weight(
5756 NeuronId(source_neuron_id as u32),
5757 NeuronId(target_neuron_id as u32),
5758 feagi_npu_neural::types::SynapticWeight(new_weight),
5759 );
5760
5761 if updated {
5762 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
5763 Ok(())
5764 } else {
5765 Err(BduError::InvalidSynapse(format!(
5766 "Synapse {} -> {} not found",
5767 source_neuron_id, target_neuron_id
5768 )))
5769 }
5770 }
5771
5772 pub fn remove_synapse(
5784 &mut self,
5785 source_neuron_id: u64,
5786 target_neuron_id: u64,
5787 ) -> BduResult<bool> {
5788 let npu = self
5790 .npu
5791 .as_ref()
5792 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5793
5794 let mut npu_lock = npu
5795 .lock()
5796 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5797
5798 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5799 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5800 let source_cortical_id =
5801 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5802 let target_cortical_id =
5803 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5804
5805 let removed = npu_lock.remove_synapse(
5807 NeuronId(source_neuron_id as u32),
5808 NeuronId(target_neuron_id as u32),
5809 );
5810
5811 if removed {
5812 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
5813
5814 let state_manager = StateManager::instance();
5816 let state_manager = state_manager.read();
5817 let core_state = state_manager.get_core_state();
5818 core_state.subtract_synapse_count(1);
5819 if let Some(cortical_id) = source_cortical_id {
5820 state_manager
5821 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5822 }
5823 if let Some(cortical_id) = target_cortical_id {
5824 state_manager
5825 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5826 }
5827 }
5828
5829 Ok(removed)
5830 }
5831
5832 pub fn batch_create_neurons(
5850 &mut self,
5851 cortical_id: &CorticalID,
5852 neurons: Vec<NeuronData>,
5853 ) -> BduResult<Vec<u64>> {
5854 let npu = self
5856 .npu
5857 .as_ref()
5858 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5859
5860 let mut npu_lock = npu
5861 .lock()
5862 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5863
5864 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5866 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5867 })?;
5868 let cortical_idx = area.cortical_idx;
5869
5870 let count = neurons.len();
5871
5872 let mut x_coords = Vec::with_capacity(count);
5874 let mut y_coords = Vec::with_capacity(count);
5875 let mut z_coords = Vec::with_capacity(count);
5876 let mut firing_thresholds = Vec::with_capacity(count);
5877 let mut threshold_limits = Vec::with_capacity(count);
5878 let mut leak_coeffs = Vec::with_capacity(count);
5879 let mut resting_potentials = Vec::with_capacity(count);
5880 let mut neuron_types = Vec::with_capacity(count);
5881 let mut refractory_periods = Vec::with_capacity(count);
5882 let mut excitabilities = Vec::with_capacity(count);
5883 let mut consec_fire_limits = Vec::with_capacity(count);
5884 let mut snooze_lengths = Vec::with_capacity(count);
5885 let mut mp_accums = Vec::with_capacity(count);
5886 let mut cortical_areas = Vec::with_capacity(count);
5887
5888 for (
5889 x,
5890 y,
5891 z,
5892 threshold,
5893 threshold_limit,
5894 leak,
5895 resting,
5896 ntype,
5897 refract,
5898 excit,
5899 consec_limit,
5900 snooze,
5901 mp_accum,
5902 ) in neurons
5903 {
5904 x_coords.push(x);
5905 y_coords.push(y);
5906 z_coords.push(z);
5907 firing_thresholds.push(threshold);
5908 threshold_limits.push(threshold_limit);
5909 leak_coeffs.push(leak);
5910 resting_potentials.push(resting);
5911 neuron_types.push(ntype);
5912 refractory_periods.push(refract);
5913 excitabilities.push(excit);
5914 consec_fire_limits.push(consec_limit);
5915 snooze_lengths.push(snooze);
5916 mp_accums.push(mp_accum);
5917 cortical_areas.push(cortical_idx);
5918 }
5919
5920 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5922
5923 let firing_thresholds_t = firing_thresholds;
5928 let threshold_limits_t = threshold_limits;
5929 let resting_potentials_t = resting_potentials;
5930 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5931 firing_thresholds_t,
5932 threshold_limits_t,
5933 leak_coeffs,
5934 resting_potentials_t,
5935 neuron_types,
5936 refractory_periods,
5937 excitabilities,
5938 consec_fire_limits,
5939 snooze_lengths,
5940 mp_accums,
5941 cortical_areas,
5942 x_coords,
5943 y_coords,
5944 z_coords,
5945 );
5946
5947 let mut neuron_ids = Vec::with_capacity(count);
5949 for i in 0..neurons_created {
5950 neuron_ids.push((first_neuron_id + i) as u64);
5951 }
5952
5953 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5954
5955 let state_manager = StateManager::instance();
5957 let state_manager = state_manager.read();
5958 let core_state = state_manager.get_core_state();
5959 core_state.add_neuron_count(neurons_created);
5960 core_state.add_regular_neuron_count(neurons_created);
5961 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5962
5963 {
5965 let mut cache = self.cached_neuron_counts_per_area.write();
5966 cache
5967 .entry(*cortical_id)
5968 .or_insert_with(|| AtomicUsize::new(0))
5969 .fetch_add(count, Ordering::Relaxed);
5970 }
5971
5972 Ok(neuron_ids)
5973 }
5974
5975 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5986 let npu = self
5988 .npu
5989 .as_ref()
5990 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5991
5992 let mut npu_lock = npu
5993 .lock()
5994 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5995
5996 let mut deleted_count = 0;
5997 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5998 std::collections::HashMap::new();
5999
6000 for neuron_id in neuron_ids {
6003 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
6004 let cortical_id =
6005 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6006
6007 if npu_lock.delete_neuron(neuron_id as u32) {
6008 deleted_count += 1;
6009 if let Some(cortical_id) = cortical_id {
6010 let key = cortical_id.as_base_64();
6011 *per_area_deleted.entry(key).or_insert(0) += 1;
6012 }
6013 }
6014 }
6015
6016 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
6017
6018 if deleted_count > 0 {
6020 let state_manager = StateManager::instance();
6021 let state_manager = state_manager.read();
6022 let core_state = state_manager.get_core_state();
6023 core_state.subtract_neuron_count(deleted_count as u32);
6024 core_state.subtract_regular_neuron_count(deleted_count as u32);
6025 for (cortical_id, count) in per_area_deleted {
6026 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
6027 }
6028 }
6029
6030 Ok(deleted_count)
6037 }
6038
6039 pub fn update_neuron_properties(
6058 &mut self,
6059 neuron_id: u64,
6060 firing_threshold: Option<f32>,
6061 leak_coefficient: Option<f32>,
6062 resting_potential: Option<f32>,
6063 excitability: Option<f32>,
6064 ) -> BduResult<()> {
6065 let npu = self
6067 .npu
6068 .as_ref()
6069 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6070
6071 let mut npu_lock = npu
6072 .lock()
6073 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6074
6075 let neuron_id_u32 = neuron_id as u32;
6076
6077 let mut updated = false;
6079
6080 if let Some(threshold) = firing_threshold {
6082 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
6083 updated = true;
6084 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
6085 } else if !updated {
6086 return Err(BduError::InvalidNeuron(format!(
6087 "Neuron {} not found",
6088 neuron_id
6089 )));
6090 }
6091 }
6092
6093 if let Some(leak) = leak_coefficient {
6094 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
6095 updated = true;
6096 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
6097 } else if !updated {
6098 return Err(BduError::InvalidNeuron(format!(
6099 "Neuron {} not found",
6100 neuron_id
6101 )));
6102 }
6103 }
6104
6105 if let Some(resting) = resting_potential {
6106 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
6107 updated = true;
6108 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
6109 } else if !updated {
6110 return Err(BduError::InvalidNeuron(format!(
6111 "Neuron {} not found",
6112 neuron_id
6113 )));
6114 }
6115 }
6116
6117 if let Some(excit) = excitability {
6118 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
6119 updated = true;
6120 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
6121 } else if !updated {
6122 return Err(BduError::InvalidNeuron(format!(
6123 "Neuron {} not found",
6124 neuron_id
6125 )));
6126 }
6127 }
6128
6129 if !updated {
6130 return Err(BduError::Internal(
6131 "No properties provided for update".to_string(),
6132 ));
6133 }
6134
6135 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
6136
6137 Ok(())
6138 }
6139
6140 pub fn set_neuron_firing_threshold(
6152 &mut self,
6153 neuron_id: u64,
6154 new_threshold: f32,
6155 ) -> BduResult<()> {
6156 let npu = self
6158 .npu
6159 .as_ref()
6160 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6161
6162 let mut npu_lock = npu
6163 .lock()
6164 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6165
6166 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
6168 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
6169 Ok(())
6170 } else {
6171 Err(BduError::InvalidNeuron(format!(
6172 "Neuron {} not found",
6173 neuron_id
6174 )))
6175 }
6176 }
6177
6178 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
6193 self.cortical_areas
6194 .values()
6195 .find(|area| area.name == name)
6196 .cloned()
6197 }
6198
6199 pub fn resize_cortical_area(
6216 &mut self,
6217 cortical_id: &CorticalID,
6218 new_dimensions: CorticalAreaDimensions,
6219 ) -> BduResult<()> {
6220 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
6222 return Err(BduError::InvalidArea(format!(
6223 "Invalid dimensions: {:?} (all must be > 0)",
6224 new_dimensions
6225 )));
6226 }
6227
6228 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
6230 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6231 })?;
6232
6233 let old_dimensions = area.dimensions;
6234 area.dimensions = new_dimensions;
6235
6236 info!(target: "feagi-bdu",
6240 "Resized cortical area {} from {:?} to {:?}",
6241 cortical_id,
6242 old_dimensions,
6243 new_dimensions
6244 );
6245
6246 self.refresh_cortical_area_hashes(false, true);
6247
6248 Ok(())
6249 }
6250
6251 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
6262 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
6263 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6264 })?;
6265
6266 Ok(region
6268 .cortical_areas
6269 .iter()
6270 .map(|id| id.as_base_64())
6271 .collect())
6272 }
6273
6274 pub fn update_brain_region(
6287 &mut self,
6288 region_id: &str,
6289 new_name: Option<String>,
6290 new_description: Option<String>,
6291 ) -> BduResult<()> {
6292 let region = self
6293 .brain_regions
6294 .get_region_mut(region_id)
6295 .ok_or_else(|| {
6296 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6297 })?;
6298
6299 if let Some(name) = new_name {
6300 region.name = name;
6301 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
6302 }
6303
6304 if let Some(desc) = new_description {
6305 region
6307 .properties
6308 .insert("description".to_string(), serde_json::json!(desc));
6309 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
6310 }
6311
6312 info!(target: "feagi-bdu","Updated brain region {}", region_id);
6313
6314 self.refresh_brain_regions_hash();
6315
6316 Ok(())
6317 }
6318
6319 pub fn update_brain_region_properties(
6333 &mut self,
6334 region_id: &str,
6335 properties: std::collections::HashMap<String, serde_json::Value>,
6336 ) -> BduResult<Option<BrainRegionIoRegistry>> {
6337 use tracing::{debug, info};
6338
6339 let should_recompute_io = properties
6340 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6341 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
6342
6343 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6344 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
6345 {
6346 let region_snapshot = self
6347 .brain_regions
6348 .get_region(region_id)
6349 .ok_or_else(|| {
6350 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6351 })?
6352 .clone();
6353 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
6354 ®ion_snapshot,
6355 &properties,
6356 )?;
6357 crate::region_io_designation::validate_merged_designations_against_connectivity(
6358 self,
6359 ®ion_snapshot,
6360 &merged_in,
6361 &merged_out,
6362 )?;
6363 }
6364
6365 let region = self
6366 .brain_regions
6367 .get_region_mut(region_id)
6368 .ok_or_else(|| {
6369 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6370 })?;
6371
6372 for (key, value) in properties {
6373 match key.as_str() {
6374 "title" | "name" | "region_title" => {
6376 if let Some(name) = value.as_str() {
6377 region.name = name.to_string();
6378 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
6379 }
6380 }
6381 "coordinate_3d" | "coordinates_3d" => {
6382 region
6383 .properties
6384 .insert("coordinate_3d".to_string(), value.clone());
6385 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
6386 }
6387 "coordinate_2d" | "coordinates_2d" => {
6388 region
6389 .properties
6390 .insert("coordinate_2d".to_string(), value.clone());
6391 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
6392 }
6393 "description" => {
6394 region
6395 .properties
6396 .insert("description".to_string(), value.clone());
6397 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
6398 }
6399 "region_type" => {
6400 if let Some(type_str) = value.as_str() {
6401 region.region_type = feagi_structures::genomic::RegionType::Undefined;
6404 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
6405 }
6406 }
6407 _ => {
6409 region.properties.insert(key.clone(), value.clone());
6410 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
6411 }
6412 }
6413 }
6414
6415 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
6416
6417 if should_recompute_io {
6420 let registry = self.recompute_brain_region_io_registry()?;
6421 return Ok(Some(registry));
6422 }
6423
6424 self.refresh_brain_regions_hash();
6428
6429 Ok(None)
6430 }
6431
6432 pub fn get_neuron_by_coordinates(
6450 &self,
6451 cortical_id: &CorticalID,
6452 x: u32,
6453 y: u32,
6454 z: u32,
6455 ) -> Option<u64> {
6456 let area = self.get_cortical_area(cortical_id)?;
6458 let cortical_idx = area.cortical_idx;
6459
6460 let npu = self.npu.as_ref()?;
6462 let npu_lock = npu.lock().ok()?;
6463
6464 npu_lock
6465 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
6466 .map(|id| id as u64)
6467 }
6468
6469 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
6480 let npu = self.npu.as_ref()?;
6481 let npu_lock = npu.lock().ok()?;
6482
6483 let neuron_count = npu_lock.get_neuron_count();
6485 if (neuron_id as usize) >= neuron_count {
6486 return None;
6487 }
6488
6489 Some(
6490 npu_lock
6491 .get_neuron_coordinates(neuron_id as u32)
6492 .unwrap_or((0, 0, 0)),
6493 )
6494 }
6495
6496 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
6507 let npu = self.npu.as_ref()?;
6508 let npu_lock = npu.lock().ok()?;
6509
6510 let neuron_count = npu_lock.get_neuron_count();
6512 if (neuron_id as usize) >= neuron_count {
6513 return None;
6514 }
6515
6516 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
6517
6518 self.cortical_areas
6520 .values()
6521 .find(|area| area.cortical_idx == cortical_idx)
6522 .map(|area| area.cortical_id)
6523 }
6524
6525 pub fn get_neuron_properties(
6536 &self,
6537 neuron_id: u64,
6538 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6539 let npu = self.npu.as_ref()?;
6540 let npu_lock = npu.lock().ok()?;
6541
6542 let neuron_id_u32 = neuron_id as u32;
6543 let idx = neuron_id as usize;
6544
6545 let neuron_count = npu_lock.get_neuron_count();
6547 if idx >= neuron_count {
6548 return None;
6549 }
6550
6551 let mut properties = std::collections::HashMap::new();
6552
6553 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
6555
6556 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
6558 properties.insert("x".to_string(), serde_json::json!(x));
6559 properties.insert("y".to_string(), serde_json::json!(y));
6560 properties.insert("z".to_string(), serde_json::json!(z));
6561
6562 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
6564 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
6565
6566 properties.insert(
6568 "mp_charge_accumulation".to_string(),
6569 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
6570 );
6571 properties.insert(
6572 "neuron_type".to_string(),
6573 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
6574 );
6575 let (mp_drv, psp_uni) = self
6576 .cortical_idx_to_id
6577 .get(&cortical_idx)
6578 .map(|cid| {
6579 (
6580 npu_lock.get_mp_driven_psp_for_cortical(cid),
6581 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
6582 )
6583 })
6584 .unwrap_or((false, false));
6585 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
6586 properties.insert(
6587 "psp_uniform_distribution".to_string(),
6588 serde_json::json!(psp_uni),
6589 );
6590
6591 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
6594 .get_neuron_state(NeuronId(neuron_id_u32))
6595 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
6596 properties.insert(
6597 "consecutive_fire_count".to_string(),
6598 serde_json::json!(consec_count),
6599 );
6600 properties.insert(
6601 "consecutive_fire_limit".to_string(),
6602 serde_json::json!(consec_limit),
6603 );
6604 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
6605 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
6606 properties.insert("threshold".to_string(), serde_json::json!(threshold));
6607 properties.insert(
6608 "refractory_countdown".to_string(),
6609 serde_json::json!(refract_countdown),
6610 );
6611
6612 properties.insert(
6614 "leak_coefficient".to_string(),
6615 serde_json::json!(npu_lock
6616 .get_neuron_property_by_index(idx, "leak_coefficient")
6617 .unwrap_or(0.0)),
6618 );
6619 properties.insert(
6620 "resting_potential".to_string(),
6621 serde_json::json!(npu_lock
6622 .get_neuron_property_by_index(idx, "resting_potential")
6623 .unwrap_or(0.0)),
6624 );
6625 properties.insert(
6626 "excitability".to_string(),
6627 serde_json::json!(npu_lock
6628 .get_neuron_property_by_index(idx, "excitability")
6629 .unwrap_or(0.0)),
6630 );
6631 properties.insert(
6632 "threshold_limit".to_string(),
6633 serde_json::json!(npu_lock
6634 .get_neuron_property_by_index(idx, "threshold_limit")
6635 .unwrap_or(0.0)),
6636 );
6637 properties.insert(
6638 "refractory_period".to_string(),
6639 serde_json::json!(npu_lock
6640 .get_neuron_property_u16_by_index(idx, "refractory_period")
6641 .unwrap_or(0)),
6642 );
6643
6644 Some(properties)
6645 }
6646
6647 pub fn get_neuron_property(
6659 &self,
6660 neuron_id: u64,
6661 property_name: &str,
6662 ) -> Option<serde_json::Value> {
6663 self.get_neuron_properties(neuron_id)?
6664 .get(property_name)
6665 .cloned()
6666 }
6667
6668 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
6679 self.cortical_areas.keys().copied().collect()
6680 }
6681
6682 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
6689 self.cortical_areas
6690 .values()
6691 .map(|area| area.cortical_idx)
6692 .collect()
6693 }
6694
6695 pub fn get_cortical_area_names(&self) -> Vec<String> {
6702 self.cortical_areas
6703 .values()
6704 .map(|area| area.name.clone())
6705 .collect()
6706 }
6707
6708 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
6715 use crate::models::CorticalAreaExt;
6716 self.cortical_areas
6717 .values()
6718 .filter(|area| area.is_input_area())
6719 .map(|area| area.cortical_id)
6720 .collect()
6721 }
6722
6723 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
6730 use crate::models::CorticalAreaExt;
6731 self.cortical_areas
6732 .values()
6733 .filter(|area| area.is_output_area())
6734 .map(|area| area.cortical_id)
6735 .collect()
6736 }
6737
6738 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
6745 self.cortical_areas
6746 .values()
6747 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
6748 (
6749 max_w.max(area.dimensions.width as usize),
6750 max_h.max(area.dimensions.height as usize),
6751 max_d.max(area.dimensions.depth as usize),
6752 )
6753 })
6754 }
6755
6756 pub fn get_cortical_area_properties(
6767 &self,
6768 cortical_id: &CorticalID,
6769 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6770 let area = self.get_cortical_area(cortical_id)?;
6771
6772 let mut properties = std::collections::HashMap::new();
6773 properties.insert(
6774 "cortical_id".to_string(),
6775 serde_json::json!(area.cortical_id),
6776 );
6777 properties.insert(
6778 "cortical_id_s".to_string(),
6779 serde_json::json!(area.cortical_id.to_string()),
6780 );
6781 properties.insert(
6782 "cortical_idx".to_string(),
6783 serde_json::json!(area.cortical_idx),
6784 );
6785 properties.insert("name".to_string(), serde_json::json!(area.name));
6786 use crate::models::CorticalAreaExt;
6787 properties.insert(
6788 "area_type".to_string(),
6789 serde_json::json!(area.get_cortical_group()),
6790 );
6791 properties.insert(
6792 "dimensions".to_string(),
6793 serde_json::json!({
6794 "width": area.dimensions.width,
6795 "height": area.dimensions.height,
6796 "depth": area.dimensions.depth,
6797 }),
6798 );
6799 properties.insert("position".to_string(), serde_json::json!(area.position));
6800
6801 for (key, value) in &area.properties {
6803 properties.insert(key.clone(), value.clone());
6804 }
6805
6806 properties.extend(area.properties.clone());
6808
6809 Some(properties)
6810 }
6811
6812 pub fn get_all_cortical_area_properties(
6819 &self,
6820 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
6821 self.cortical_areas
6822 .keys()
6823 .filter_map(|id| self.get_cortical_area_properties(id))
6824 .collect()
6825 }
6826
6827 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
6838 self.brain_regions
6839 .get_all_region_ids()
6840 .into_iter()
6841 .cloned()
6842 .collect()
6843 }
6844
6845 pub fn get_brain_region_names(&self) -> Vec<String> {
6852 self.brain_regions
6853 .get_all_region_ids()
6854 .iter()
6855 .filter_map(|id| {
6856 self.brain_regions
6857 .get_region(id)
6858 .map(|region| region.name.clone())
6859 })
6860 .collect()
6861 }
6862
6863 pub fn get_brain_region_properties(
6874 &self,
6875 region_id: &str,
6876 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6877 let region = self.brain_regions.get_region(region_id)?;
6878
6879 let mut properties = std::collections::HashMap::new();
6880 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
6881 properties.insert("name".to_string(), serde_json::json!(region.name));
6882 properties.insert(
6883 "region_type".to_string(),
6884 serde_json::json!(format!("{:?}", region.region_type)),
6885 );
6886 properties.insert(
6887 "cortical_areas".to_string(),
6888 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
6889 );
6890
6891 properties.extend(region.properties.clone());
6893
6894 Some(properties)
6895 }
6896
6897 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
6908 self.cortical_areas.contains_key(cortical_id)
6909 }
6910
6911 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6922 self.brain_regions.get_region(region_id).is_some()
6923 }
6924
6925 pub fn get_brain_region_count(&self) -> usize {
6932 self.brain_regions.region_count()
6933 }
6934
6935 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6946 self.get_neurons_in_area(cortical_id)
6950 }
6951}
6952
6953impl std::fmt::Debug for ConnectomeManager {
6955 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6956 f.debug_struct("ConnectomeManager")
6957 .field("cortical_areas", &self.cortical_areas.len())
6958 .field("next_cortical_idx", &self.next_cortical_idx)
6959 .field("brain_regions", &self.brain_regions)
6960 .field(
6961 "npu",
6962 &if self.npu.is_some() {
6963 "Connected"
6964 } else {
6965 "Not connected"
6966 },
6967 )
6968 .field("initialized", &self.initialized)
6969 .finish()
6970 }
6971}
6972
6973#[cfg(test)]
6974mod tests {
6975 use super::*;
6976 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6977
6978 #[test]
6979 fn test_singleton_instance() {
6980 let instance1 = ConnectomeManager::instance();
6981 let instance2 = ConnectomeManager::instance();
6982
6983 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6985 }
6986
6987 #[test]
6988 fn test_add_cortical_area() {
6989 ConnectomeManager::reset_for_testing();
6990
6991 let instance = ConnectomeManager::instance();
6992 let mut manager = instance.write();
6993
6994 use feagi_structures::genomic::cortical_area::{
6995 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6996 };
6997 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6999 let area = CorticalArea::new(
7000 cortical_id,
7001 0,
7002 "Visual Input".to_string(),
7003 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
7004 (0, 0, 0).into(),
7005 cortical_type,
7006 )
7007 .unwrap();
7008
7009 let initial_count = manager.get_cortical_area_count();
7010 let _cortical_idx = manager.add_cortical_area(area).unwrap();
7011
7012 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
7013 assert!(manager.has_cortical_area(&cortical_id));
7014 assert!(manager.is_initialized());
7015 }
7016
7017 #[test]
7018 fn test_cortical_area_lookups() {
7019 ConnectomeManager::reset_for_testing();
7020
7021 let instance = ConnectomeManager::instance();
7022 let mut manager = instance.write();
7023
7024 use feagi_structures::genomic::cortical_area::{
7025 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7026 };
7027 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7029 let area = CorticalArea::new(
7030 cortical_id,
7031 0,
7032 "Test Area".to_string(),
7033 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7034 (0, 0, 0).into(),
7035 cortical_type,
7036 )
7037 .unwrap();
7038
7039 let cortical_idx = manager.add_cortical_area(area).unwrap();
7040
7041 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
7043
7044 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
7046
7047 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
7049 assert_eq!(retrieved_area.name, "Test Area");
7050 }
7051
7052 #[test]
7053 fn test_remove_cortical_area() {
7054 ConnectomeManager::reset_for_testing();
7055
7056 let instance = ConnectomeManager::instance();
7057 let mut manager = instance.write();
7058
7059 use feagi_structures::genomic::cortical_area::{
7060 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7061 };
7062 let cortical_id = CoreCorticalType::Power.to_cortical_id();
7063
7064 if manager.has_cortical_area(&cortical_id) {
7066 manager.remove_cortical_area(&cortical_id).unwrap();
7067 }
7068
7069 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7070 let area = CorticalArea::new(
7071 cortical_id,
7072 0,
7073 "Test".to_string(),
7074 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7075 (0, 0, 0).into(),
7076 cortical_type,
7077 )
7078 .unwrap();
7079
7080 let initial_count = manager.get_cortical_area_count();
7081 manager.add_cortical_area(area).unwrap();
7082 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
7083
7084 manager.remove_cortical_area(&cortical_id).unwrap();
7085 assert_eq!(manager.get_cortical_area_count(), initial_count);
7086 assert!(!manager.has_cortical_area(&cortical_id));
7087 }
7088
7089 #[test]
7090 fn test_duplicate_area_error() {
7091 ConnectomeManager::reset_for_testing();
7092
7093 let instance = ConnectomeManager::instance();
7094 let mut manager = instance.write();
7095
7096 use feagi_structures::genomic::cortical_area::{
7097 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7098 };
7099 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
7102 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7103 let area1 = CorticalArea::new(
7104 cortical_id,
7105 0,
7106 "First".to_string(),
7107 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7108 (0, 0, 0).into(),
7109 cortical_type,
7110 )
7111 .unwrap();
7112
7113 let area2 = CorticalArea::new(
7114 cortical_id, 1,
7116 "Second".to_string(),
7117 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7118 (0, 0, 0).into(),
7119 cortical_type,
7120 )
7121 .unwrap();
7122
7123 manager.add_cortical_area(area1).unwrap();
7124 let result = manager.add_cortical_area(area2);
7125
7126 assert!(result.is_err());
7127 }
7128
7129 #[test]
7130 fn test_brain_region_management() {
7131 ConnectomeManager::reset_for_testing();
7132
7133 let instance = ConnectomeManager::instance();
7134 let mut manager = instance.write();
7135
7136 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
7137 let region_id_str = region_id.to_string();
7138 let root = BrainRegion::new(
7139 region_id,
7140 "Root".to_string(),
7141 feagi_structures::genomic::brain_regions::RegionType::Undefined,
7142 )
7143 .unwrap();
7144
7145 let initial_count = manager.get_brain_region_ids().len();
7146 manager.add_brain_region(root, None).unwrap();
7147
7148 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
7149 assert!(manager.get_brain_region(®ion_id_str).is_some());
7150 }
7151
7152 #[test]
7153 fn test_synapse_operations() {
7154 use feagi_npu_burst_engine::npu::RustNPU;
7155 use feagi_npu_burst_engine::TracingMutex;
7156 use std::sync::Arc;
7157
7158 use feagi_npu_burst_engine::backend::CPUBackend;
7160 use feagi_npu_burst_engine::DynamicNPU;
7161 use feagi_npu_runtime::StdRuntime;
7162
7163 let runtime = StdRuntime;
7164 let backend = CPUBackend::new();
7165 let npu_result =
7166 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
7167 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
7168 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
7169
7170 use feagi_structures::genomic::cortical_area::{
7172 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7173 };
7174 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7176 let area = CorticalArea::new(
7177 cortical_id,
7178 0, "Test Area".to_string(),
7180 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
7181 (0, 0, 0).into(), cortical_type,
7183 )
7184 .unwrap();
7185 let cortical_idx = manager.add_cortical_area(area).unwrap();
7186
7187 if let Some(npu_arc) = manager.get_npu() {
7189 if let Ok(mut npu_guard) = npu_arc.try_lock() {
7190 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
7191 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
7192 }
7193 }
7194 }
7195
7196 let neuron1_id = manager
7198 .add_neuron(
7199 &cortical_id,
7200 0,
7201 0,
7202 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
7214 .unwrap();
7215
7216 let neuron2_id = manager
7217 .add_neuron(
7218 &cortical_id,
7219 1,
7220 0,
7221 0, 100.0,
7223 f32::MAX, 0.1,
7225 -60.0,
7226 0,
7227 2,
7228 1.0,
7229 5,
7230 10,
7231 false,
7232 )
7233 .unwrap();
7234
7235 manager
7237 .create_synapse(
7238 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
7242 .unwrap();
7243
7244 println!("✅ Synapse creation test passed");
7247 }
7248
7249 #[test]
7250 fn test_apply_cortical_mapping_missing_rules_is_ok() {
7251 let mut manager = ConnectomeManager::new_for_testing();
7254
7255 use feagi_structures::genomic::cortical_area::{
7256 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7257 };
7258
7259 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
7260 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
7261
7262 let src_area = CorticalArea::new(
7263 src_id,
7264 0,
7265 "src".to_string(),
7266 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7267 (0, 0, 0).into(),
7268 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7269 )
7270 .unwrap();
7271
7272 let dst_area = CorticalArea::new(
7273 dst_id,
7274 1,
7275 "dst".to_string(),
7276 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7277 (0, 0, 0).into(),
7278 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7279 )
7280 .unwrap();
7281
7282 manager.add_cortical_area(src_area).unwrap();
7283 manager.add_cortical_area(dst_area).unwrap();
7284
7285 let count = manager
7287 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7288 .unwrap();
7289 assert_eq!(count, 0);
7290
7291 manager
7293 .update_cortical_mapping(
7294 &src_id,
7295 &dst_id,
7296 vec![serde_json::json!({"morphology_id":"m1"})],
7297 )
7298 .unwrap();
7299 manager
7300 .update_cortical_mapping(&src_id, &dst_id, vec![])
7301 .unwrap();
7302
7303 let count2 = manager
7304 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7305 .unwrap();
7306 assert_eq!(count2, 0);
7307 }
7308
7309 #[test]
7310 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
7311 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7312 let mapping_dst = serde_json::json!({
7313 "csrc0002": [
7314 {"morphology_id": "m1"}
7315 ]
7316 });
7317 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
7318
7319 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
7320 .expect("legacy destination key should resolve");
7321 assert_eq!(rules.len(), 1);
7322 assert_eq!(
7323 rules[0].get("morphology_id").and_then(|v| v.as_str()),
7324 Some("m1")
7325 );
7326 }
7327
7328 #[test]
7329 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
7330 use feagi_npu_burst_engine::backend::CPUBackend;
7331 use feagi_npu_burst_engine::RustNPU;
7332 use feagi_npu_burst_engine::TracingMutex;
7333 use feagi_npu_runtime::StdRuntime;
7334 use feagi_structures::genomic::cortical_area::{
7335 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7336 };
7337 use std::sync::Arc;
7338
7339 let runtime = StdRuntime;
7340 let backend = CPUBackend::new();
7341 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7342 let dyn_npu = Arc::new(TracingMutex::new(
7343 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7344 "TestNPU",
7345 ));
7346 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7347
7348 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
7349 let area = CorticalArea::new(
7350 area_id,
7351 0,
7352 "n".to_string(),
7353 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7354 (0, 0, 0).into(),
7355 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7356 )
7357 .unwrap();
7358
7359 manager.add_cortical_area(area).unwrap();
7360 let nid = manager
7361 .add_neuron(
7362 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
7363 )
7364 .unwrap();
7365
7366 let props = manager
7367 .get_neuron_properties(nid)
7368 .expect("neuron properties");
7369 for key in [
7370 "consecutive_fire_count",
7371 "consecutive_fire_limit",
7372 "snooze_period",
7373 "membrane_potential",
7374 "threshold",
7375 "refractory_countdown",
7376 "mp_charge_accumulation",
7377 "neuron_type",
7378 "mp_driven_psp",
7379 "psp_uniform_distribution",
7380 "leak_coefficient",
7381 "resting_potential",
7382 "excitability",
7383 "threshold_limit",
7384 "refractory_period",
7385 ] {
7386 assert!(props.contains_key(key), "missing neuron state key: {key}");
7387 }
7388 }
7389
7390 #[test]
7391 fn test_mapping_deletion_prunes_synapses_between_areas() {
7392 use feagi_npu_burst_engine::backend::CPUBackend;
7393 use feagi_npu_burst_engine::RustNPU;
7394 use feagi_npu_burst_engine::TracingMutex;
7395 use feagi_npu_runtime::StdRuntime;
7396 use feagi_structures::genomic::cortical_area::{
7397 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7398 };
7399 use std::sync::Arc;
7400
7401 let runtime = StdRuntime;
7403 let backend = CPUBackend::new();
7404 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7405 let dyn_npu = Arc::new(TracingMutex::new(
7406 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7407 "TestNPU",
7408 ));
7409 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7410
7411 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
7413 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
7414
7415 let src_area = CorticalArea::new(
7416 src_id,
7417 0,
7418 "src".to_string(),
7419 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7420 (0, 0, 0).into(),
7421 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7422 )
7423 .unwrap();
7424 let dst_area = CorticalArea::new(
7425 dst_id,
7426 1,
7427 "dst".to_string(),
7428 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7429 (0, 0, 0).into(),
7430 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7431 )
7432 .unwrap();
7433
7434 manager.add_cortical_area(src_area).unwrap();
7435 manager.add_cortical_area(dst_area).unwrap();
7436
7437 let s0 = manager
7439 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7440 .unwrap();
7441 let s1 = manager
7442 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7443 .unwrap();
7444 let t0 = manager
7445 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7446 .unwrap();
7447 let t1 = manager
7448 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7449 .unwrap();
7450
7451 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7453 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7454
7455 {
7457 let mut npu = dyn_npu.lock().unwrap();
7458 npu.rebuild_synapse_index();
7459 assert_eq!(npu.get_synapse_count(), 2);
7460 }
7461
7462 manager
7464 .update_cortical_mapping(&src_id, &dst_id, vec![])
7465 .unwrap();
7466 let created = manager
7467 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7468 .unwrap();
7469 assert_eq!(created, 0);
7470
7471 {
7473 let mut npu = dyn_npu.lock().unwrap();
7474 npu.rebuild_synapse_index();
7476 assert_eq!(npu.get_synapse_count(), 0);
7477 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7478 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7479 }
7480 }
7481
7482 #[test]
7483 fn test_mapping_update_prunes_synapses_between_areas() {
7484 use feagi_npu_burst_engine::backend::CPUBackend;
7485 use feagi_npu_burst_engine::RustNPU;
7486 use feagi_npu_burst_engine::TracingMutex;
7487 use feagi_npu_runtime::StdRuntime;
7488 use feagi_structures::genomic::cortical_area::{
7489 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7490 };
7491 use std::sync::Arc;
7492
7493 let runtime = StdRuntime;
7495 let backend = CPUBackend::new();
7496 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7497 let dyn_npu = Arc::new(TracingMutex::new(
7498 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7499 "TestNPU",
7500 ));
7501 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7502
7503 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7505
7506 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
7509 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
7510
7511 let src_area = CorticalArea::new(
7512 src_id,
7513 0,
7514 "src".to_string(),
7515 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7516 (0, 0, 0).into(),
7517 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7518 )
7519 .unwrap();
7520 let dst_area = CorticalArea::new(
7521 dst_id,
7522 0,
7523 "dst".to_string(),
7524 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7525 (0, 0, 0).into(),
7526 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7527 )
7528 .unwrap();
7529
7530 manager.add_cortical_area(src_area).unwrap();
7531 manager.add_cortical_area(dst_area).unwrap();
7532
7533 let s0 = manager
7535 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7536 .unwrap();
7537 let s1 = manager
7538 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7539 .unwrap();
7540 let t0 = manager
7541 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7542 .unwrap();
7543 let t1 = manager
7544 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7545 .unwrap();
7546
7547 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7549 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7550
7551 {
7553 let mut npu = dyn_npu.lock().unwrap();
7554 npu.rebuild_synapse_index();
7555 assert_eq!(npu.get_synapse_count(), 2);
7556 }
7557
7558 manager
7564 .update_cortical_mapping(
7565 &src_id,
7566 &dst_id,
7567 vec![serde_json::json!({
7568 "morphology_id": "episodic_memory",
7569 "morphology_scalar": [1],
7570 "postSynapticCurrent_multiplier": 1,
7571 "plasticity_flag": false,
7572 "plasticity_constant": 0,
7573 "ltp_multiplier": 0,
7574 "ltd_multiplier": 0,
7575 "plasticity_window": 0,
7576 })],
7577 )
7578 .unwrap();
7579 let created = manager
7580 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7581 .unwrap();
7582 assert_eq!(created, 0);
7583
7584 {
7586 let mut npu = dyn_npu.lock().unwrap();
7587 npu.rebuild_synapse_index();
7589 assert_eq!(npu.get_synapse_count(), 0);
7590 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7591 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7592 }
7593 }
7594
7595 #[test]
7596 fn test_upstream_area_tracking() {
7597 use crate::models::cortical_area::CorticalArea;
7599 use feagi_npu_burst_engine::backend::CPUBackend;
7600 use feagi_npu_burst_engine::TracingMutex;
7601 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7602 use feagi_npu_runtime::StdRuntime;
7603 use feagi_structures::genomic::cortical_area::{
7604 CorticalAreaDimensions, CorticalAreaType, CorticalID,
7605 };
7606
7607 let runtime = StdRuntime;
7609 let backend = CPUBackend::new();
7610 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7611 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7612 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7613
7614 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7617
7618 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
7620 let src_area = CorticalArea::new(
7621 src_id,
7622 0,
7623 "Source Area".to_string(),
7624 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7625 (0, 0, 0).into(),
7626 CorticalAreaType::Custom(
7627 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7628 ),
7629 )
7630 .unwrap();
7631 let src_idx = manager.add_cortical_area(src_area).unwrap();
7632
7633 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
7635 let dst_area = CorticalArea::new(
7636 dst_id,
7637 0,
7638 "Dest Area".to_string(),
7639 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7640 (0, 0, 0).into(),
7641 CorticalAreaType::Custom(
7642 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7643 ),
7644 )
7645 .unwrap();
7646 manager.add_cortical_area(dst_area).unwrap();
7647
7648 {
7650 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
7651 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
7652 assert!(
7653 upstream.as_array().unwrap().is_empty(),
7654 "Upstream areas should be empty initially"
7655 );
7656 }
7657
7658 let mapping_data = vec![serde_json::json!({
7660 "morphology_id": "episodic_memory",
7661 "morphology_scalar": 1,
7662 "postSynapticCurrent_multiplier": 1.0,
7663 })];
7664 manager
7665 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7666 .unwrap();
7667 manager
7668 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7669 .unwrap();
7670
7671 {
7673 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7674 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
7675 assert_eq!(
7676 upstream_areas[0], src_idx,
7677 "Upstream area should be src_idx"
7678 );
7679 }
7680
7681 manager
7683 .update_cortical_mapping(&src_id, &dst_id, vec![])
7684 .unwrap();
7685 manager
7686 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7687 .unwrap();
7688
7689 {
7691 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7692 assert_eq!(
7693 upstream_areas.len(),
7694 0,
7695 "Should have 0 upstream areas after deletion"
7696 );
7697 }
7698 }
7699
7700 #[test]
7701 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
7702 use crate::models::cortical_area::CorticalArea;
7703 use feagi_npu_burst_engine::backend::CPUBackend;
7704 use feagi_npu_burst_engine::TracingMutex;
7705 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7706 use feagi_npu_runtime::StdRuntime;
7707 use feagi_structures::genomic::cortical_area::{
7708 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7709 };
7710 use std::sync::Arc;
7711
7712 let runtime = StdRuntime;
7713 let backend = CPUBackend::new();
7714 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7715 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7716 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7717 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7718
7719 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7720 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
7721 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7722 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
7723
7724 let a1_area = CorticalArea::new(
7725 a1_id,
7726 0,
7727 "A1".to_string(),
7728 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7729 (0, 0, 0).into(),
7730 CorticalAreaType::Custom(
7731 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7732 ),
7733 )
7734 .unwrap();
7735 let a2_area = CorticalArea::new(
7736 a2_id,
7737 0,
7738 "A2".to_string(),
7739 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7740 (0, 0, 0).into(),
7741 CorticalAreaType::Custom(
7742 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7743 ),
7744 )
7745 .unwrap();
7746
7747 let mut m1_area = CorticalArea::new(
7748 m1_id,
7749 0,
7750 "M1".to_string(),
7751 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7752 (0, 0, 0).into(),
7753 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7754 )
7755 .unwrap();
7756 m1_area
7757 .properties
7758 .insert("is_mem_type".to_string(), serde_json::json!(true));
7759 m1_area
7760 .properties
7761 .insert("temporal_depth".to_string(), serde_json::json!(1));
7762
7763 let mut m2_area = CorticalArea::new(
7764 m2_id,
7765 0,
7766 "M2".to_string(),
7767 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7768 (0, 0, 0).into(),
7769 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7770 )
7771 .unwrap();
7772 m2_area
7773 .properties
7774 .insert("is_mem_type".to_string(), serde_json::json!(true));
7775 m2_area
7776 .properties
7777 .insert("temporal_depth".to_string(), serde_json::json!(1));
7778
7779 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
7780 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
7781 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
7782 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
7783
7784 manager
7785 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7786 .unwrap();
7787 manager
7788 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7789 .unwrap();
7790
7791 let episodic_mapping = vec![serde_json::json!({
7792 "morphology_id": "episodic_memory",
7793 "morphology_scalar": 1,
7794 "postSynapticCurrent_multiplier": 1.0,
7795 })];
7796 manager
7797 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
7798 .unwrap();
7799 manager
7800 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
7801 .unwrap();
7802 manager
7803 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
7804 .unwrap();
7805 manager
7806 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
7807 .unwrap();
7808
7809 let assoc_mapping = vec![serde_json::json!({
7810 "morphology_id": "associative_memory",
7811 "morphology_scalar": 1,
7812 "postSynapticCurrent_multiplier": 1.0,
7813 "plasticity_flag": true,
7814 "plasticity_constant": 1,
7815 "ltp_multiplier": 1,
7816 "ltd_multiplier": 1,
7817 "plasticity_window": 5,
7818 })];
7819 manager
7820 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
7821 .unwrap();
7822 manager
7823 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7824 .unwrap();
7825 manager
7827 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
7828 .unwrap();
7829 manager
7830 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
7831 .unwrap();
7832
7833 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7834 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7835 assert_eq!(
7836 upstream_m1.len(),
7837 2,
7838 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
7839 );
7840 assert_eq!(
7841 upstream_m2.len(),
7842 2,
7843 "M2 should have A2 and M1 as upstreams"
7844 );
7845
7846 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
7847 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
7848
7849 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7850 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7851 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
7852 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
7853 assert!(upstream_m1.contains(&a1_idx));
7854 assert!(upstream_m1.contains(&m2_idx));
7855 assert!(upstream_m2.contains(&a2_idx));
7856 assert!(upstream_m2.contains(&m1_idx));
7857
7858 {
7860 let mut npu_lock = dyn_npu.lock().unwrap();
7861 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
7862 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
7863 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
7864 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
7865 npu_lock.process_burst().expect("Burst processing failed");
7866 }
7867
7868 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7869 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7870 assert_eq!(
7871 upstream_m1.len(),
7872 2,
7873 "M1 should keep 2 upstreams after firing"
7874 );
7875 assert_eq!(
7876 upstream_m2.len(),
7877 2,
7878 "M2 should keep 2 upstreams after firing"
7879 );
7880 }
7881
7882 #[test]
7883 fn test_memory_twin_created_for_memory_mapping() {
7884 use crate::models::cortical_area::CorticalArea;
7885 use feagi_npu_burst_engine::backend::CPUBackend;
7886 use feagi_npu_burst_engine::TracingMutex;
7887 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7888 use feagi_npu_runtime::StdRuntime;
7889 use feagi_structures::genomic::cortical_area::{
7890 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7891 MemoryCorticalType,
7892 };
7893 use std::sync::Arc;
7894
7895 let runtime = StdRuntime;
7896 let backend = CPUBackend::new();
7897 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7898 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7899 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7900 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7901
7902 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
7903 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
7904
7905 let src_area = CorticalArea::new(
7906 src_id,
7907 0,
7908 "Source Area".to_string(),
7909 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7910 (0, 0, 0).into(),
7911 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7912 )
7913 .unwrap();
7914 let mut dst_area = CorticalArea::new(
7915 dst_id,
7916 0,
7917 "Memory Area".to_string(),
7918 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7919 (0, 0, 0).into(),
7920 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7921 )
7922 .unwrap();
7923 dst_area
7924 .properties
7925 .insert("is_mem_type".to_string(), serde_json::json!(true));
7926 dst_area
7927 .properties
7928 .insert("temporal_depth".to_string(), serde_json::json!(1));
7929
7930 manager.add_cortical_area(src_area).unwrap();
7931 manager.add_cortical_area(dst_area).unwrap();
7932
7933 let mapping_data = vec![serde_json::json!({
7934 "morphology_id": "episodic_memory",
7935 "morphology_scalar": 1,
7936 "postSynapticCurrent_multiplier": 1.0,
7937 })];
7938 manager
7939 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7940 .unwrap();
7941 manager
7942 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7943 .unwrap();
7944
7945 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7946 let twin_map = memory_area
7947 .properties
7948 .get("memory_twin_areas")
7949 .and_then(|v| v.as_object())
7950 .expect("memory_twin_areas should be set");
7951 let twin_id_str = twin_map
7952 .get(&src_id.as_base_64())
7953 .and_then(|v| v.as_str())
7954 .expect("Missing twin entry for upstream area");
7955 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7956 let mapping = memory_area
7957 .properties
7958 .get("cortical_mapping_dst")
7959 .and_then(|v| v.as_object())
7960 .and_then(|map| map.get(&twin_id.as_base_64()))
7961 .and_then(|v| v.as_array())
7962 .expect("Missing memory replay mapping for twin area");
7963 let uses_replay = mapping.iter().any(|rule| {
7964 rule.get("morphology_id")
7965 .and_then(|v| v.as_str())
7966 .is_some_and(|id| id == "memory_replay")
7967 });
7968 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7969
7970 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7971 assert!(matches!(
7972 twin_area.cortical_type,
7973 CorticalAreaType::Custom(_)
7974 ));
7975 assert_eq!(
7976 twin_area
7977 .properties
7978 .get("memory_twin_of")
7979 .and_then(|v| v.as_str()),
7980 Some(src_id.as_base_64().as_str())
7981 );
7982 assert_eq!(
7983 twin_area
7984 .properties
7985 .get("memory_twin_for")
7986 .and_then(|v| v.as_str()),
7987 Some(dst_id.as_base_64().as_str())
7988 );
7989 }
7990
7991 #[test]
7992 fn test_associative_memory_between_memory_areas_creates_synapses() {
7993 use crate::models::cortical_area::CorticalArea;
7994 use feagi_npu_burst_engine::backend::CPUBackend;
7995 use feagi_npu_burst_engine::TracingMutex;
7996 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7997 use feagi_npu_runtime::StdRuntime;
7998 use feagi_structures::genomic::cortical_area::{
7999 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8000 };
8001 use std::sync::Arc;
8002
8003 let runtime = StdRuntime;
8004 let backend = CPUBackend::new();
8005 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8006 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8007 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8008 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8009
8010 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
8011 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
8012
8013 let mut m1_area = CorticalArea::new(
8014 m1_id,
8015 0,
8016 "Memory M1".to_string(),
8017 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8018 (0, 0, 0).into(),
8019 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8020 )
8021 .unwrap();
8022 m1_area
8023 .properties
8024 .insert("is_mem_type".to_string(), serde_json::json!(true));
8025 m1_area
8026 .properties
8027 .insert("temporal_depth".to_string(), serde_json::json!(1));
8028
8029 let mut m2_area = CorticalArea::new(
8030 m2_id,
8031 0,
8032 "Memory M2".to_string(),
8033 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8034 (0, 0, 0).into(),
8035 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8036 )
8037 .unwrap();
8038 m2_area
8039 .properties
8040 .insert("is_mem_type".to_string(), serde_json::json!(true));
8041 m2_area
8042 .properties
8043 .insert("temporal_depth".to_string(), serde_json::json!(1));
8044
8045 manager.add_cortical_area(m1_area).unwrap();
8046 manager.add_cortical_area(m2_area).unwrap();
8047
8048 manager
8049 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8050 .unwrap();
8051 manager
8052 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8053 .unwrap();
8054
8055 let mapping_data = vec![serde_json::json!({
8056 "morphology_id": "associative_memory",
8057 "morphology_scalar": 1,
8058 "postSynapticCurrent_multiplier": 1.0,
8059 "plasticity_flag": true,
8060 "plasticity_constant": 1,
8061 "ltp_multiplier": 1,
8062 "ltd_multiplier": 1,
8063 "plasticity_window": 5,
8064 })];
8065 manager
8066 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
8067 .unwrap();
8068 let created = manager
8069 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8070 .unwrap();
8071 assert!(
8072 created > 0,
8073 "Expected associative memory mapping between memory areas to create synapses"
8074 );
8075 let npu_guard = dyn_npu.lock().unwrap();
8076 let assoc_tagged =
8077 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
8078 assert!(
8079 assoc_tagged >= 1,
8080 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
8081 );
8082 }
8083
8084 #[test]
8085 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
8086 use crate::models::cortical_area::CorticalArea;
8087 use feagi_npu_burst_engine::backend::CPUBackend;
8088 use feagi_npu_burst_engine::TracingMutex;
8089 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8090 use feagi_npu_runtime::StdRuntime;
8091 use feagi_structures::genomic::cortical_area::{
8092 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8093 MemoryCorticalType,
8094 };
8095 use std::sync::Arc;
8096
8097 let runtime = StdRuntime;
8098 let backend = CPUBackend::new();
8099 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8100 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8101 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8102 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8103
8104 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8105 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
8106
8107 let src_area = CorticalArea::new(
8108 src_id,
8109 0,
8110 "Source Area".to_string(),
8111 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8112 (0, 0, 0).into(),
8113 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8114 )
8115 .unwrap();
8116 let mut mem_area = CorticalArea::new(
8117 mem_id,
8118 0,
8119 "Memory Area".to_string(),
8120 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8121 (0, 0, 0).into(),
8122 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8123 )
8124 .unwrap();
8125 mem_area
8126 .properties
8127 .insert("is_mem_type".to_string(), serde_json::json!(true));
8128 mem_area
8129 .properties
8130 .insert("temporal_depth".to_string(), serde_json::json!(1));
8131
8132 manager.add_cortical_area(src_area).unwrap();
8133 manager.add_cortical_area(mem_area).unwrap();
8134
8135 let twin_id = manager
8136 .build_memory_twin_id(&mem_id, &src_id)
8137 .expect("Failed to build twin id");
8138 let twin_area = CorticalArea::new(
8139 twin_id,
8140 0,
8141 "Source Area_twin".to_string(),
8142 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8143 (0, 0, 0).into(),
8144 CorticalAreaType::Custom(
8145 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8146 ),
8147 )
8148 .unwrap();
8149 manager.add_cortical_area(twin_area).unwrap();
8150
8151 let repaired = manager
8152 .ensure_memory_twin_area(&mem_id, &src_id)
8153 .expect("Failed to repair twin");
8154 assert_eq!(repaired, twin_id);
8155
8156 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
8157 let twin_map = mem_area
8158 .properties
8159 .get("memory_twin_areas")
8160 .and_then(|v| v.as_object())
8161 .expect("memory_twin_areas should be set");
8162 let twin_id_str = twin_map
8163 .get(&src_id.as_base_64())
8164 .and_then(|v| v.as_str())
8165 .expect("Missing twin entry for upstream area");
8166 assert_eq!(twin_id_str, twin_id.as_base_64());
8167
8168 let replay_map = mem_area
8169 .properties
8170 .get("cortical_mapping_dst")
8171 .and_then(|v| v.as_object())
8172 .and_then(|map| map.get(&twin_id.as_base_64()))
8173 .and_then(|v| v.as_array())
8174 .expect("Missing memory replay mapping for twin area");
8175 let uses_replay = replay_map.iter().any(|rule| {
8176 rule.get("morphology_id")
8177 .and_then(|v| v.as_str())
8178 .is_some_and(|id| id == "memory_replay")
8179 });
8180 assert!(uses_replay, "Expected memory_replay mapping for twin area");
8181
8182 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8183 assert_eq!(
8184 twin_area
8185 .properties
8186 .get("memory_twin_of")
8187 .and_then(|v| v.as_str()),
8188 Some(src_id.as_base_64().as_str())
8189 );
8190 assert_eq!(
8191 twin_area
8192 .properties
8193 .get("memory_twin_for")
8194 .and_then(|v| v.as_str()),
8195 Some(mem_id.as_base_64().as_str())
8196 );
8197 }
8198
8199 fn build_max_weight_test_manager() -> (
8204 ConnectomeManager,
8205 CorticalID,
8206 CorticalID,
8207 CorticalID,
8208 CorticalID,
8209 ) {
8210 use feagi_npu_burst_engine::backend::CPUBackend;
8211 use feagi_npu_burst_engine::TracingMutex;
8212 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8213 use feagi_npu_runtime::StdRuntime;
8214 use feagi_structures::genomic::cortical_area::{
8215 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8216 };
8217
8218 let runtime = StdRuntime;
8219 let backend = CPUBackend::new();
8220 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("npu");
8221 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8222 let mut mgr = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
8223 feagi_evolutionary::templates::add_core_morphologies(&mut mgr.morphology_registry);
8227
8228 let src = CorticalID::try_from_bytes(b"cstmwsrc").unwrap();
8229 let dst = CorticalID::try_from_bytes(b"cstmwdst").unwrap();
8230 let reward = CorticalID::try_from_bytes(b"cstmwrwd").unwrap();
8231 let pain = CorticalID::try_from_bytes(b"cstmwpan").unwrap();
8232
8233 for (id, label, kind) in [
8234 (
8235 src,
8236 "src",
8237 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8238 ),
8239 (
8240 dst,
8241 "dst",
8242 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8243 ),
8244 (
8245 reward,
8246 "reward",
8247 CorticalAreaType::Custom(
8248 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8249 ),
8250 ),
8251 (
8252 pain,
8253 "pain",
8254 CorticalAreaType::Custom(
8255 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8256 ),
8257 ),
8258 ] {
8259 mgr.add_cortical_area(
8260 CorticalArea::new(
8261 id,
8262 0,
8263 label.to_string(),
8264 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8265 (0, 0, 0).into(),
8266 kind,
8267 )
8268 .unwrap(),
8269 )
8270 .unwrap();
8271 mgr.add_neuron(&id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8272 .unwrap();
8273 }
8274 (mgr, src, dst, reward, pain)
8275 }
8276
8277 fn write_and_regenerate_mapping(
8282 mgr: &mut ConnectomeManager,
8283 src: &CorticalID,
8284 dst: &CorticalID,
8285 rule: serde_json::Value,
8286 ) -> BduResult<usize> {
8287 mgr.update_cortical_mapping(src, dst, vec![rule])?;
8288 mgr.regenerate_synapses_for_mapping(src, dst)
8289 }
8290
8291 #[test]
8295 fn test_max_weight_finite_positive_accepted_on_rstdp_rule() {
8296 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8297
8298 let result = write_and_regenerate_mapping(
8299 &mut mgr,
8300 &src,
8301 &dst,
8302 serde_json::json!({
8303 "morphology_id": "block_to_block",
8304 "morphology_scalar": [1, 1, 1],
8305 "postSynapticCurrent_multiplier": 1,
8306 "plasticity_flag": true,
8307 "plasticity_constant": 1,
8308 "ltp_multiplier": 1,
8309 "ltd_multiplier": 1,
8310 "plasticity_window": 10,
8311 "synaptic_delay_bursts": 1,
8312 "plasticity_mode": "rstdp",
8313 "eligibility_decay_bursts": 50,
8314 "reward_source_area": reward.as_base_64(),
8315 "punishment_source_area": pain.as_base_64(),
8316 "max_weight": 12.5,
8317 }),
8318 );
8319 assert!(
8320 result.is_ok(),
8321 "valid max_weight=12.5 must be accepted, got {:?}",
8322 result
8323 );
8324 }
8325
8326 #[test]
8331 fn test_max_weight_invalid_values_rejected() {
8332 for bad in &[
8333 serde_json::json!(0.0),
8334 serde_json::json!(-1.5),
8335 serde_json::json!("not_a_number"),
8336 ] {
8337 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8338 let result = write_and_regenerate_mapping(
8339 &mut mgr,
8340 &src,
8341 &dst,
8342 serde_json::json!({
8343 "morphology_id": "block_to_block",
8344 "morphology_scalar": [1, 1, 1],
8345 "postSynapticCurrent_multiplier": 1,
8346 "plasticity_flag": true,
8347 "plasticity_constant": 1,
8348 "ltp_multiplier": 1,
8349 "ltd_multiplier": 1,
8350 "plasticity_window": 10,
8351 "synaptic_delay_bursts": 1,
8352 "plasticity_mode": "rstdp",
8353 "eligibility_decay_bursts": 50,
8354 "reward_source_area": reward.as_base_64(),
8355 "punishment_source_area": pain.as_base_64(),
8356 "max_weight": bad,
8357 }),
8358 );
8359 assert!(
8360 result.is_err(),
8361 "max_weight={:?} should have been rejected, got {:?}",
8362 bad,
8363 result
8364 );
8365 }
8366 }
8367
8368 #[test]
8371 fn test_ltp_ltd_multiplier_out_of_i8_range_rejected() {
8372 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8373 let result = write_and_regenerate_mapping(
8374 &mut mgr,
8375 &src,
8376 &dst,
8377 serde_json::json!({
8378 "morphology_id": "block_to_block",
8379 "morphology_scalar": [1, 1, 1],
8380 "postSynapticCurrent_multiplier": 1,
8381 "plasticity_flag": true,
8382 "plasticity_constant": 1,
8383 "ltp_multiplier": 200,
8384 "ltd_multiplier": 1,
8385 "plasticity_window": 10,
8386 "synaptic_delay_bursts": 1,
8387 "plasticity_mode": "rstdp",
8388 "eligibility_decay_bursts": 50,
8389 "reward_source_area": reward.as_base_64(),
8390 "punishment_source_area": pain.as_base_64(),
8391 }),
8392 );
8393 assert!(
8394 result.is_err(),
8395 "ltp_multiplier=200 must be rejected (i8 range); got {:?}",
8396 result
8397 );
8398 }
8399
8400 #[test]
8403 fn test_max_weight_rejected_when_plasticity_off() {
8404 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
8405
8406 let result = write_and_regenerate_mapping(
8407 &mut mgr,
8408 &src,
8409 &dst,
8410 serde_json::json!({
8411 "morphology_id": "block_to_block",
8412 "morphology_scalar": [1, 1, 1],
8413 "postSynapticCurrent_multiplier": 1,
8414 "plasticity_flag": true,
8419 "plasticity_constant": 0,
8420 "ltp_multiplier": 0,
8421 "ltd_multiplier": 0,
8422 "plasticity_window": 0,
8423 "synaptic_delay_bursts": 1,
8424 "plasticity_mode": "off",
8425 "max_weight": 10.0,
8426 }),
8427 );
8428 assert!(
8429 result.is_err(),
8430 "max_weight on off-mode rule must be rejected; got {:?}",
8431 result
8432 );
8433 }
8434
8435 #[test]
8436 fn test_plasticity_eta_rejected_when_plasticity_off() {
8437 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
8438
8439 let result = write_and_regenerate_mapping(
8440 &mut mgr,
8441 &src,
8442 &dst,
8443 serde_json::json!({
8444 "morphology_id": "block_to_block",
8445 "morphology_scalar": [1, 1, 1],
8446 "postSynapticCurrent_multiplier": 1,
8447 "plasticity_flag": true,
8448 "plasticity_constant": 0,
8449 "ltp_multiplier": 0,
8450 "ltd_multiplier": 0,
8451 "plasticity_window": 0,
8452 "synaptic_delay_bursts": 1,
8453 "plasticity_mode": "off",
8454 "plasticity_eta": 0.5,
8455 }),
8456 );
8457 assert!(
8458 result.is_err(),
8459 "plasticity_eta on off-mode rule must be rejected; got {:?}",
8460 result
8461 );
8462 }
8463
8464 #[test]
8465 fn test_plasticity_eta_non_positive_rejected() {
8466 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8467
8468 let result = write_and_regenerate_mapping(
8469 &mut mgr,
8470 &src,
8471 &dst,
8472 serde_json::json!({
8473 "morphology_id": "block_to_block",
8474 "morphology_scalar": [1, 1, 1],
8475 "postSynapticCurrent_multiplier": 1,
8476 "plasticity_flag": true,
8477 "plasticity_constant": 1,
8478 "ltp_multiplier": 1,
8479 "ltd_multiplier": 1,
8480 "plasticity_window": 10,
8481 "synaptic_delay_bursts": 1,
8482 "plasticity_mode": "rstdp",
8483 "eligibility_decay_bursts": 50,
8484 "reward_source_area": reward.as_base_64(),
8485 "punishment_source_area": pain.as_base_64(),
8486 "plasticity_eta": 0.0,
8487 }),
8488 );
8489 assert!(
8490 result.is_err(),
8491 "plasticity_eta=0 must be rejected; got {:?}",
8492 result
8493 );
8494 }
8495}