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 );
2297 } else {
2298 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2299 }
2300 } else {
2301 debug!(
2302 target: "feagi-bdu",
2303 "Skipping plasticity registration: no memory properties for area {}",
2304 dst_area_id.as_base_64()
2305 );
2306 }
2307 } else {
2308 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2309 }
2310 } else {
2311 warn!(
2312 target: "feagi-bdu",
2313 "PlasticityExecutor not available; memory area {} not registered",
2314 dst_area_id.as_base_64()
2315 );
2316 }
2317
2318 #[cfg(not(feature = "plasticity"))]
2319 {
2320 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2321 }
2322 } else {
2323 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2325
2326 let mut npu = npu_arc.lock().unwrap();
2328 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2329 }
2330
2331 info!(
2332 target: "feagi-bdu",
2333 "Created {} new synapses: {} -> {}",
2334 synapse_count,
2335 src_area_id,
2336 dst_area_id
2337 );
2338
2339 if pruned_synapse_count > 0 || synapse_count == 0 {
2342 let mut npu = npu_arc.lock().unwrap();
2343 npu.rebuild_synapse_index();
2344 info!(
2345 target: "feagi-bdu",
2346 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2347 src_area_id,
2348 dst_area_id,
2349 pruned_synapse_count,
2350 synapse_count
2351 );
2352 } else {
2353 info!(
2354 target: "feagi-bdu",
2355 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2356 src_area_id,
2357 dst_area_id,
2358 synapse_count
2359 );
2360 }
2361
2362 {
2364 let npu = npu_arc.lock().unwrap();
2365 let fresh_count = npu.get_synapse_count();
2366 self.cached_synapse_count
2367 .store(fresh_count, Ordering::Relaxed);
2368 }
2369
2370 Ok(synapse_count)
2371 }
2372
2373 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2375 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2376 }
2377
2378 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2379 v.as_u64()
2380 .map(|n| n as usize)
2381 .or_else(|| v.as_f64().map(|f| f as usize))
2382 }
2383
2384 #[allow(clippy::too_many_arguments)]
2386 fn register_stdp_mapping_for_rule(
2387 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2388 src_area_id: &CorticalID,
2389 dst_area_id: &CorticalID,
2390 src_cortical_idx: u32,
2391 dst_cortical_idx: u32,
2392 rule_obj: &serde_json::Map<String, serde_json::Value>,
2393 bidirectional_stdp: bool,
2394 synapse_psp: f32,
2395 synapse_type: feagi_npu_neural::SynapseType,
2396 ) -> BduResult<()> {
2397 let plasticity_window = rule_obj
2398 .get("plasticity_window")
2399 .and_then(Self::json_number_as_usize_for_stdp)
2400 .ok_or_else(|| {
2401 BduError::Internal(format!(
2402 "Missing plasticity_window in plastic mapping rule {} -> {}",
2403 src_area_id, dst_area_id
2404 ))
2405 })?;
2406 let plasticity_constant = rule_obj
2407 .get("plasticity_constant")
2408 .and_then(Self::json_number_as_i64_for_stdp)
2409 .ok_or_else(|| {
2410 BduError::Internal(format!(
2411 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2412 src_area_id, dst_area_id
2413 ))
2414 })?;
2415 let ltp_i64 = rule_obj
2416 .get("ltp_multiplier")
2417 .and_then(Self::json_number_as_i64_for_stdp)
2418 .ok_or_else(|| {
2419 BduError::Internal(format!(
2420 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2421 src_area_id, dst_area_id
2422 ))
2423 })?;
2424 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
2425 BduError::Internal(format!(
2426 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2427 i8::MIN,
2428 i8::MAX,
2429 ltp_i64,
2430 src_area_id,
2431 dst_area_id
2432 ))
2433 })?;
2434 let ltd_i64 = rule_obj
2435 .get("ltd_multiplier")
2436 .and_then(Self::json_number_as_i64_for_stdp)
2437 .ok_or_else(|| {
2438 BduError::Internal(format!(
2439 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2440 src_area_id, dst_area_id
2441 ))
2442 })?;
2443 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
2444 BduError::Internal(format!(
2445 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2446 i8::MIN,
2447 i8::MAX,
2448 ltd_i64,
2449 src_area_id,
2450 dst_area_id
2451 ))
2452 })?;
2453
2454 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
2458 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
2459 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2460 }
2461 Some(s) if s.eq_ignore_ascii_case("stdp") => {
2462 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
2463 }
2464 Some(s) if s.eq_ignore_ascii_case("off") => {
2465 feagi_npu_burst_engine::npu::PlasticityMode::Off
2466 }
2467 Some(other) => {
2468 return Err(BduError::Internal(format!(
2469 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
2470 other, src_area_id, dst_area_id
2471 )));
2472 }
2473 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
2474 };
2475
2476 let eligibility_decay_bursts = rule_obj
2478 .get("eligibility_decay_bursts")
2479 .and_then(|v| v.as_u64())
2480 .map(|n| n as u32)
2481 .unwrap_or(0);
2482 let reward_source_area_id = rule_obj
2483 .get("reward_source_area")
2484 .and_then(|v| v.as_str())
2485 .map(str::to_string);
2486 let punishment_source_area_id = rule_obj
2487 .get("punishment_source_area")
2488 .and_then(|v| v.as_str())
2489 .map(str::to_string);
2490
2491 let max_weight_provided = rule_obj.get("max_weight").is_some()
2496 && !rule_obj
2497 .get("max_weight")
2498 .map(|v| v.is_null())
2499 .unwrap_or(true);
2500 let max_weight: f32 = if max_weight_provided {
2501 let raw = rule_obj
2502 .get("max_weight")
2503 .and_then(|v| v.as_f64())
2504 .ok_or_else(|| {
2505 BduError::Internal(format!(
2506 "max_weight must be a number on mapping {} -> {}",
2507 src_area_id, dst_area_id
2508 ))
2509 })?;
2510 if raw.is_nan() || raw <= 0.0 {
2511 return Err(BduError::Internal(format!(
2512 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
2513 raw, src_area_id, dst_area_id
2514 )));
2515 }
2516 raw as f32
2517 } else {
2518 f32::INFINITY
2519 };
2520
2521 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
2524 && !rule_obj
2525 .get("plasticity_eta")
2526 .map(|v| v.is_null())
2527 .unwrap_or(true);
2528 let plasticity_eta: f32 = if plasticity_eta_provided {
2529 let raw = rule_obj
2530 .get("plasticity_eta")
2531 .and_then(|v| v.as_f64())
2532 .ok_or_else(|| {
2533 BduError::Internal(format!(
2534 "plasticity_eta must be a number on mapping {} -> {}",
2535 src_area_id, dst_area_id
2536 ))
2537 })?;
2538 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
2539 return Err(BduError::Internal(format!(
2540 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
2541 raw, src_area_id, dst_area_id
2542 )));
2543 }
2544 raw as f32
2545 } else {
2546 1.0
2547 };
2548
2549 if !matches!(
2551 plasticity_mode,
2552 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2553 ) && (reward_source_area_id.is_some()
2554 || punishment_source_area_id.is_some()
2555 || eligibility_decay_bursts != 0)
2556 {
2557 return Err(BduError::Internal(format!(
2558 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
2559 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
2560 src_area_id, dst_area_id
2561 )));
2562 }
2563
2564 if matches!(
2568 plasticity_mode,
2569 feagi_npu_burst_engine::npu::PlasticityMode::Off
2570 ) && max_weight_provided
2571 {
2572 return Err(BduError::Internal(format!(
2573 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2574 src_area_id, dst_area_id
2575 )));
2576 }
2577
2578 if matches!(
2579 plasticity_mode,
2580 feagi_npu_burst_engine::npu::PlasticityMode::Off
2581 ) && plasticity_eta_provided
2582 {
2583 return Err(BduError::Internal(format!(
2584 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2585 src_area_id, dst_area_id
2586 )));
2587 }
2588
2589 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2590 let mut npu_lock = npu
2591 .lock()
2592 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2593 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2594
2595 let resolve_optional_area =
2600 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
2601 let Some(name) = name_opt else {
2602 return Ok(None);
2603 };
2604 match npu_lock.get_cortical_area_id(name.as_str()) {
2605 Some(idx) => Ok(Some(idx)),
2606 None => Err(BduError::Internal(format!(
2607 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
2608 label, name, src_area_id, dst_area_id
2609 ))),
2610 }
2611 };
2612 let reward_source_area =
2613 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
2614 let punishment_source_area =
2615 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
2616
2617 if matches!(
2619 plasticity_mode,
2620 feagi_npu_burst_engine::npu::PlasticityMode::Off
2621 ) {
2622 return Ok(());
2623 }
2624
2625 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2626 plasticity_window,
2627 plasticity_constant,
2628 ltp_multiplier,
2629 ltd_multiplier,
2630 bidirectional_stdp,
2631 synapse_psp,
2632 synapse_type,
2633 plasticity_mode,
2634 eligibility_decay_bursts,
2635 reward_source_area,
2636 punishment_source_area,
2637 max_weight,
2638 plasticity_eta,
2639 };
2640
2641 npu_lock
2642 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2643 .map_err(|e| {
2644 BduError::Internal(format!(
2645 "Failed to register STDP mapping {} -> {}: {}",
2646 src_area_id, dst_area_id, e
2647 ))
2648 })?;
2649
2650 let mut areas_to_track: Vec<(u32, usize)> = vec![
2654 (src_cortical_idx, plasticity_window),
2655 (dst_cortical_idx, plasticity_window),
2656 ];
2657 if let Some(area) = reward_source_area {
2658 areas_to_track.push((area, 1));
2659 }
2660 if let Some(area) = punishment_source_area {
2661 areas_to_track.push((area, 1));
2662 }
2663
2664 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2665 for (area_idx, required_depth) in areas_to_track {
2666 let existing = existing_configs
2667 .iter()
2668 .find(|(idx, _)| *idx == area_idx)
2669 .map(|(_, w)| *w)
2670 .unwrap_or(0);
2671 let resolved = existing.max(required_depth);
2672 if resolved != existing {
2673 npu_lock
2674 .configure_fire_ledger_window(area_idx, resolved)
2675 .map_err(|e| {
2676 BduError::Internal(format!(
2677 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2678 area_idx, resolved, e
2679 ))
2680 })?;
2681 }
2682 }
2683
2684 Ok(())
2685 }
2686
2687 fn resolve_synapse_params_for_rule(
2689 &self,
2690 src_area_id: &CorticalID,
2691 rule: &serde_json::Value,
2692 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
2693 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2695 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2696 })?;
2697
2698 let (weight, synapse_type) = {
2700 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
2701 if let Some(i) = v.as_i64() {
2702 return Some(i as f64);
2703 }
2704 v.as_f64()
2705 };
2706
2707 let mult: f64 = if let Some(obj) = rule.as_object() {
2708 obj.get("postSynapticCurrent_multiplier")
2709 .and_then(parse_f64)
2710 .unwrap_or(1.0)
2711 } else if let Some(arr) = rule.as_array() {
2712 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
2713 } else {
2714 128.0
2715 };
2716
2717 if mult < 0.0 {
2718 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
2719 } else {
2720 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
2721 }
2722 };
2723
2724 use crate::models::cortical_area::CorticalAreaExt;
2726 let psp_f32 = src_area.postsynaptic_current();
2727
2728 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
2729 obj.get("synaptic_delay_bursts")
2730 .and_then(|v| v.as_u64())
2731 .map(|d| u8::try_from(d).unwrap_or(1))
2732 .unwrap_or(1)
2733 } else if let Some(arr) = rule.as_array() {
2734 arr.get(8)
2735 .and_then(|v| v.as_u64())
2736 .map(|d| u8::try_from(d).unwrap_or(1))
2737 .unwrap_or(1)
2738 } else {
2739 1
2740 };
2741 if delay_bursts < 1 {
2742 return Err(crate::types::BduError::Internal(format!(
2743 "synaptic_delay_bursts must be >= 1 (src area {})",
2744 src_area_id.as_base_64()
2745 )));
2746 }
2747
2748 tracing::debug!(
2749 target: "feagi-bdu",
2750 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
2751 src_area_id.as_base_64(),
2752 weight,
2753 psp_f32,
2754 synapse_type,
2755 delay_bursts
2756 );
2757
2758 Ok((weight, psp_f32, synapse_type, delay_bursts))
2759 }
2760
2761 fn apply_cortical_mapping_for_pair(
2763 &mut self,
2764 src_area_id: &CorticalID,
2765 dst_area_id: &CorticalID,
2766 ) -> BduResult<usize> {
2767 let rules = {
2773 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2774 crate::types::BduError::InvalidArea(format!(
2775 "Source area not found: {}",
2776 src_area_id
2777 ))
2778 })?;
2779
2780 let Some(mapping_dst) = src_area
2781 .properties
2782 .get("cortical_mapping_dst")
2783 .and_then(|v| v.as_object())
2784 else {
2785 return Ok(0);
2786 };
2787
2788 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
2789 else {
2790 return Ok(0);
2791 };
2792
2793 rules.clone()
2794 }; if rules.is_empty() {
2797 return Ok(0);
2798 }
2799
2800 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2802 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2803 })?;
2804 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2805 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2806 })?;
2807
2808 let npu_arc = self
2810 .npu
2811 .as_ref()
2812 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2813 .clone();
2814
2815 tracing::debug!(
2816 target: "feagi-bdu",
2817 "Applying {} mapping rule(s) for {} -> {}",
2818 rules.len(),
2819 src_area_id,
2820 dst_area_id
2821 );
2822 let mut total_synapses = 0;
2824 for rule in &rules {
2825 let morphology_id = if let Some(rule_obj) = rule.as_object() {
2826 rule_obj
2827 .get("morphology_id")
2828 .and_then(|v| v.as_str())
2829 .unwrap_or("unknown")
2830 .to_string()
2831 } else if let Some(rule_arr) = rule.as_array() {
2832 rule_arr
2833 .first()
2834 .and_then(|v| v.as_str())
2835 .unwrap_or("unknown")
2836 .to_string()
2837 } else {
2838 "unknown".to_string()
2839 };
2840
2841 let rule_keys: Vec<String> = rule
2842 .as_object()
2843 .map(|obj| obj.keys().cloned().collect())
2844 .unwrap_or_default();
2845
2846 let mut plasticity_flag = rule
2848 .as_object()
2849 .and_then(|obj| obj.get("plasticity_flag"))
2850 .and_then(|v| v.as_bool())
2851 .unwrap_or(false);
2852 if morphology_id == "associative_memory" {
2853 plasticity_flag = true;
2854 }
2855 if plasticity_flag {
2856 let Some(rule_obj) = rule.as_object() else {
2857 return Err(crate::types::BduError::InvalidMorphology(
2858 "Plasticity mapping rule must be an object format".to_string(),
2859 ));
2860 };
2861 let (_weight, psp, synapse_type, _delay_bursts) =
2862 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
2863 let bidirectional_stdp = morphology_id == "associative_memory";
2864 if let Err(e) = Self::register_stdp_mapping_for_rule(
2865 &npu_arc,
2866 src_area_id,
2867 dst_area_id,
2868 src_cortical_idx,
2869 dst_cortical_idx,
2870 rule_obj,
2871 bidirectional_stdp,
2872 psp,
2873 synapse_type,
2874 ) {
2875 tracing::error!(
2876 target: "feagi-bdu",
2877 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
2878 src_area_id,
2879 dst_area_id,
2880 morphology_id,
2881 rule_keys,
2882 e
2883 );
2884 return Err(e);
2885 }
2886 }
2887
2888 let synapse_count = match self.apply_single_morphology_rule(
2890 src_area_id,
2891 dst_area_id,
2892 rule,
2893 ) {
2894 Ok(count) => count,
2895 Err(e) => {
2896 tracing::error!(
2897 target: "feagi-bdu",
2898 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
2899 src_area_id,
2900 dst_area_id,
2901 morphology_id,
2902 rule_keys,
2903 e
2904 );
2905 return Err(e);
2906 }
2907 };
2908 total_synapses += synapse_count;
2909 tracing::debug!(
2910 target: "feagi-bdu",
2911 "Rule {} created {} synapses for {} -> {}",
2912 morphology_id,
2913 synapse_count,
2914 src_area_id,
2915 dst_area_id
2916 );
2917 }
2918
2919 Ok(total_synapses)
2920 }
2921
2922 #[allow(clippy::too_many_arguments)]
2936 fn apply_function_morphology(
2937 &self,
2938 morphology_id: &str,
2939 rule: &serde_json::Value,
2940 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2941 npu: &mut feagi_npu_burst_engine::DynamicNPU,
2942 src_area_id: &CorticalID,
2943 dst_area_id: &CorticalID,
2944 src_idx: u32,
2945 dst_idx: u32,
2946 weight: f32,
2947 psp: f32,
2948 synapse_attractivity: u8,
2949 synapse_type: feagi_npu_neural::SynapseType,
2950 delay_bursts: u8,
2951 ) -> BduResult<usize> {
2952 match morphology_id {
2953 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
2954 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2956 crate::types::BduError::InvalidArea(format!(
2957 "Source area not found: {}",
2958 src_area_id
2959 ))
2960 })?;
2961 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
2962 crate::types::BduError::InvalidArea(format!(
2963 "Destination area not found: {}",
2964 dst_area_id
2965 ))
2966 })?;
2967
2968 let src_dimensions = (
2969 src_area.dimensions.width as usize,
2970 src_area.dimensions.height as usize,
2971 src_area.dimensions.depth as usize,
2972 );
2973 let dst_dimensions = (
2974 dst_area.dimensions.width as usize,
2975 dst_area.dimensions.height as usize,
2976 dst_area.dimensions.depth as usize,
2977 );
2978
2979 let transpose = match morphology_id {
2982 "transpose_xy" => Some((1, 0, 2)),
2983 "transpose_yz" => Some((0, 2, 1)),
2984 "transpose_xz" => Some((2, 1, 0)),
2985 _ => None,
2986 };
2987
2988 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
2989 let count = apply_projector_morphology_with_dimensions(
2990 npu,
2991 src_idx,
2992 dst_idx,
2993 src_dimensions,
2994 dst_dimensions,
2995 transpose,
2996 None, weight,
2998 psp,
2999 synapse_attractivity,
3000 synapse_type,
3001 0,
3002 delay_bursts,
3003 )?;
3004 npu.rebuild_synapse_index();
3006 Ok(count as usize)
3007 }
3008 "episodic_memory" => {
3009 use tracing::trace;
3012 trace!(
3013 target: "feagi-bdu",
3014 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
3015 src_idx, dst_idx
3016 );
3017 Ok(0)
3018 }
3019 "memory_replay" => {
3020 use tracing::trace;
3022 trace!(
3023 target: "feagi-bdu",
3024 "Memory replay morphology: {} -> {} (no physical synapses)",
3025 src_idx, dst_idx
3026 );
3027 Ok(0)
3028 }
3029 "associative_memory" => {
3030 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3034 crate::types::BduError::InvalidArea(format!(
3035 "Source area not found: {}",
3036 src_area_id
3037 ))
3038 })?;
3039 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3040 crate::types::BduError::InvalidArea(format!(
3041 "Destination area not found: {}",
3042 dst_area_id
3043 ))
3044 })?;
3045
3046 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3047 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3048 {
3049 let src_dimensions = (
3050 src_area.dimensions.width as usize,
3051 src_area.dimensions.height as usize,
3052 src_area.dimensions.depth as usize,
3053 );
3054 let dst_dimensions = (
3055 dst_area.dimensions.width as usize,
3056 dst_area.dimensions.height as usize,
3057 dst_area.dimensions.depth as usize,
3058 );
3059 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3060 let count = apply_projector_morphology_with_dimensions(
3061 npu,
3062 src_idx,
3063 dst_idx,
3064 src_dimensions,
3065 dst_dimensions,
3066 None,
3067 None,
3068 weight,
3069 psp,
3070 synapse_attractivity,
3071 synapse_type,
3072 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
3073 delay_bursts,
3074 )?;
3075 npu.rebuild_synapse_index();
3076 Ok(count as usize)
3077 } else {
3078 Ok(0)
3079 }
3080 }
3081 "block_to_block" => {
3082 tracing::warn!(
3083 target: "feagi-bdu",
3084 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
3085 src_idx, dst_idx
3086 );
3087 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3089 crate::types::BduError::InvalidArea(format!(
3090 "Source area not found: {}",
3091 src_area_id
3092 ))
3093 })?;
3094 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3095 crate::types::BduError::InvalidArea(format!(
3096 "Destination area not found: {}",
3097 dst_area_id
3098 ))
3099 })?;
3100
3101 let src_dimensions = (
3102 src_area.dimensions.width as usize,
3103 src_area.dimensions.height as usize,
3104 src_area.dimensions.depth as usize,
3105 );
3106 let dst_dimensions = (
3107 dst_area.dimensions.width as usize,
3108 dst_area.dimensions.height as usize,
3109 dst_area.dimensions.depth as usize,
3110 );
3111
3112 let scalar = if let Some(obj) = rule.as_object() {
3114 if let Some(scalar_arr) =
3116 obj.get("morphology_scalar").and_then(|v| v.as_array())
3117 {
3118 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
3120 } else {
3121 1 }
3123 } else if let Some(arr) = rule.as_array() {
3124 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
3126 } else {
3127 1 };
3129
3130 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
3133 let count = if estimated_neurons > 100_000 {
3134 let _ = npu;
3136
3137 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
3138 npu_arc,
3139 src_idx,
3140 dst_idx,
3141 src_dimensions,
3142 dst_dimensions,
3143 scalar, weight,
3145 psp,
3146 synapse_attractivity,
3147 synapse_type,
3148 delay_bursts,
3149 )? as usize
3150 } else {
3151 tracing::warn!(
3153 target: "feagi-bdu",
3154 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
3155 src_idx, dst_idx, src_dimensions, dst_dimensions
3156 );
3157 let count =
3158 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
3159 npu,
3160 src_idx,
3161 dst_idx,
3162 src_dimensions,
3163 dst_dimensions,
3164 scalar, weight,
3166 psp,
3167 synapse_attractivity,
3168 synapse_type,
3169 delay_bursts,
3170 )? as usize;
3171 tracing::warn!(
3172 target: "feagi-bdu",
3173 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
3174 count
3175 );
3176 if count > 0 {
3178 npu.rebuild_synapse_index();
3179 }
3180 count
3181 };
3182
3183 if count > 0 && estimated_neurons > 100_000 {
3185 let mut npu_lock = npu_arc.lock().unwrap();
3186 npu_lock.rebuild_synapse_index();
3187 }
3188
3189 Ok(count)
3190 }
3191 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
3192 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
3193 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3194 crate::types::BduError::InvalidArea(format!(
3195 "Source area not found: {}",
3196 src_area_id
3197 ))
3198 })?;
3199 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3200 crate::types::BduError::InvalidArea(format!(
3201 "Destination area not found: {}",
3202 dst_area_id
3203 ))
3204 })?;
3205
3206 let src_dimensions = (
3207 src_area.dimensions.width as usize,
3208 src_area.dimensions.height as usize,
3209 src_area.dimensions.depth as usize,
3210 );
3211 let dst_dimensions = (
3212 dst_area.dimensions.width as usize,
3213 dst_area.dimensions.height as usize,
3214 dst_area.dimensions.depth as usize,
3215 );
3216
3217 let (axis, mode) = match morphology_id {
3218 "bitmask_encoder_x" => (
3219 crate::connectivity::core_morphologies::BitmaskAxis::X,
3220 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3221 ),
3222 "bitmask_encoder_y" => (
3223 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3224 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3225 ),
3226 "bitmask_encoder_z" => (
3227 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3228 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3229 ),
3230 "bitmask_decoder_x" => (
3231 crate::connectivity::core_morphologies::BitmaskAxis::X,
3232 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3233 ),
3234 "bitmask_decoder_y" => (
3235 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3236 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3237 ),
3238 "bitmask_decoder_z" => (
3239 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3240 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3241 ),
3242 _ => unreachable!("matched bitmask morphology above"),
3243 };
3244
3245 let count =
3246 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
3247 npu,
3248 src_idx,
3249 dst_idx,
3250 src_dimensions,
3251 dst_dimensions,
3252 axis,
3253 mode,
3254 weight,
3255 psp,
3256 synapse_attractivity,
3257 synapse_type,
3258 delay_bursts,
3259 )?;
3260 if count > 0 {
3261 npu.rebuild_synapse_index();
3262 }
3263 Ok(count as usize)
3264 }
3265 "sweeper" => {
3266 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3267 crate::types::BduError::InvalidArea(format!(
3268 "Destination area not found: {}",
3269 dst_area_id
3270 ))
3271 })?;
3272 let dst_dimensions = (
3273 dst_area.dimensions.width as usize,
3274 dst_area.dimensions.height as usize,
3275 dst_area.dimensions.depth as usize,
3276 );
3277
3278 let count =
3279 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3280 npu,
3281 src_idx,
3282 dst_idx,
3283 dst_dimensions,
3284 weight,
3285 psp,
3286 synapse_attractivity,
3287 synapse_type,
3288 delay_bursts,
3289 )?;
3290 if count > 0 {
3291 npu.rebuild_synapse_index();
3292 }
3293 Ok(count as usize)
3294 }
3295 "last_to_first" => {
3296 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3297 crate::types::BduError::InvalidArea(format!(
3298 "Source area not found: {}",
3299 src_area_id
3300 ))
3301 })?;
3302 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3303 crate::types::BduError::InvalidArea(format!(
3304 "Destination area not found: {}",
3305 dst_area_id
3306 ))
3307 })?;
3308 let src_dimensions = (
3309 src_area.dimensions.width as usize,
3310 src_area.dimensions.height as usize,
3311 src_area.dimensions.depth as usize,
3312 );
3313 let dst_dimensions = (
3314 dst_area.dimensions.width as usize,
3315 dst_area.dimensions.height as usize,
3316 dst_area.dimensions.depth as usize,
3317 );
3318
3319 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3320 npu,
3321 src_idx,
3322 dst_idx,
3323 src_dimensions,
3324 dst_dimensions,
3325 weight,
3326 psp,
3327 synapse_attractivity,
3328 synapse_type,
3329 delay_bursts,
3330 )?;
3331 if count > 0 {
3332 npu.rebuild_synapse_index();
3333 }
3334 Ok(count as usize)
3335 }
3336 "first_to_last" => {
3337 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3338 crate::types::BduError::InvalidArea(format!(
3339 "Source area not found: {}",
3340 src_area_id
3341 ))
3342 })?;
3343 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3344 crate::types::BduError::InvalidArea(format!(
3345 "Destination area not found: {}",
3346 dst_area_id
3347 ))
3348 })?;
3349 let src_dimensions = (
3350 src_area.dimensions.width as usize,
3351 src_area.dimensions.height as usize,
3352 src_area.dimensions.depth as usize,
3353 );
3354 let dst_dimensions = (
3355 dst_area.dimensions.width as usize,
3356 dst_area.dimensions.height as usize,
3357 dst_area.dimensions.depth as usize,
3358 );
3359
3360 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
3361 npu,
3362 src_idx,
3363 dst_idx,
3364 src_dimensions,
3365 dst_dimensions,
3366 weight,
3367 psp,
3368 synapse_attractivity,
3369 synapse_type,
3370 delay_bursts,
3371 )?;
3372 if count > 0 {
3373 npu.rebuild_synapse_index();
3374 }
3375 Ok(count as usize)
3376 }
3377 "rotator_z" => {
3378 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3379 crate::types::BduError::InvalidArea(format!(
3380 "Source area not found: {}",
3381 src_area_id
3382 ))
3383 })?;
3384 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3385 crate::types::BduError::InvalidArea(format!(
3386 "Destination area not found: {}",
3387 dst_area_id
3388 ))
3389 })?;
3390 let src_dimensions = (
3391 src_area.dimensions.width as usize,
3392 src_area.dimensions.height as usize,
3393 src_area.dimensions.depth as usize,
3394 );
3395 let dst_dimensions = (
3396 dst_area.dimensions.width as usize,
3397 dst_area.dimensions.height as usize,
3398 dst_area.dimensions.depth as usize,
3399 );
3400
3401 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3402 npu,
3403 src_idx,
3404 dst_idx,
3405 src_dimensions,
3406 dst_dimensions,
3407 weight,
3408 psp,
3409 synapse_attractivity,
3410 synapse_type,
3411 delay_bursts,
3412 )?;
3413 if count > 0 {
3414 npu.rebuild_synapse_index();
3415 }
3416 Ok(count as usize)
3417 }
3418 _ => {
3419 use tracing::debug;
3422 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3423 Ok(0)
3424 }
3425 }
3426 }
3427
3428 fn apply_single_morphology_rule(
3430 &mut self,
3431 src_area_id: &CorticalID,
3432 dst_area_id: &CorticalID,
3433 rule: &serde_json::Value,
3434 ) -> BduResult<usize> {
3435 let morphology_id = if let Some(arr) = rule.as_array() {
3437 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3438 } else if let Some(obj) = rule.as_object() {
3439 obj.get("morphology_id")
3440 .and_then(|v| v.as_str())
3441 .unwrap_or("")
3442 } else {
3443 return Ok(0);
3444 };
3445
3446 if morphology_id.is_empty() {
3447 return Ok(0);
3448 }
3449
3450 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3452 crate::types::BduError::InvalidMorphology(format!(
3453 "Morphology not found: {}",
3454 morphology_id
3455 ))
3456 })?;
3457
3458 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3460 crate::types::BduError::InvalidArea(format!(
3461 "Source area ID not found: {}",
3462 src_area_id
3463 ))
3464 })?;
3465 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3466 crate::types::BduError::InvalidArea(format!(
3467 "Destination area ID not found: {}",
3468 dst_area_id
3469 ))
3470 })?;
3471
3472 if let Some(ref npu_arc) = self.npu {
3474 let lock_start = std::time::Instant::now();
3475 let mut npu = npu_arc.lock().unwrap();
3476 let lock_wait = lock_start.elapsed();
3477 tracing::debug!(
3478 target: "feagi-bdu",
3479 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3480 lock_wait.as_secs_f64() * 1000.0,
3481 src_area_id,
3482 dst_area_id,
3483 morphology_id
3484 );
3485
3486 let (weight, psp, synapse_type, delay_bursts) =
3487 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3488
3489 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3491 obj.get("synapse_attractivity")
3492 .and_then(|v| v.as_u64())
3493 .unwrap_or(100) as u8
3494 } else {
3495 100 };
3497
3498 match morphology.morphology_type {
3499 feagi_evolutionary::MorphologyType::Functions => {
3500 tracing::warn!(
3501 target: "feagi-bdu",
3502 "🔍 DEBUG apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3503 morphology_id
3504 );
3505 self.apply_function_morphology(
3508 morphology_id,
3509 rule,
3510 npu_arc,
3511 &mut npu,
3512 src_area_id,
3513 dst_area_id,
3514 *src_idx,
3515 *dst_idx,
3516 weight,
3517 psp,
3518 synapse_attractivity,
3519 synapse_type,
3520 delay_bursts,
3521 )
3522 }
3523 feagi_evolutionary::MorphologyType::Vectors => {
3524 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3525
3526 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3528 crate::types::BduError::InvalidArea(format!(
3529 "Destination area not found: {}",
3530 dst_area_id
3531 ))
3532 })?;
3533
3534 let dst_dimensions = (
3535 dst_area.dimensions.width as usize,
3536 dst_area.dimensions.height as usize,
3537 dst_area.dimensions.depth as usize,
3538 );
3539
3540 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3541 morphology.parameters
3542 {
3543 let vectors_tuples: Vec<(i32, i32, i32)> =
3545 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3546
3547 let count = apply_vectors_morphology_with_dimensions(
3548 &mut npu,
3549 *src_idx,
3550 *dst_idx,
3551 vectors_tuples,
3552 dst_dimensions,
3553 weight, psp, synapse_attractivity, synapse_type,
3557 delay_bursts,
3558 )?;
3559 npu.rebuild_synapse_index();
3562 Ok(count as usize)
3563 } else {
3564 Ok(0)
3565 }
3566 }
3567 feagi_evolutionary::MorphologyType::Patterns => {
3568 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3569 use crate::connectivity::rules::patterns::{
3570 Pattern3D, PatternElement as RulePatternElement,
3571 };
3572 use feagi_evolutionary::PatternElement as EvoPatternElement;
3573
3574 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3575 morphology.parameters
3576 else {
3577 return Ok(0);
3578 };
3579
3580 let convert_element =
3581 |element: &EvoPatternElement|
3582 -> crate::types::BduResult<RulePatternElement> {
3583 match element {
3584 EvoPatternElement::Value(value) => {
3585 if *value < 0 {
3586 return Err(crate::types::BduError::InvalidMorphology(
3587 format!(
3588 "Pattern morphology {} contains negative voxel coordinate {}",
3589 morphology_id, value
3590 ),
3591 ));
3592 }
3593 Ok(RulePatternElement::Exact(*value))
3594 }
3595 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3596 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3597 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3598 }
3599 };
3600
3601 let mut converted_patterns = Vec::with_capacity(patterns.len());
3602 for pattern_pair in patterns {
3603 if pattern_pair.len() != 2 {
3604 return Err(crate::types::BduError::InvalidMorphology(format!(
3605 "Pattern morphology {} must contain [src, dst] pairs",
3606 morphology_id
3607 )));
3608 }
3609
3610 let src_pattern = &pattern_pair[0];
3611 let dst_pattern = &pattern_pair[1];
3612
3613 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3614 return Err(crate::types::BduError::InvalidMorphology(format!(
3615 "Pattern morphology {} requires 3-axis patterns",
3616 morphology_id
3617 )));
3618 }
3619
3620 let src: Pattern3D = (
3621 convert_element(&src_pattern[0])?,
3622 convert_element(&src_pattern[1])?,
3623 convert_element(&src_pattern[2])?,
3624 );
3625 let dst: Pattern3D = (
3626 convert_element(&dst_pattern[0])?,
3627 convert_element(&dst_pattern[1])?,
3628 convert_element(&dst_pattern[2])?,
3629 );
3630
3631 converted_patterns.push((src, dst));
3632 }
3633
3634 let count = apply_patterns_morphology(
3635 &mut npu,
3636 *src_idx,
3637 *dst_idx,
3638 converted_patterns,
3639 weight,
3640 psp,
3641 synapse_attractivity,
3642 synapse_type,
3643 delay_bursts,
3644 )?;
3645 if count > 0 {
3646 npu.rebuild_synapse_index();
3647 }
3648 Ok(count as usize)
3649 }
3650 feagi_evolutionary::MorphologyType::Composite => {
3651 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3652 morphology.parameters
3653 else {
3654 return Ok(0);
3655 };
3656
3657 if morphology_id != "tile" {
3658 use tracing::debug;
3659 debug!(
3660 target: "feagi-bdu",
3661 "Composite morphology {} not yet implemented",
3662 morphology_id
3663 );
3664 return Ok(0);
3665 }
3666
3667 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3668 crate::types::BduError::InvalidArea(format!(
3669 "Source area not found: {}",
3670 src_area_id
3671 ))
3672 })?;
3673 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3674 crate::types::BduError::InvalidArea(format!(
3675 "Destination area not found: {}",
3676 dst_area_id
3677 ))
3678 })?;
3679 let src_dimensions = (
3680 src_area.dimensions.width as usize,
3681 src_area.dimensions.height as usize,
3682 src_area.dimensions.depth as usize,
3683 );
3684 let dst_dimensions = (
3685 dst_area.dimensions.width as usize,
3686 dst_area.dimensions.height as usize,
3687 dst_area.dimensions.depth as usize,
3688 );
3689
3690 let count =
3691 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3692 &mut npu,
3693 *src_idx,
3694 *dst_idx,
3695 src_dimensions,
3696 dst_dimensions,
3697 weight,
3698 psp,
3699 synapse_attractivity,
3700 synapse_type,
3701 delay_bursts,
3702 )?;
3703 if count > 0 {
3704 npu.rebuild_synapse_index();
3705 }
3706 Ok(count as usize)
3707 }
3708 }
3709 } else {
3710 Ok(0) }
3712 }
3713
3714 pub fn set_npu(
3727 &mut self,
3728 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3729 ) {
3730 self.npu = Some(Arc::clone(&npu));
3731 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
3732
3733 #[cfg(not(feature = "wasm"))]
3736 {
3737 use feagi_state_manager::StateManager;
3738 let state_manager = StateManager::instance();
3739 let state_manager = state_manager.read();
3740 let core_state = state_manager.get_core_state();
3741 core_state.set_neuron_capacity(self.config.max_neurons as u32);
3743 core_state.set_synapse_capacity(self.config.max_synapses as u32);
3744 info!(
3745 target: "feagi-bdu",
3746 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
3747 self.config.max_neurons, self.config.max_synapses
3748 );
3749 }
3750
3751 let existing_area_count = self.cortical_id_to_idx.len();
3758 if existing_area_count > 0 {
3759 match npu.lock() {
3760 Ok(mut npu_lock) => {
3761 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
3762 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
3763 }
3764 info!(
3765 target: "feagi-bdu",
3766 "🔁 Backfilled {} cortical area registrations into NPU",
3767 existing_area_count
3768 );
3769 }
3770 Err(e) => {
3771 warn!(
3772 target: "feagi-bdu",
3773 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
3774 e
3775 );
3776 }
3777 }
3778 }
3779
3780 self.update_all_cached_stats();
3782 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
3783 self.get_neuron_count(), self.get_synapse_count());
3784 }
3785
3786 pub fn has_npu(&self) -> bool {
3788 self.npu.is_some()
3789 }
3790
3791 pub fn get_npu(
3798 &self,
3799 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
3800 {
3801 self.npu.as_ref()
3802 }
3803
3804 #[cfg(feature = "plasticity")]
3807 pub fn set_plasticity_executor(
3808 &mut self,
3809 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
3810 ) {
3811 self.plasticity_executor = Some(executor);
3812 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
3813 }
3814
3815 #[cfg(feature = "plasticity")]
3817 pub fn get_plasticity_executor(
3818 &self,
3819 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
3820 self.plasticity_executor.as_ref()
3821 }
3822
3823 pub fn get_neuron_capacity(&self) -> usize {
3835 self.config.max_neurons
3837 }
3838
3839 pub fn get_synapse_capacity(&self) -> usize {
3851 self.config.max_synapses
3853 }
3854
3855 pub fn update_fatigue_index(&self) -> Option<u8> {
3870 let mut last_calc = match self.last_fatigue_calculation.lock() {
3872 Ok(guard) => guard,
3873 Err(_) => return None, };
3875
3876 let now = std::time::Instant::now();
3877 if now.duration_since(*last_calc).as_secs() < 2 {
3878 return None; }
3880 *last_calc = now;
3881 drop(last_calc);
3882
3883 let regular_neuron_count = self.get_neuron_count();
3885 let regular_neuron_capacity = self.get_neuron_capacity();
3886 let regular_neuron_util = if regular_neuron_capacity > 0 {
3887 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
3888 } else {
3889 0
3890 };
3891
3892 let memory_neuron_util = match StateManager::instance().try_read() {
3896 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
3897 None => {
3898 return None;
3900 }
3901 };
3902
3903 let synapse_count = self.get_synapse_count();
3905 let synapse_capacity = self.get_synapse_capacity();
3906 let synapse_util = if synapse_capacity > 0 {
3907 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
3908 } else {
3909 0
3910 };
3911
3912 let fatigue_index = regular_neuron_util
3914 .max(memory_neuron_util)
3915 .max(synapse_util);
3916
3917 let current_fatigue_active = {
3919 StateManager::instance()
3921 .try_read()
3922 .map(|m| m.get_core_state().is_fatigue_active())
3923 .unwrap_or(false)
3924 };
3925
3926 let new_fatigue_active = if fatigue_index >= 85 {
3927 true
3928 } else if fatigue_index < 80 {
3929 false
3930 } else {
3931 current_fatigue_active };
3933
3934 if let Some(state_manager) = StateManager::instance().try_write() {
3938 let core_state = state_manager.get_core_state();
3939 core_state.set_fatigue_index(fatigue_index);
3940 core_state.set_fatigue_active(new_fatigue_active);
3941 core_state.set_regular_neuron_util(regular_neuron_util);
3942 core_state.set_memory_neuron_util(memory_neuron_util);
3943 core_state.set_synapse_util(synapse_util);
3944 } else {
3945 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
3947 }
3948
3949 if let Some(ref npu) = self.npu {
3951 if let Ok(mut npu_lock) = npu.lock() {
3952 npu_lock.set_fatigue_active(new_fatigue_active);
3953 }
3954 }
3955
3956 trace!(
3957 target: "feagi-bdu",
3958 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
3959 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
3960 );
3961
3962 Some(fatigue_index)
3963 }
3964
3965 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
3982 let area = self
3984 .cortical_areas
3985 .get(cortical_id)
3986 .ok_or_else(|| {
3987 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
3988 })?
3989 .clone();
3990
3991 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
3993 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
3994 })?;
3995
3996 let npu = self
3998 .npu
3999 .as_ref()
4000 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4001
4002 use crate::models::CorticalAreaExt;
4005 let per_voxel_cnt = area.neurons_per_voxel();
4006 let firing_threshold = area.firing_threshold();
4007 let firing_threshold_increment_x = area.firing_threshold_increment_x();
4008 let firing_threshold_increment_y = area.firing_threshold_increment_y();
4009 let firing_threshold_increment_z = area.firing_threshold_increment_z();
4010 let firing_threshold_limit_raw = area.firing_threshold_limit();
4012 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
4013 f32::MAX } else {
4015 firing_threshold_limit_raw
4016 };
4017
4018 if firing_threshold_increment_x != 0.0
4020 || firing_threshold_increment_y != 0.0
4021 || firing_threshold_increment_z != 0.0
4022 {
4023 info!(
4024 target: "feagi-bdu",
4025 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
4026 cortical_id.as_base_64(),
4027 firing_threshold_increment_x,
4028 firing_threshold_increment_y,
4029 firing_threshold_increment_z
4030 );
4031 } else {
4032 if area.properties.contains_key("firing_threshold_increment_x")
4034 || area.properties.contains_key("firing_threshold_increment_y")
4035 || area.properties.contains_key("firing_threshold_increment_z")
4036 {
4037 info!(
4038 target: "feagi-bdu",
4039 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
4040 cortical_id.as_base_64(),
4041 area.properties.get("firing_threshold_increment_x"),
4042 area.properties.get("firing_threshold_increment_y"),
4043 area.properties.get("firing_threshold_increment_z")
4044 );
4045 }
4046 }
4047
4048 let leak_coefficient = area.leak_coefficient();
4049 let excitability = area.neuron_excitability();
4050 let refractory_period = area.refractory_period();
4051 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
4053 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
4054 u16::MAX } else {
4056 consecutive_fire_limit_raw
4057 };
4058 let snooze_length = area.snooze_period();
4059 let mp_charge_accumulation = area.mp_charge_accumulation();
4060
4061 let voxels = area.dimensions.width as usize
4063 * area.dimensions.height as usize
4064 * area.dimensions.depth as usize;
4065 let expected_neurons = voxels * per_voxel_cnt as usize;
4066
4067 trace!(
4068 target: "feagi-bdu",
4069 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
4070 cortical_id.as_base_64(),
4071 area.dimensions.width,
4072 area.dimensions.height,
4073 area.dimensions.depth,
4074 per_voxel_cnt,
4075 expected_neurons
4076 );
4077
4078 let neuron_count: u32 = {
4083 let mut npu_lock = npu
4084 .lock()
4085 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4086 npu_lock
4087 .create_cortical_area_neurons(
4088 *cortical_idx,
4089 area.dimensions.width,
4090 area.dimensions.height,
4091 area.dimensions.depth,
4092 per_voxel_cnt,
4093 firing_threshold,
4094 firing_threshold_increment_x,
4095 firing_threshold_increment_y,
4096 firing_threshold_increment_z,
4097 firing_threshold_limit,
4098 leak_coefficient,
4099 0.0, 0, refractory_period,
4102 excitability,
4103 consecutive_fire_limit,
4104 snooze_length,
4105 mp_charge_accumulation,
4106 )
4107 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
4108 };
4109
4110 trace!(
4111 target: "feagi-bdu",
4112 "Created {} neurons for area {} via NPU",
4113 neuron_count,
4114 cortical_id.as_base_64()
4115 );
4116
4117 {
4120 let mut cache = self.cached_neuron_counts_per_area.write();
4121 cache
4122 .entry(*cortical_id)
4123 .or_insert_with(|| AtomicUsize::new(0))
4124 .store(neuron_count as usize, Ordering::Relaxed);
4125 }
4126
4127 let state_manager = StateManager::instance();
4129 let state_manager = state_manager.read();
4130 state_manager
4131 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
4132
4133 self.cached_neuron_count
4135 .fetch_add(neuron_count as usize, Ordering::Relaxed);
4136
4137 let state_manager = StateManager::instance();
4139 let state_manager = state_manager.read();
4140 let core_state = state_manager.get_core_state();
4141 core_state.add_neuron_count(neuron_count);
4142 core_state.add_regular_neuron_count(neuron_count);
4143
4144 if let Some(npu) = &self.npu {
4146 if let Ok(mut npl) = npu.lock() {
4147 if let Some(v) = area.properties.get("rate_modulated_leak") {
4148 use crate::models::CorticalAreaExt;
4149 let idxs: Vec<usize> = npl
4150 .get_neurons_in_cortical_area(*cortical_idx)
4151 .into_iter()
4152 .map(|id| id as usize)
4153 .collect();
4154 npl.sync_rate_modulated_leak_from_cortical_property(
4155 *cortical_idx,
4156 v,
4157 area.leak_coefficient(),
4158 idxs,
4159 );
4160 } else {
4161 npl.remove_rate_modulated_leak(*cortical_idx);
4162 }
4163 }
4164 }
4165
4166 Ok(neuron_count)
4174 }
4175
4176 #[allow(clippy::too_many_arguments)]
4200 pub fn add_neuron(
4201 &mut self,
4202 cortical_id: &CorticalID,
4203 x: u32,
4204 y: u32,
4205 z: u32,
4206 firing_threshold: f32,
4207 firing_threshold_limit: f32,
4208 leak_coefficient: f32,
4209 resting_potential: f32,
4210 neuron_type: u8,
4211 refractory_period: u16,
4212 excitability: f32,
4213 consecutive_fire_limit: u16,
4214 snooze_length: u16,
4215 mp_charge_accumulation: bool,
4216 ) -> BduResult<u64> {
4217 if !self.cortical_areas.contains_key(cortical_id) {
4219 return Err(BduError::InvalidArea(format!(
4220 "Cortical area {} not found",
4221 cortical_id
4222 )));
4223 }
4224
4225 let cortical_idx = *self
4226 .cortical_id_to_idx
4227 .get(cortical_id)
4228 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
4229
4230 let npu = self
4232 .npu
4233 .as_ref()
4234 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4235
4236 let mut npu_lock = npu
4237 .lock()
4238 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4239
4240 let neuron_id = npu_lock
4242 .add_neuron(
4243 firing_threshold,
4244 firing_threshold_limit,
4245 leak_coefficient,
4246 resting_potential,
4247 neuron_type as i32,
4248 refractory_period,
4249 excitability,
4250 consecutive_fire_limit,
4251 snooze_length,
4252 mp_charge_accumulation,
4253 cortical_idx,
4254 x,
4255 y,
4256 z,
4257 )
4258 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
4259
4260 trace!(
4261 target: "feagi-bdu",
4262 "Created neuron {} in area {} at ({}, {}, {})",
4263 neuron_id.0,
4264 cortical_id,
4265 x,
4266 y,
4267 z
4268 );
4269
4270 let state_manager = StateManager::instance();
4272 let state_manager = state_manager.read();
4273 let core_state = state_manager.get_core_state();
4274 core_state.add_neuron_count(1);
4275 core_state.add_regular_neuron_count(1);
4276 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4277
4278 Ok(neuron_id.0 as u64)
4279 }
4280
4281 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
4292 let npu = self
4294 .npu
4295 .as_ref()
4296 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4297
4298 let mut npu_lock = npu
4299 .lock()
4300 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4301
4302 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
4303 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
4304
4305 let deleted = npu_lock.delete_neuron(neuron_id as u32);
4306
4307 if deleted {
4308 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
4309
4310 let state_manager = StateManager::instance();
4312 let state_manager = state_manager.read();
4313 let core_state = state_manager.get_core_state();
4314 core_state.subtract_neuron_count(1);
4315 core_state.subtract_regular_neuron_count(1);
4316 if let Some(cortical_id) = cortical_id {
4317 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4318 }
4319
4320 }
4324
4325 Ok(deleted)
4326 }
4327
4328 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
4342 let src_area = self
4344 .cortical_areas
4345 .get(src_cortical_id)
4346 .ok_or_else(|| {
4347 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
4348 })?
4349 .clone();
4350
4351 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
4353 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
4354 _ => return Ok(0), };
4356
4357 let src_cortical_idx = *self
4358 .cortical_id_to_idx
4359 .get(src_cortical_id)
4360 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
4361
4362 let mut total_synapses = 0u32;
4363 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
4367 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
4369 Ok(id) => id,
4370 Err(_) => {
4371 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
4372 continue;
4373 }
4374 };
4375
4376 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
4378 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
4379 continue;
4380 }
4381
4382 let synapse_count =
4384 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
4385 total_synapses += synapse_count as u32;
4386
4387 upstream_updates.push((dst_cortical_id, src_cortical_idx));
4390 }
4391
4392 for (dst_id, src_idx) in upstream_updates {
4394 self.add_upstream_area(&dst_id, src_idx);
4395 }
4396
4397 trace!(
4398 target: "feagi-bdu",
4399 "Created {} synapses for area {} via NPU",
4400 total_synapses,
4401 src_cortical_id
4402 );
4403
4404 if total_synapses > 0 {
4407 let mut cache = self.cached_synapse_counts_per_area.write();
4408 cache
4409 .entry(*src_cortical_id)
4410 .or_insert_with(|| AtomicUsize::new(0))
4411 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4412 }
4413
4414 self.cached_synapse_count
4416 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4417
4418 if total_synapses > 0 {
4420 let state_manager = StateManager::instance();
4421 let state_manager = state_manager.read();
4422 let core_state = state_manager.get_core_state();
4423 core_state.add_synapse_count(total_synapses);
4424 }
4425
4426 Ok(total_synapses)
4427 }
4428
4429 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4448 if let Some(ref npu) = self.npu {
4449 if let Ok(npu_lock) = npu.lock() {
4450 npu_lock.is_neuron_valid(neuron_id as u32)
4452 } else {
4453 false
4454 }
4455 } else {
4456 false
4457 }
4458 }
4459
4460 pub fn get_neuron_count(&self) -> usize {
4472 if let Some(ref npu) = self.npu {
4474 if let Ok(npu_lock) = npu.try_lock() {
4475 let fresh_count = npu_lock.get_neuron_count();
4476 self.cached_neuron_count
4477 .store(fresh_count, Ordering::Relaxed);
4478 }
4479 }
4481
4482 self.cached_neuron_count.load(Ordering::Relaxed)
4484 }
4485
4486 pub fn update_cached_neuron_count(&self) {
4492 if let Some(ref npu) = self.npu {
4493 if let Ok(npu_lock) = npu.try_lock() {
4494 let count = npu_lock.get_neuron_count();
4495 self.cached_neuron_count.store(count, Ordering::Relaxed);
4496 }
4497 }
4498 }
4499
4500 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4504 let npu = self.npu.as_ref()?;
4505 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4506 let npu_lock = npu.lock().ok()?;
4507 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4508 drop(npu_lock);
4509
4510 let mut cache = self.cached_neuron_counts_per_area.write();
4511 cache
4512 .entry(*cortical_id)
4513 .or_insert_with(|| AtomicUsize::new(0))
4514 .store(count, Ordering::Relaxed);
4515
4516 let state_manager = StateManager::instance();
4518 let state_manager = state_manager.read();
4519 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4520
4521 self.update_cached_neuron_count();
4522
4523 Some(count)
4524 }
4525
4526 pub fn get_synapse_count(&self) -> usize {
4538 if let Some(ref npu) = self.npu {
4540 if let Ok(npu_lock) = npu.try_lock() {
4541 let fresh_count = npu_lock.get_synapse_count();
4542 self.cached_synapse_count
4543 .store(fresh_count, Ordering::Relaxed);
4544 }
4545 }
4547
4548 self.cached_synapse_count.load(Ordering::Relaxed)
4550 }
4551
4552 pub fn update_cached_synapse_count(&self) {
4558 if let Some(ref npu) = self.npu {
4559 if let Ok(npu_lock) = npu.try_lock() {
4560 let count = npu_lock.get_synapse_count();
4561 self.cached_synapse_count.store(count, Ordering::Relaxed);
4562 }
4563 }
4564 }
4565
4566 pub fn update_all_cached_stats(&self) {
4572 self.update_cached_neuron_count();
4573 self.update_cached_synapse_count();
4574 }
4575
4576 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4587 #[cfg(feature = "plasticity")]
4592 {
4593 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4594 return (0, 0, 0);
4595 }
4596 }
4597 if let Some(ref npu) = self.npu {
4598 if let Ok(npu_lock) = npu.lock() {
4599 npu_lock
4600 .get_neuron_coordinates(neuron_id as u32)
4601 .unwrap_or((0, 0, 0))
4602 } else {
4603 (0, 0, 0)
4604 }
4605 } else {
4606 (0, 0, 0)
4607 }
4608 }
4609
4610 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
4621 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
4622 }
4623
4624 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
4630 #[cfg(feature = "plasticity")]
4631 {
4632 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4633 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
4634 }
4635 }
4636 if let Some(ref npu) = self.npu {
4637 if let Ok(npu_lock) = npu.lock() {
4638 npu_lock.get_neuron_cortical_area(neuron_id as u32)
4639 } else {
4640 None
4641 }
4642 } else {
4643 None
4644 }
4645 }
4646
4647 #[cfg(feature = "plasticity")]
4649 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
4650 let exec = self.get_plasticity_executor()?;
4651 let guard = exec.lock().ok()?;
4652 guard
4653 .memory_neuron_detail(neuron_id)
4654 .map(|d| d.cortical_area_idx)
4655 }
4656
4657 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
4668 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
4670 Some(idx) => *idx,
4671 None => return Vec::new(),
4672 };
4673
4674 if let Some(ref npu) = self.npu {
4675 if let Ok(npu_lock) = npu.lock() {
4676 npu_lock
4678 .get_neurons_in_cortical_area(cortical_idx)
4679 .into_iter()
4680 .map(|id| id as u64)
4681 .collect()
4682 } else {
4683 Vec::new()
4684 }
4685 } else {
4686 Vec::new()
4687 }
4688 }
4689
4690 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4701 if let Some(ref npu) = self.npu {
4702 if let Ok(npu_lock) = npu.lock() {
4703 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
4704 } else {
4705 Vec::new()
4706 }
4707 } else {
4708 Vec::new()
4709 }
4710 }
4711
4712 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
4723 if let Some(ref npu) = self.npu {
4724 if let Ok(npu_lock) = npu.lock() {
4725 npu_lock.get_incoming_synapses(target_neuron_id as u32)
4726 } else {
4727 Vec::new()
4728 }
4729 } else {
4730 Vec::new()
4731 }
4732 }
4733
4734 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4760 let cache = self.cached_neuron_counts_per_area.read();
4762 let base_count = cache
4763 .get(cortical_id)
4764 .map(|count| count.load(Ordering::Relaxed))
4765 .unwrap_or(0);
4766
4767 let memory_count = self
4769 .cortical_areas
4770 .get(cortical_id)
4771 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
4772 .and_then(|_| {
4773 StateManager::instance()
4774 .try_read()
4775 .and_then(|state_manager| {
4776 state_manager.get_cortical_area_stats(&cortical_id.as_base_64())
4777 })
4778 })
4779 .map(|stats| stats.neuron_count)
4780 .unwrap_or(0);
4781
4782 base_count.saturating_add(memory_count)
4783 }
4784
4785 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
4792 let mut result = Vec::new();
4793
4794 for cortical_id in self.cortical_areas.keys() {
4795 let count = self.get_neuron_count_in_area(cortical_id);
4796 if count > 0 {
4797 result.push((cortical_id.to_string(), count));
4798 }
4799 }
4800
4801 result
4802 }
4803
4804 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
4815 self.get_neuron_count_in_area(cortical_id) > 0
4816 }
4817
4818 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4834 let cache = self.cached_synapse_counts_per_area.read();
4836 cache
4837 .get(cortical_id)
4838 .map(|count| count.load(Ordering::Relaxed))
4839 .unwrap_or(0)
4840 }
4841
4842 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4852 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4853 return 0;
4854 }
4855
4856 if let Some(state_manager) = StateManager::instance().try_read() {
4857 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4858 return stats.incoming_synapse_count;
4859 }
4860 }
4861
4862 0
4863 }
4864
4865 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
4875 if !self.cortical_id_to_idx.contains_key(cortical_id) {
4876 return 0;
4877 }
4878
4879 if let Some(state_manager) = StateManager::instance().try_read() {
4880 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
4881 return stats.outgoing_synapse_count;
4882 }
4883 }
4884
4885 0
4886 }
4887
4888 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
4900 let synapses = self.get_outgoing_synapses(source_neuron_id);
4901 synapses
4902 .iter()
4903 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
4904 }
4905
4906 pub fn get_connection_weight(
4918 &self,
4919 source_neuron_id: u64,
4920 target_neuron_id: u64,
4921 ) -> Option<f32> {
4922 let synapses = self.get_outgoing_synapses(source_neuron_id);
4923 synapses
4924 .iter()
4925 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
4926 .map(|(_, weight, _, _)| *weight)
4927 }
4928
4929 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
4940 let neurons = self.get_neurons_in_area(cortical_id);
4941 let neuron_count = neurons.len();
4942
4943 if neuron_count == 0 {
4944 return (0, 0, 0.0);
4945 }
4946
4947 let mut total_synapses = 0;
4948 for neuron_id in neurons {
4949 total_synapses += self.get_outgoing_synapses(neuron_id).len();
4950 }
4951
4952 let avg_synapses = total_synapses as f32 / neuron_count as f32;
4953
4954 (neuron_count, total_synapses, avg_synapses)
4955 }
4956
4957 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
4968 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
4969 self.cortical_idx_to_id.get(&cortical_idx).copied()
4970 }
4971
4972 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
4983 let area = match self.cortical_areas.get(cortical_id) {
4984 Some(a) => a,
4985 None => return 0.0,
4986 };
4987
4988 let neuron_count = self.get_neuron_count_in_area(cortical_id);
4989 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
4990
4991 if volume == 0 {
4992 return 0.0;
4993 }
4994
4995 neuron_count as f32 / volume as f32
4996 }
4997
4998 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
5005 let mut stats = Vec::new();
5006
5007 for cortical_id in self.cortical_areas.keys() {
5008 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5009 let synapse_count = self.get_synapse_count_in_area(cortical_id);
5010 let density = self.get_neuron_density(cortical_id);
5011
5012 stats.push((
5013 cortical_id.to_string(),
5014 neuron_count,
5015 synapse_count,
5016 density,
5017 ));
5018 }
5019
5020 stats
5021 }
5022
5023 pub fn get_config(&self) -> &ConnectomeConfig {
5029 &self.config
5030 }
5031
5032 pub fn set_config(&mut self, config: ConnectomeConfig) {
5034 self.config = config;
5035 }
5036
5037 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
5060 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
5061
5062 use feagi_structures::genomic::cortical_area::{
5063 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
5064 };
5065
5066 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
5068 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
5069 })?;
5070
5071 let core_position = (0, 0, 0).into();
5073
5074 let death_id = CoreCorticalType::Death.to_cortical_id();
5076 if !self.cortical_areas.contains_key(&death_id) {
5077 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
5078 let death_area = CorticalArea::new(
5079 death_id,
5080 0, "_death".to_string(),
5082 core_dimensions,
5083 core_position,
5084 CorticalAreaType::Core(CoreCorticalType::Death),
5085 )
5086 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
5087 match self.add_cortical_area(death_area) {
5088 Ok(idx) => {
5089 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
5090 }
5091 Err(e) => {
5092 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
5093 return Err(e);
5094 }
5095 }
5096 } else {
5097 info!(target: "feagi-bdu", " ✓ _death area already exists");
5098 }
5099
5100 let power_id = CoreCorticalType::Power.to_cortical_id();
5102 if !self.cortical_areas.contains_key(&power_id) {
5103 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
5104 let power_area = CorticalArea::new(
5105 power_id,
5106 1, "_power".to_string(),
5108 core_dimensions,
5109 core_position,
5110 CorticalAreaType::Core(CoreCorticalType::Power),
5111 )
5112 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
5113 match self.add_cortical_area(power_area) {
5114 Ok(idx) => {
5115 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
5116 }
5117 Err(e) => {
5118 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
5119 return Err(e);
5120 }
5121 }
5122 } else {
5123 info!(target: "feagi-bdu", " ✓ _power area already exists");
5124 }
5125
5126 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
5128 let pain_id = CoreCorticalType::Pain.to_cortical_id();
5129 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
5130 let fear_id = CoreCorticalType::Fear.to_cortical_id();
5131 let hope_id = CoreCorticalType::Hope.to_cortical_id();
5132 if !self.cortical_areas.contains_key(&fatigue_id) {
5133 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
5134 let fatigue_area = CorticalArea::new(
5135 fatigue_id,
5136 2, "_fatigue".to_string(),
5138 core_dimensions,
5139 core_position,
5140 CorticalAreaType::Core(CoreCorticalType::Fatigue),
5141 )
5142 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
5143 match self.add_cortical_area(fatigue_area) {
5144 Ok(idx) => {
5145 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
5146 }
5147 Err(e) => {
5148 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
5149 return Err(e);
5150 }
5151 }
5152 } else {
5153 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
5154 }
5155
5156 if !self.cortical_areas.contains_key(&pain_id) {
5158 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
5159 let pain_area = CorticalArea::new(
5160 pain_id,
5161 3, "_pain".to_string(),
5163 core_dimensions,
5164 core_position,
5165 CorticalAreaType::Core(CoreCorticalType::Pain),
5166 )
5167 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
5168 match self.add_cortical_area(pain_area) {
5169 Ok(idx) => {
5170 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
5171 }
5172 Err(e) => {
5173 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
5174 return Err(e);
5175 }
5176 }
5177 } else {
5178 info!(target: "feagi-bdu", " ✓ _pain area already exists");
5179 }
5180
5181 if !self.cortical_areas.contains_key(&pleasure_id) {
5183 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
5184 let pleasure_area = CorticalArea::new(
5185 pleasure_id,
5186 4, "_pleasure".to_string(),
5188 core_dimensions,
5189 core_position,
5190 CorticalAreaType::Core(CoreCorticalType::Pleasure),
5191 )
5192 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
5193 match self.add_cortical_area(pleasure_area) {
5194 Ok(idx) => {
5195 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
5196 }
5197 Err(e) => {
5198 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
5199 return Err(e);
5200 }
5201 }
5202 } else {
5203 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
5204 }
5205
5206 if !self.cortical_areas.contains_key(&fear_id) {
5208 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
5209 let fear_area = CorticalArea::new(
5210 fear_id,
5211 5, "_fear".to_string(),
5213 core_dimensions,
5214 core_position,
5215 CorticalAreaType::Core(CoreCorticalType::Fear),
5216 )
5217 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
5218 match self.add_cortical_area(fear_area) {
5219 Ok(idx) => {
5220 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
5221 }
5222 Err(e) => {
5223 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
5224 return Err(e);
5225 }
5226 }
5227 } else {
5228 info!(target: "feagi-bdu", " ✓ _fear area already exists");
5229 }
5230
5231 if !self.cortical_areas.contains_key(&hope_id) {
5233 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
5234 let hope_area = CorticalArea::new(
5235 hope_id,
5236 6, "_hope".to_string(),
5238 core_dimensions,
5239 core_position,
5240 CorticalAreaType::Core(CoreCorticalType::Hope),
5241 )
5242 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
5243 match self.add_cortical_area(hope_area) {
5244 Ok(idx) => {
5245 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
5246 }
5247 Err(e) => {
5248 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
5249 return Err(e);
5250 }
5251 }
5252 } else {
5253 info!(target: "feagi-bdu", " ✓ _hope area already exists");
5254 }
5255
5256 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
5257 Ok(())
5258 }
5259
5260 #[deprecated(
5277 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
5278 )]
5279 #[allow(deprecated)]
5280 pub fn save_genome_to_json(
5281 &self,
5282 genome_id: Option<String>,
5283 genome_title: Option<String>,
5284 ) -> BduResult<String> {
5285 let mut brain_regions_with_parents = std::collections::HashMap::new();
5287
5288 for region_id in self.brain_regions.get_all_region_ids() {
5289 if let Some(region) = self.brain_regions.get_region(region_id) {
5290 let parent_id = self
5291 .brain_regions
5292 .get_parent(region_id)
5293 .map(|s| s.to_string());
5294 brain_regions_with_parents
5295 .insert(region_id.to_string(), (region.clone(), parent_id));
5296 }
5297 }
5298
5299 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
5301 &self.cortical_areas,
5302 &brain_regions_with_parents,
5303 genome_id,
5304 genome_title,
5305 )?)
5306 }
5307
5308 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
5339 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
5340
5341 self.cortical_areas.clear();
5343 self.cortical_id_to_idx.clear();
5344 self.cortical_idx_to_id.clear();
5345 self.next_cortical_idx = 7;
5347 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)");
5348
5349 self.brain_regions = BrainRegionHierarchy::new();
5351
5352 if let Some(ref npu) = self.npu {
5354 let mut npu_lock = npu
5355 .lock()
5356 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5357 npu_lock
5358 .reset_for_new_genome()
5359 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
5360 }
5361
5362 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
5363 Ok(())
5364 }
5365
5366 pub fn resize_for_genome(
5376 &mut self,
5377 genome: &feagi_evolutionary::RuntimeGenome,
5378 ) -> BduResult<()> {
5379 self.morphology_registry = genome.morphologies.clone();
5381 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
5382
5383 let required_neurons = genome.stats.innate_neuron_count;
5385 let required_synapses = genome.stats.innate_synapse_count;
5386
5387 info!(target: "feagi-bdu",
5388 "Genome requires: {} neurons, {} synapses",
5389 required_neurons,
5390 required_synapses
5391 );
5392
5393 let mut total_voxels = 0;
5395 for area in genome.cortical_areas.values() {
5396 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5397 }
5398
5399 info!(target: "feagi-bdu",
5400 "Genome has {} cortical areas with {} total voxels",
5401 genome.cortical_areas.len(),
5402 total_voxels
5403 );
5404
5405 Ok(())
5410 }
5411
5412 pub fn create_synapse(
5431 &mut self,
5432 source_neuron_id: u64,
5433 target_neuron_id: u64,
5434 weight: f32,
5435 psp: f32,
5436 synapse_type: u8,
5437 ) -> BduResult<()> {
5438 let npu = self
5440 .npu
5441 .as_ref()
5442 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5443
5444 let mut npu_lock = npu
5445 .lock()
5446 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5447
5448 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5450 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5451
5452 if !source_exists {
5453 return Err(BduError::InvalidNeuron(format!(
5454 "Source neuron {} not found",
5455 source_neuron_id
5456 )));
5457 }
5458 if !target_exists {
5459 return Err(BduError::InvalidNeuron(format!(
5460 "Target neuron {} not found",
5461 target_neuron_id
5462 )));
5463 }
5464
5465 let syn_type = if synapse_type == 0 {
5467 feagi_npu_neural::synapse::SynapseType::Excitatory
5468 } else {
5469 feagi_npu_neural::synapse::SynapseType::Inhibitory
5470 };
5471
5472 let synapse_idx = npu_lock
5473 .add_synapse(
5474 NeuronId(source_neuron_id as u32),
5475 NeuronId(target_neuron_id as u32),
5476 feagi_npu_neural::types::SynapticWeight(weight),
5477 feagi_npu_neural::types::SynapticPsp(psp),
5478 syn_type,
5479 0,
5480 1,
5481 )
5482 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
5483
5484 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
5485 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
5486
5487 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5488 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5489 let source_cortical_id =
5490 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5491 let target_cortical_id =
5492 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5493
5494 let state_manager = StateManager::instance();
5495 let state_manager = state_manager.read();
5496 let core_state = state_manager.get_core_state();
5497 core_state.add_synapse_count(1);
5498 if let Some(cortical_id) = source_cortical_id {
5499 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5500 }
5501 if let Some(cortical_id) = target_cortical_id {
5502 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5503 }
5504
5505 Ok(())
5510 }
5511
5512 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
5515 if let Some(ref npu) = self.npu {
5516 if let Ok(mut npu_lock) = npu.lock() {
5517 let mut psp_uniform_flags = ahash::AHashMap::new();
5519 let mut mp_driven_psp_flags = ahash::AHashMap::new();
5520 let mut postsynaptic_current_flags = ahash::AHashMap::new();
5521 let mut degeneration_flags = ahash::AHashMap::new();
5522
5523 for (cortical_id, area) in &self.cortical_areas {
5524 let default_psp_uniform = *cortical_id
5527 == CoreCorticalType::Power.to_cortical_id()
5528 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
5529 let psp_uniform = area
5530 .get_property("psp_uniform_distribution")
5531 .and_then(|v| v.as_bool())
5532 .unwrap_or(default_psp_uniform);
5533 psp_uniform_flags.insert(*cortical_id, psp_uniform);
5534
5535 let mp_driven_psp = area
5537 .get_property("mp_driven_psp")
5538 .and_then(|v| v.as_bool())
5539 .unwrap_or(false);
5540 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
5541
5542 let postsynaptic_current = area
5544 .get_property("postsynaptic_current")
5545 .and_then(|v| v.as_f64())
5546 .unwrap_or(1.0) as f32;
5547 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
5548
5549 let degeneration = area
5551 .get_property("degeneration")
5552 .and_then(|v| v.as_f64())
5553 .unwrap_or(0.0) as f32;
5554 if degeneration > 0.0 {
5555 degeneration_flags.insert(*cortical_id, degeneration);
5556 }
5557 }
5558
5559 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
5561 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5562 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
5563 npu_lock.set_degeneration_flags(degeneration_flags);
5564
5565 trace!(
5566 target: "feagi-bdu",
5567 "Synchronized cortical area flags to NPU ({} areas)",
5568 self.cortical_areas.len()
5569 );
5570 }
5571 }
5572
5573 Ok(())
5574 }
5575
5576 pub fn get_synapse(
5588 &self,
5589 source_neuron_id: u64,
5590 target_neuron_id: u64,
5591 ) -> Option<(f32, f32, u8)> {
5592 let npu = self.npu.as_ref()?;
5594 let npu_lock = npu.lock().ok()?;
5595
5596 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
5599
5600 for (source_id, weight, psp, synapse_type) in incoming {
5602 if source_id == source_neuron_id as u32 {
5603 return Some((weight, psp, synapse_type));
5604 }
5605 }
5606
5607 None
5608 }
5609
5610 pub fn update_synapse_weight(
5623 &mut self,
5624 source_neuron_id: u64,
5625 target_neuron_id: u64,
5626 new_weight: f32,
5627 ) -> BduResult<()> {
5628 let npu = self
5630 .npu
5631 .as_ref()
5632 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5633
5634 let mut npu_lock = npu
5635 .lock()
5636 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5637
5638 let updated = npu_lock.update_synapse_weight(
5640 NeuronId(source_neuron_id as u32),
5641 NeuronId(target_neuron_id as u32),
5642 feagi_npu_neural::types::SynapticWeight(new_weight),
5643 );
5644
5645 if updated {
5646 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
5647 Ok(())
5648 } else {
5649 Err(BduError::InvalidSynapse(format!(
5650 "Synapse {} -> {} not found",
5651 source_neuron_id, target_neuron_id
5652 )))
5653 }
5654 }
5655
5656 pub fn remove_synapse(
5668 &mut self,
5669 source_neuron_id: u64,
5670 target_neuron_id: u64,
5671 ) -> BduResult<bool> {
5672 let npu = self
5674 .npu
5675 .as_ref()
5676 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5677
5678 let mut npu_lock = npu
5679 .lock()
5680 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5681
5682 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5683 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5684 let source_cortical_id =
5685 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5686 let target_cortical_id =
5687 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5688
5689 let removed = npu_lock.remove_synapse(
5691 NeuronId(source_neuron_id as u32),
5692 NeuronId(target_neuron_id as u32),
5693 );
5694
5695 if removed {
5696 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
5697
5698 let state_manager = StateManager::instance();
5700 let state_manager = state_manager.read();
5701 let core_state = state_manager.get_core_state();
5702 core_state.subtract_synapse_count(1);
5703 if let Some(cortical_id) = source_cortical_id {
5704 state_manager
5705 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5706 }
5707 if let Some(cortical_id) = target_cortical_id {
5708 state_manager
5709 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5710 }
5711 }
5712
5713 Ok(removed)
5714 }
5715
5716 pub fn batch_create_neurons(
5734 &mut self,
5735 cortical_id: &CorticalID,
5736 neurons: Vec<NeuronData>,
5737 ) -> BduResult<Vec<u64>> {
5738 let npu = self
5740 .npu
5741 .as_ref()
5742 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5743
5744 let mut npu_lock = npu
5745 .lock()
5746 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5747
5748 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
5750 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
5751 })?;
5752 let cortical_idx = area.cortical_idx;
5753
5754 let count = neurons.len();
5755
5756 let mut x_coords = Vec::with_capacity(count);
5758 let mut y_coords = Vec::with_capacity(count);
5759 let mut z_coords = Vec::with_capacity(count);
5760 let mut firing_thresholds = Vec::with_capacity(count);
5761 let mut threshold_limits = Vec::with_capacity(count);
5762 let mut leak_coeffs = Vec::with_capacity(count);
5763 let mut resting_potentials = Vec::with_capacity(count);
5764 let mut neuron_types = Vec::with_capacity(count);
5765 let mut refractory_periods = Vec::with_capacity(count);
5766 let mut excitabilities = Vec::with_capacity(count);
5767 let mut consec_fire_limits = Vec::with_capacity(count);
5768 let mut snooze_lengths = Vec::with_capacity(count);
5769 let mut mp_accums = Vec::with_capacity(count);
5770 let mut cortical_areas = Vec::with_capacity(count);
5771
5772 for (
5773 x,
5774 y,
5775 z,
5776 threshold,
5777 threshold_limit,
5778 leak,
5779 resting,
5780 ntype,
5781 refract,
5782 excit,
5783 consec_limit,
5784 snooze,
5785 mp_accum,
5786 ) in neurons
5787 {
5788 x_coords.push(x);
5789 y_coords.push(y);
5790 z_coords.push(z);
5791 firing_thresholds.push(threshold);
5792 threshold_limits.push(threshold_limit);
5793 leak_coeffs.push(leak);
5794 resting_potentials.push(resting);
5795 neuron_types.push(ntype);
5796 refractory_periods.push(refract);
5797 excitabilities.push(excit);
5798 consec_fire_limits.push(consec_limit);
5799 snooze_lengths.push(snooze);
5800 mp_accums.push(mp_accum);
5801 cortical_areas.push(cortical_idx);
5802 }
5803
5804 let first_neuron_id = npu_lock.get_neuron_count() as u32;
5806
5807 let firing_thresholds_t = firing_thresholds;
5812 let threshold_limits_t = threshold_limits;
5813 let resting_potentials_t = resting_potentials;
5814 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
5815 firing_thresholds_t,
5816 threshold_limits_t,
5817 leak_coeffs,
5818 resting_potentials_t,
5819 neuron_types,
5820 refractory_periods,
5821 excitabilities,
5822 consec_fire_limits,
5823 snooze_lengths,
5824 mp_accums,
5825 cortical_areas,
5826 x_coords,
5827 y_coords,
5828 z_coords,
5829 );
5830
5831 let mut neuron_ids = Vec::with_capacity(count);
5833 for i in 0..neurons_created {
5834 neuron_ids.push((first_neuron_id + i) as u64);
5835 }
5836
5837 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
5838
5839 let state_manager = StateManager::instance();
5841 let state_manager = state_manager.read();
5842 let core_state = state_manager.get_core_state();
5843 core_state.add_neuron_count(neurons_created);
5844 core_state.add_regular_neuron_count(neurons_created);
5845 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
5846
5847 {
5849 let mut cache = self.cached_neuron_counts_per_area.write();
5850 cache
5851 .entry(*cortical_id)
5852 .or_insert_with(|| AtomicUsize::new(0))
5853 .fetch_add(count, Ordering::Relaxed);
5854 }
5855
5856 Ok(neuron_ids)
5857 }
5858
5859 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
5870 let npu = self
5872 .npu
5873 .as_ref()
5874 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5875
5876 let mut npu_lock = npu
5877 .lock()
5878 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5879
5880 let mut deleted_count = 0;
5881 let mut per_area_deleted: std::collections::HashMap<String, usize> =
5882 std::collections::HashMap::new();
5883
5884 for neuron_id in neuron_ids {
5887 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
5888 let cortical_id =
5889 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5890
5891 if npu_lock.delete_neuron(neuron_id as u32) {
5892 deleted_count += 1;
5893 if let Some(cortical_id) = cortical_id {
5894 let key = cortical_id.as_base_64();
5895 *per_area_deleted.entry(key).or_insert(0) += 1;
5896 }
5897 }
5898 }
5899
5900 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
5901
5902 if deleted_count > 0 {
5904 let state_manager = StateManager::instance();
5905 let state_manager = state_manager.read();
5906 let core_state = state_manager.get_core_state();
5907 core_state.subtract_neuron_count(deleted_count as u32);
5908 core_state.subtract_regular_neuron_count(deleted_count as u32);
5909 for (cortical_id, count) in per_area_deleted {
5910 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
5911 }
5912 }
5913
5914 Ok(deleted_count)
5921 }
5922
5923 pub fn update_neuron_properties(
5942 &mut self,
5943 neuron_id: u64,
5944 firing_threshold: Option<f32>,
5945 leak_coefficient: Option<f32>,
5946 resting_potential: Option<f32>,
5947 excitability: Option<f32>,
5948 ) -> BduResult<()> {
5949 let npu = self
5951 .npu
5952 .as_ref()
5953 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5954
5955 let mut npu_lock = npu
5956 .lock()
5957 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5958
5959 let neuron_id_u32 = neuron_id as u32;
5960
5961 let mut updated = false;
5963
5964 if let Some(threshold) = firing_threshold {
5966 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
5967 updated = true;
5968 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
5969 } else if !updated {
5970 return Err(BduError::InvalidNeuron(format!(
5971 "Neuron {} not found",
5972 neuron_id
5973 )));
5974 }
5975 }
5976
5977 if let Some(leak) = leak_coefficient {
5978 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
5979 updated = true;
5980 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
5981 } else if !updated {
5982 return Err(BduError::InvalidNeuron(format!(
5983 "Neuron {} not found",
5984 neuron_id
5985 )));
5986 }
5987 }
5988
5989 if let Some(resting) = resting_potential {
5990 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
5991 updated = true;
5992 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
5993 } else if !updated {
5994 return Err(BduError::InvalidNeuron(format!(
5995 "Neuron {} not found",
5996 neuron_id
5997 )));
5998 }
5999 }
6000
6001 if let Some(excit) = excitability {
6002 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
6003 updated = true;
6004 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
6005 } else if !updated {
6006 return Err(BduError::InvalidNeuron(format!(
6007 "Neuron {} not found",
6008 neuron_id
6009 )));
6010 }
6011 }
6012
6013 if !updated {
6014 return Err(BduError::Internal(
6015 "No properties provided for update".to_string(),
6016 ));
6017 }
6018
6019 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
6020
6021 Ok(())
6022 }
6023
6024 pub fn set_neuron_firing_threshold(
6036 &mut self,
6037 neuron_id: u64,
6038 new_threshold: f32,
6039 ) -> BduResult<()> {
6040 let npu = self
6042 .npu
6043 .as_ref()
6044 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6045
6046 let mut npu_lock = npu
6047 .lock()
6048 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6049
6050 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
6052 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
6053 Ok(())
6054 } else {
6055 Err(BduError::InvalidNeuron(format!(
6056 "Neuron {} not found",
6057 neuron_id
6058 )))
6059 }
6060 }
6061
6062 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
6077 self.cortical_areas
6078 .values()
6079 .find(|area| area.name == name)
6080 .cloned()
6081 }
6082
6083 pub fn resize_cortical_area(
6100 &mut self,
6101 cortical_id: &CorticalID,
6102 new_dimensions: CorticalAreaDimensions,
6103 ) -> BduResult<()> {
6104 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
6106 return Err(BduError::InvalidArea(format!(
6107 "Invalid dimensions: {:?} (all must be > 0)",
6108 new_dimensions
6109 )));
6110 }
6111
6112 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
6114 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6115 })?;
6116
6117 let old_dimensions = area.dimensions;
6118 area.dimensions = new_dimensions;
6119
6120 info!(target: "feagi-bdu",
6124 "Resized cortical area {} from {:?} to {:?}",
6125 cortical_id,
6126 old_dimensions,
6127 new_dimensions
6128 );
6129
6130 self.refresh_cortical_area_hashes(false, true);
6131
6132 Ok(())
6133 }
6134
6135 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
6146 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
6147 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6148 })?;
6149
6150 Ok(region
6152 .cortical_areas
6153 .iter()
6154 .map(|id| id.as_base_64())
6155 .collect())
6156 }
6157
6158 pub fn update_brain_region(
6171 &mut self,
6172 region_id: &str,
6173 new_name: Option<String>,
6174 new_description: Option<String>,
6175 ) -> BduResult<()> {
6176 let region = self
6177 .brain_regions
6178 .get_region_mut(region_id)
6179 .ok_or_else(|| {
6180 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6181 })?;
6182
6183 if let Some(name) = new_name {
6184 region.name = name;
6185 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
6186 }
6187
6188 if let Some(desc) = new_description {
6189 region
6191 .properties
6192 .insert("description".to_string(), serde_json::json!(desc));
6193 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
6194 }
6195
6196 info!(target: "feagi-bdu","Updated brain region {}", region_id);
6197
6198 self.refresh_brain_regions_hash();
6199
6200 Ok(())
6201 }
6202
6203 pub fn update_brain_region_properties(
6217 &mut self,
6218 region_id: &str,
6219 properties: std::collections::HashMap<String, serde_json::Value>,
6220 ) -> BduResult<Option<BrainRegionIoRegistry>> {
6221 use tracing::{debug, info};
6222
6223 let should_recompute_io = properties
6224 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6225 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
6226
6227 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6228 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
6229 {
6230 let region_snapshot = self
6231 .brain_regions
6232 .get_region(region_id)
6233 .ok_or_else(|| {
6234 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6235 })?
6236 .clone();
6237 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
6238 ®ion_snapshot,
6239 &properties,
6240 )?;
6241 crate::region_io_designation::validate_merged_designations_against_connectivity(
6242 self,
6243 ®ion_snapshot,
6244 &merged_in,
6245 &merged_out,
6246 )?;
6247 }
6248
6249 let region = self
6250 .brain_regions
6251 .get_region_mut(region_id)
6252 .ok_or_else(|| {
6253 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6254 })?;
6255
6256 for (key, value) in properties {
6257 match key.as_str() {
6258 "title" | "name" | "region_title" => {
6260 if let Some(name) = value.as_str() {
6261 region.name = name.to_string();
6262 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
6263 }
6264 }
6265 "coordinate_3d" | "coordinates_3d" => {
6266 region
6267 .properties
6268 .insert("coordinate_3d".to_string(), value.clone());
6269 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
6270 }
6271 "coordinate_2d" | "coordinates_2d" => {
6272 region
6273 .properties
6274 .insert("coordinate_2d".to_string(), value.clone());
6275 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
6276 }
6277 "description" => {
6278 region
6279 .properties
6280 .insert("description".to_string(), value.clone());
6281 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
6282 }
6283 "region_type" => {
6284 if let Some(type_str) = value.as_str() {
6285 region.region_type = feagi_structures::genomic::RegionType::Undefined;
6288 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
6289 }
6290 }
6291 _ => {
6293 region.properties.insert(key.clone(), value.clone());
6294 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
6295 }
6296 }
6297 }
6298
6299 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
6300
6301 if should_recompute_io {
6304 let registry = self.recompute_brain_region_io_registry()?;
6305 return Ok(Some(registry));
6306 }
6307
6308 self.refresh_brain_regions_hash();
6312
6313 Ok(None)
6314 }
6315
6316 pub fn get_neuron_by_coordinates(
6334 &self,
6335 cortical_id: &CorticalID,
6336 x: u32,
6337 y: u32,
6338 z: u32,
6339 ) -> Option<u64> {
6340 let area = self.get_cortical_area(cortical_id)?;
6342 let cortical_idx = area.cortical_idx;
6343
6344 let npu = self.npu.as_ref()?;
6346 let npu_lock = npu.lock().ok()?;
6347
6348 npu_lock
6349 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
6350 .map(|id| id as u64)
6351 }
6352
6353 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
6364 let npu = self.npu.as_ref()?;
6365 let npu_lock = npu.lock().ok()?;
6366
6367 let neuron_count = npu_lock.get_neuron_count();
6369 if (neuron_id as usize) >= neuron_count {
6370 return None;
6371 }
6372
6373 Some(
6374 npu_lock
6375 .get_neuron_coordinates(neuron_id as u32)
6376 .unwrap_or((0, 0, 0)),
6377 )
6378 }
6379
6380 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
6391 let npu = self.npu.as_ref()?;
6392 let npu_lock = npu.lock().ok()?;
6393
6394 let neuron_count = npu_lock.get_neuron_count();
6396 if (neuron_id as usize) >= neuron_count {
6397 return None;
6398 }
6399
6400 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
6401
6402 self.cortical_areas
6404 .values()
6405 .find(|area| area.cortical_idx == cortical_idx)
6406 .map(|area| area.cortical_id)
6407 }
6408
6409 pub fn get_neuron_properties(
6420 &self,
6421 neuron_id: u64,
6422 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6423 let npu = self.npu.as_ref()?;
6424 let npu_lock = npu.lock().ok()?;
6425
6426 let neuron_id_u32 = neuron_id as u32;
6427 let idx = neuron_id as usize;
6428
6429 let neuron_count = npu_lock.get_neuron_count();
6431 if idx >= neuron_count {
6432 return None;
6433 }
6434
6435 let mut properties = std::collections::HashMap::new();
6436
6437 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
6439
6440 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
6442 properties.insert("x".to_string(), serde_json::json!(x));
6443 properties.insert("y".to_string(), serde_json::json!(y));
6444 properties.insert("z".to_string(), serde_json::json!(z));
6445
6446 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
6448 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
6449
6450 properties.insert(
6452 "mp_charge_accumulation".to_string(),
6453 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
6454 );
6455 properties.insert(
6456 "neuron_type".to_string(),
6457 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
6458 );
6459 let (mp_drv, psp_uni) = self
6460 .cortical_idx_to_id
6461 .get(&cortical_idx)
6462 .map(|cid| {
6463 (
6464 npu_lock.get_mp_driven_psp_for_cortical(cid),
6465 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
6466 )
6467 })
6468 .unwrap_or((false, false));
6469 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
6470 properties.insert(
6471 "psp_uniform_distribution".to_string(),
6472 serde_json::json!(psp_uni),
6473 );
6474
6475 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
6478 .get_neuron_state(NeuronId(neuron_id_u32))
6479 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
6480 properties.insert(
6481 "consecutive_fire_count".to_string(),
6482 serde_json::json!(consec_count),
6483 );
6484 properties.insert(
6485 "consecutive_fire_limit".to_string(),
6486 serde_json::json!(consec_limit),
6487 );
6488 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
6489 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
6490 properties.insert("threshold".to_string(), serde_json::json!(threshold));
6491 properties.insert(
6492 "refractory_countdown".to_string(),
6493 serde_json::json!(refract_countdown),
6494 );
6495
6496 properties.insert(
6498 "leak_coefficient".to_string(),
6499 serde_json::json!(npu_lock
6500 .get_neuron_property_by_index(idx, "leak_coefficient")
6501 .unwrap_or(0.0)),
6502 );
6503 properties.insert(
6504 "resting_potential".to_string(),
6505 serde_json::json!(npu_lock
6506 .get_neuron_property_by_index(idx, "resting_potential")
6507 .unwrap_or(0.0)),
6508 );
6509 properties.insert(
6510 "excitability".to_string(),
6511 serde_json::json!(npu_lock
6512 .get_neuron_property_by_index(idx, "excitability")
6513 .unwrap_or(0.0)),
6514 );
6515 properties.insert(
6516 "threshold_limit".to_string(),
6517 serde_json::json!(npu_lock
6518 .get_neuron_property_by_index(idx, "threshold_limit")
6519 .unwrap_or(0.0)),
6520 );
6521 properties.insert(
6522 "refractory_period".to_string(),
6523 serde_json::json!(npu_lock
6524 .get_neuron_property_u16_by_index(idx, "refractory_period")
6525 .unwrap_or(0)),
6526 );
6527
6528 Some(properties)
6529 }
6530
6531 pub fn get_neuron_property(
6543 &self,
6544 neuron_id: u64,
6545 property_name: &str,
6546 ) -> Option<serde_json::Value> {
6547 self.get_neuron_properties(neuron_id)?
6548 .get(property_name)
6549 .cloned()
6550 }
6551
6552 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
6563 self.cortical_areas.keys().copied().collect()
6564 }
6565
6566 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
6573 self.cortical_areas
6574 .values()
6575 .map(|area| area.cortical_idx)
6576 .collect()
6577 }
6578
6579 pub fn get_cortical_area_names(&self) -> Vec<String> {
6586 self.cortical_areas
6587 .values()
6588 .map(|area| area.name.clone())
6589 .collect()
6590 }
6591
6592 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
6599 use crate::models::CorticalAreaExt;
6600 self.cortical_areas
6601 .values()
6602 .filter(|area| area.is_input_area())
6603 .map(|area| area.cortical_id)
6604 .collect()
6605 }
6606
6607 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
6614 use crate::models::CorticalAreaExt;
6615 self.cortical_areas
6616 .values()
6617 .filter(|area| area.is_output_area())
6618 .map(|area| area.cortical_id)
6619 .collect()
6620 }
6621
6622 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
6629 self.cortical_areas
6630 .values()
6631 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
6632 (
6633 max_w.max(area.dimensions.width as usize),
6634 max_h.max(area.dimensions.height as usize),
6635 max_d.max(area.dimensions.depth as usize),
6636 )
6637 })
6638 }
6639
6640 pub fn get_cortical_area_properties(
6651 &self,
6652 cortical_id: &CorticalID,
6653 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6654 let area = self.get_cortical_area(cortical_id)?;
6655
6656 let mut properties = std::collections::HashMap::new();
6657 properties.insert(
6658 "cortical_id".to_string(),
6659 serde_json::json!(area.cortical_id),
6660 );
6661 properties.insert(
6662 "cortical_id_s".to_string(),
6663 serde_json::json!(area.cortical_id.to_string()),
6664 );
6665 properties.insert(
6666 "cortical_idx".to_string(),
6667 serde_json::json!(area.cortical_idx),
6668 );
6669 properties.insert("name".to_string(), serde_json::json!(area.name));
6670 use crate::models::CorticalAreaExt;
6671 properties.insert(
6672 "area_type".to_string(),
6673 serde_json::json!(area.get_cortical_group()),
6674 );
6675 properties.insert(
6676 "dimensions".to_string(),
6677 serde_json::json!({
6678 "width": area.dimensions.width,
6679 "height": area.dimensions.height,
6680 "depth": area.dimensions.depth,
6681 }),
6682 );
6683 properties.insert("position".to_string(), serde_json::json!(area.position));
6684
6685 for (key, value) in &area.properties {
6687 properties.insert(key.clone(), value.clone());
6688 }
6689
6690 properties.extend(area.properties.clone());
6692
6693 Some(properties)
6694 }
6695
6696 pub fn get_all_cortical_area_properties(
6703 &self,
6704 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
6705 self.cortical_areas
6706 .keys()
6707 .filter_map(|id| self.get_cortical_area_properties(id))
6708 .collect()
6709 }
6710
6711 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
6722 self.brain_regions
6723 .get_all_region_ids()
6724 .into_iter()
6725 .cloned()
6726 .collect()
6727 }
6728
6729 pub fn get_brain_region_names(&self) -> Vec<String> {
6736 self.brain_regions
6737 .get_all_region_ids()
6738 .iter()
6739 .filter_map(|id| {
6740 self.brain_regions
6741 .get_region(id)
6742 .map(|region| region.name.clone())
6743 })
6744 .collect()
6745 }
6746
6747 pub fn get_brain_region_properties(
6758 &self,
6759 region_id: &str,
6760 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6761 let region = self.brain_regions.get_region(region_id)?;
6762
6763 let mut properties = std::collections::HashMap::new();
6764 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
6765 properties.insert("name".to_string(), serde_json::json!(region.name));
6766 properties.insert(
6767 "region_type".to_string(),
6768 serde_json::json!(format!("{:?}", region.region_type)),
6769 );
6770 properties.insert(
6771 "cortical_areas".to_string(),
6772 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
6773 );
6774
6775 properties.extend(region.properties.clone());
6777
6778 Some(properties)
6779 }
6780
6781 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
6792 self.cortical_areas.contains_key(cortical_id)
6793 }
6794
6795 pub fn brain_region_exists(&self, region_id: &str) -> bool {
6806 self.brain_regions.get_region(region_id).is_some()
6807 }
6808
6809 pub fn get_brain_region_count(&self) -> usize {
6816 self.brain_regions.region_count()
6817 }
6818
6819 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6830 self.get_neurons_in_area(cortical_id)
6834 }
6835}
6836
6837impl std::fmt::Debug for ConnectomeManager {
6839 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6840 f.debug_struct("ConnectomeManager")
6841 .field("cortical_areas", &self.cortical_areas.len())
6842 .field("next_cortical_idx", &self.next_cortical_idx)
6843 .field("brain_regions", &self.brain_regions)
6844 .field(
6845 "npu",
6846 &if self.npu.is_some() {
6847 "Connected"
6848 } else {
6849 "Not connected"
6850 },
6851 )
6852 .field("initialized", &self.initialized)
6853 .finish()
6854 }
6855}
6856
6857#[cfg(test)]
6858mod tests {
6859 use super::*;
6860 use feagi_structures::genomic::cortical_area::CoreCorticalType;
6861
6862 #[test]
6863 fn test_singleton_instance() {
6864 let instance1 = ConnectomeManager::instance();
6865 let instance2 = ConnectomeManager::instance();
6866
6867 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
6869 }
6870
6871 #[test]
6872 fn test_add_cortical_area() {
6873 ConnectomeManager::reset_for_testing();
6874
6875 let instance = ConnectomeManager::instance();
6876 let mut manager = instance.write();
6877
6878 use feagi_structures::genomic::cortical_area::{
6879 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6880 };
6881 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6883 let area = CorticalArea::new(
6884 cortical_id,
6885 0,
6886 "Visual Input".to_string(),
6887 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
6888 (0, 0, 0).into(),
6889 cortical_type,
6890 )
6891 .unwrap();
6892
6893 let initial_count = manager.get_cortical_area_count();
6894 let _cortical_idx = manager.add_cortical_area(area).unwrap();
6895
6896 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6897 assert!(manager.has_cortical_area(&cortical_id));
6898 assert!(manager.is_initialized());
6899 }
6900
6901 #[test]
6902 fn test_cortical_area_lookups() {
6903 ConnectomeManager::reset_for_testing();
6904
6905 let instance = ConnectomeManager::instance();
6906 let mut manager = instance.write();
6907
6908 use feagi_structures::genomic::cortical_area::{
6909 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6910 };
6911 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6913 let area = CorticalArea::new(
6914 cortical_id,
6915 0,
6916 "Test Area".to_string(),
6917 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6918 (0, 0, 0).into(),
6919 cortical_type,
6920 )
6921 .unwrap();
6922
6923 let cortical_idx = manager.add_cortical_area(area).unwrap();
6924
6925 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
6927
6928 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
6930
6931 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
6933 assert_eq!(retrieved_area.name, "Test Area");
6934 }
6935
6936 #[test]
6937 fn test_remove_cortical_area() {
6938 ConnectomeManager::reset_for_testing();
6939
6940 let instance = ConnectomeManager::instance();
6941 let mut manager = instance.write();
6942
6943 use feagi_structures::genomic::cortical_area::{
6944 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6945 };
6946 let cortical_id = CoreCorticalType::Power.to_cortical_id();
6947
6948 if manager.has_cortical_area(&cortical_id) {
6950 manager.remove_cortical_area(&cortical_id).unwrap();
6951 }
6952
6953 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6954 let area = CorticalArea::new(
6955 cortical_id,
6956 0,
6957 "Test".to_string(),
6958 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6959 (0, 0, 0).into(),
6960 cortical_type,
6961 )
6962 .unwrap();
6963
6964 let initial_count = manager.get_cortical_area_count();
6965 manager.add_cortical_area(area).unwrap();
6966 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
6967
6968 manager.remove_cortical_area(&cortical_id).unwrap();
6969 assert_eq!(manager.get_cortical_area_count(), initial_count);
6970 assert!(!manager.has_cortical_area(&cortical_id));
6971 }
6972
6973 #[test]
6974 fn test_duplicate_area_error() {
6975 ConnectomeManager::reset_for_testing();
6976
6977 let instance = ConnectomeManager::instance();
6978 let mut manager = instance.write();
6979
6980 use feagi_structures::genomic::cortical_area::{
6981 CorticalAreaType, IOCorticalAreaConfigurationFlag,
6982 };
6983 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
6986 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
6987 let area1 = CorticalArea::new(
6988 cortical_id,
6989 0,
6990 "First".to_string(),
6991 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
6992 (0, 0, 0).into(),
6993 cortical_type,
6994 )
6995 .unwrap();
6996
6997 let area2 = CorticalArea::new(
6998 cortical_id, 1,
7000 "Second".to_string(),
7001 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7002 (0, 0, 0).into(),
7003 cortical_type,
7004 )
7005 .unwrap();
7006
7007 manager.add_cortical_area(area1).unwrap();
7008 let result = manager.add_cortical_area(area2);
7009
7010 assert!(result.is_err());
7011 }
7012
7013 #[test]
7014 fn test_brain_region_management() {
7015 ConnectomeManager::reset_for_testing();
7016
7017 let instance = ConnectomeManager::instance();
7018 let mut manager = instance.write();
7019
7020 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
7021 let region_id_str = region_id.to_string();
7022 let root = BrainRegion::new(
7023 region_id,
7024 "Root".to_string(),
7025 feagi_structures::genomic::brain_regions::RegionType::Undefined,
7026 )
7027 .unwrap();
7028
7029 let initial_count = manager.get_brain_region_ids().len();
7030 manager.add_brain_region(root, None).unwrap();
7031
7032 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
7033 assert!(manager.get_brain_region(®ion_id_str).is_some());
7034 }
7035
7036 #[test]
7037 fn test_synapse_operations() {
7038 use feagi_npu_burst_engine::npu::RustNPU;
7039 use feagi_npu_burst_engine::TracingMutex;
7040 use std::sync::Arc;
7041
7042 use feagi_npu_burst_engine::backend::CPUBackend;
7044 use feagi_npu_burst_engine::DynamicNPU;
7045 use feagi_npu_runtime::StdRuntime;
7046
7047 let runtime = StdRuntime;
7048 let backend = CPUBackend::new();
7049 let npu_result =
7050 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
7051 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
7052 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
7053
7054 use feagi_structures::genomic::cortical_area::{
7056 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7057 };
7058 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7060 let area = CorticalArea::new(
7061 cortical_id,
7062 0, "Test Area".to_string(),
7064 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
7065 (0, 0, 0).into(), cortical_type,
7067 )
7068 .unwrap();
7069 let cortical_idx = manager.add_cortical_area(area).unwrap();
7070
7071 if let Some(npu_arc) = manager.get_npu() {
7073 if let Ok(mut npu_guard) = npu_arc.try_lock() {
7074 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
7075 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
7076 }
7077 }
7078 }
7079
7080 let neuron1_id = manager
7082 .add_neuron(
7083 &cortical_id,
7084 0,
7085 0,
7086 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
7098 .unwrap();
7099
7100 let neuron2_id = manager
7101 .add_neuron(
7102 &cortical_id,
7103 1,
7104 0,
7105 0, 100.0,
7107 f32::MAX, 0.1,
7109 -60.0,
7110 0,
7111 2,
7112 1.0,
7113 5,
7114 10,
7115 false,
7116 )
7117 .unwrap();
7118
7119 manager
7121 .create_synapse(
7122 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
7126 .unwrap();
7127
7128 println!("✅ Synapse creation test passed");
7131 }
7132
7133 #[test]
7134 fn test_apply_cortical_mapping_missing_rules_is_ok() {
7135 let mut manager = ConnectomeManager::new_for_testing();
7138
7139 use feagi_structures::genomic::cortical_area::{
7140 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7141 };
7142
7143 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
7144 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
7145
7146 let src_area = CorticalArea::new(
7147 src_id,
7148 0,
7149 "src".to_string(),
7150 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7151 (0, 0, 0).into(),
7152 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7153 )
7154 .unwrap();
7155
7156 let dst_area = CorticalArea::new(
7157 dst_id,
7158 1,
7159 "dst".to_string(),
7160 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7161 (0, 0, 0).into(),
7162 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7163 )
7164 .unwrap();
7165
7166 manager.add_cortical_area(src_area).unwrap();
7167 manager.add_cortical_area(dst_area).unwrap();
7168
7169 let count = manager
7171 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7172 .unwrap();
7173 assert_eq!(count, 0);
7174
7175 manager
7177 .update_cortical_mapping(
7178 &src_id,
7179 &dst_id,
7180 vec![serde_json::json!({"morphology_id":"m1"})],
7181 )
7182 .unwrap();
7183 manager
7184 .update_cortical_mapping(&src_id, &dst_id, vec![])
7185 .unwrap();
7186
7187 let count2 = manager
7188 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7189 .unwrap();
7190 assert_eq!(count2, 0);
7191 }
7192
7193 #[test]
7194 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
7195 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7196 let mapping_dst = serde_json::json!({
7197 "csrc0002": [
7198 {"morphology_id": "m1"}
7199 ]
7200 });
7201 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
7202
7203 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
7204 .expect("legacy destination key should resolve");
7205 assert_eq!(rules.len(), 1);
7206 assert_eq!(
7207 rules[0].get("morphology_id").and_then(|v| v.as_str()),
7208 Some("m1")
7209 );
7210 }
7211
7212 #[test]
7213 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
7214 use feagi_npu_burst_engine::backend::CPUBackend;
7215 use feagi_npu_burst_engine::RustNPU;
7216 use feagi_npu_burst_engine::TracingMutex;
7217 use feagi_npu_runtime::StdRuntime;
7218 use feagi_structures::genomic::cortical_area::{
7219 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7220 };
7221 use std::sync::Arc;
7222
7223 let runtime = StdRuntime;
7224 let backend = CPUBackend::new();
7225 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7226 let dyn_npu = Arc::new(TracingMutex::new(
7227 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7228 "TestNPU",
7229 ));
7230 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7231
7232 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
7233 let area = CorticalArea::new(
7234 area_id,
7235 0,
7236 "n".to_string(),
7237 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7238 (0, 0, 0).into(),
7239 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7240 )
7241 .unwrap();
7242
7243 manager.add_cortical_area(area).unwrap();
7244 let nid = manager
7245 .add_neuron(
7246 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
7247 )
7248 .unwrap();
7249
7250 let props = manager
7251 .get_neuron_properties(nid)
7252 .expect("neuron properties");
7253 for key in [
7254 "consecutive_fire_count",
7255 "consecutive_fire_limit",
7256 "snooze_period",
7257 "membrane_potential",
7258 "threshold",
7259 "refractory_countdown",
7260 "mp_charge_accumulation",
7261 "neuron_type",
7262 "mp_driven_psp",
7263 "psp_uniform_distribution",
7264 "leak_coefficient",
7265 "resting_potential",
7266 "excitability",
7267 "threshold_limit",
7268 "refractory_period",
7269 ] {
7270 assert!(props.contains_key(key), "missing neuron state key: {key}");
7271 }
7272 }
7273
7274 #[test]
7275 fn test_mapping_deletion_prunes_synapses_between_areas() {
7276 use feagi_npu_burst_engine::backend::CPUBackend;
7277 use feagi_npu_burst_engine::RustNPU;
7278 use feagi_npu_burst_engine::TracingMutex;
7279 use feagi_npu_runtime::StdRuntime;
7280 use feagi_structures::genomic::cortical_area::{
7281 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7282 };
7283 use std::sync::Arc;
7284
7285 let runtime = StdRuntime;
7287 let backend = CPUBackend::new();
7288 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7289 let dyn_npu = Arc::new(TracingMutex::new(
7290 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7291 "TestNPU",
7292 ));
7293 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7294
7295 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
7297 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
7298
7299 let src_area = CorticalArea::new(
7300 src_id,
7301 0,
7302 "src".to_string(),
7303 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7304 (0, 0, 0).into(),
7305 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7306 )
7307 .unwrap();
7308 let dst_area = CorticalArea::new(
7309 dst_id,
7310 1,
7311 "dst".to_string(),
7312 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7313 (0, 0, 0).into(),
7314 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7315 )
7316 .unwrap();
7317
7318 manager.add_cortical_area(src_area).unwrap();
7319 manager.add_cortical_area(dst_area).unwrap();
7320
7321 let s0 = manager
7323 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7324 .unwrap();
7325 let s1 = manager
7326 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7327 .unwrap();
7328 let t0 = manager
7329 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7330 .unwrap();
7331 let t1 = manager
7332 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7333 .unwrap();
7334
7335 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7337 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7338
7339 {
7341 let mut npu = dyn_npu.lock().unwrap();
7342 npu.rebuild_synapse_index();
7343 assert_eq!(npu.get_synapse_count(), 2);
7344 }
7345
7346 manager
7348 .update_cortical_mapping(&src_id, &dst_id, vec![])
7349 .unwrap();
7350 let created = manager
7351 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7352 .unwrap();
7353 assert_eq!(created, 0);
7354
7355 {
7357 let mut npu = dyn_npu.lock().unwrap();
7358 npu.rebuild_synapse_index();
7360 assert_eq!(npu.get_synapse_count(), 0);
7361 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7362 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7363 }
7364 }
7365
7366 #[test]
7367 fn test_mapping_update_prunes_synapses_between_areas() {
7368 use feagi_npu_burst_engine::backend::CPUBackend;
7369 use feagi_npu_burst_engine::RustNPU;
7370 use feagi_npu_burst_engine::TracingMutex;
7371 use feagi_npu_runtime::StdRuntime;
7372 use feagi_structures::genomic::cortical_area::{
7373 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7374 };
7375 use std::sync::Arc;
7376
7377 let runtime = StdRuntime;
7379 let backend = CPUBackend::new();
7380 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7381 let dyn_npu = Arc::new(TracingMutex::new(
7382 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7383 "TestNPU",
7384 ));
7385 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7386
7387 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7389
7390 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
7393 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
7394
7395 let src_area = CorticalArea::new(
7396 src_id,
7397 0,
7398 "src".to_string(),
7399 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7400 (0, 0, 0).into(),
7401 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7402 )
7403 .unwrap();
7404 let dst_area = CorticalArea::new(
7405 dst_id,
7406 0,
7407 "dst".to_string(),
7408 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7409 (0, 0, 0).into(),
7410 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7411 )
7412 .unwrap();
7413
7414 manager.add_cortical_area(src_area).unwrap();
7415 manager.add_cortical_area(dst_area).unwrap();
7416
7417 let s0 = manager
7419 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7420 .unwrap();
7421 let s1 = manager
7422 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7423 .unwrap();
7424 let t0 = manager
7425 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7426 .unwrap();
7427 let t1 = manager
7428 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7429 .unwrap();
7430
7431 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7433 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7434
7435 {
7437 let mut npu = dyn_npu.lock().unwrap();
7438 npu.rebuild_synapse_index();
7439 assert_eq!(npu.get_synapse_count(), 2);
7440 }
7441
7442 manager
7448 .update_cortical_mapping(
7449 &src_id,
7450 &dst_id,
7451 vec![serde_json::json!({
7452 "morphology_id": "episodic_memory",
7453 "morphology_scalar": [1],
7454 "postSynapticCurrent_multiplier": 1,
7455 "plasticity_flag": false,
7456 "plasticity_constant": 0,
7457 "ltp_multiplier": 0,
7458 "ltd_multiplier": 0,
7459 "plasticity_window": 0,
7460 })],
7461 )
7462 .unwrap();
7463 let created = manager
7464 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7465 .unwrap();
7466 assert_eq!(created, 0);
7467
7468 {
7470 let mut npu = dyn_npu.lock().unwrap();
7471 npu.rebuild_synapse_index();
7473 assert_eq!(npu.get_synapse_count(), 0);
7474 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7475 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7476 }
7477 }
7478
7479 #[test]
7480 fn test_upstream_area_tracking() {
7481 use crate::models::cortical_area::CorticalArea;
7483 use feagi_npu_burst_engine::backend::CPUBackend;
7484 use feagi_npu_burst_engine::TracingMutex;
7485 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7486 use feagi_npu_runtime::StdRuntime;
7487 use feagi_structures::genomic::cortical_area::{
7488 CorticalAreaDimensions, CorticalAreaType, CorticalID,
7489 };
7490
7491 let runtime = StdRuntime;
7493 let backend = CPUBackend::new();
7494 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7495 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7496 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7497
7498 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7501
7502 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
7504 let src_area = CorticalArea::new(
7505 src_id,
7506 0,
7507 "Source Area".to_string(),
7508 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7509 (0, 0, 0).into(),
7510 CorticalAreaType::Custom(
7511 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7512 ),
7513 )
7514 .unwrap();
7515 let src_idx = manager.add_cortical_area(src_area).unwrap();
7516
7517 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
7519 let dst_area = CorticalArea::new(
7520 dst_id,
7521 0,
7522 "Dest Area".to_string(),
7523 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7524 (0, 0, 0).into(),
7525 CorticalAreaType::Custom(
7526 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7527 ),
7528 )
7529 .unwrap();
7530 manager.add_cortical_area(dst_area).unwrap();
7531
7532 {
7534 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
7535 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
7536 assert!(
7537 upstream.as_array().unwrap().is_empty(),
7538 "Upstream areas should be empty initially"
7539 );
7540 }
7541
7542 let mapping_data = vec![serde_json::json!({
7544 "morphology_id": "episodic_memory",
7545 "morphology_scalar": 1,
7546 "postSynapticCurrent_multiplier": 1.0,
7547 })];
7548 manager
7549 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7550 .unwrap();
7551 manager
7552 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7553 .unwrap();
7554
7555 {
7557 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7558 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
7559 assert_eq!(
7560 upstream_areas[0], src_idx,
7561 "Upstream area should be src_idx"
7562 );
7563 }
7564
7565 manager
7567 .update_cortical_mapping(&src_id, &dst_id, vec![])
7568 .unwrap();
7569 manager
7570 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7571 .unwrap();
7572
7573 {
7575 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7576 assert_eq!(
7577 upstream_areas.len(),
7578 0,
7579 "Should have 0 upstream areas after deletion"
7580 );
7581 }
7582 }
7583
7584 #[test]
7585 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
7586 use crate::models::cortical_area::CorticalArea;
7587 use feagi_npu_burst_engine::backend::CPUBackend;
7588 use feagi_npu_burst_engine::TracingMutex;
7589 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7590 use feagi_npu_runtime::StdRuntime;
7591 use feagi_structures::genomic::cortical_area::{
7592 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7593 };
7594 use std::sync::Arc;
7595
7596 let runtime = StdRuntime;
7597 let backend = CPUBackend::new();
7598 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7599 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7600 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7601 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7602
7603 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7604 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
7605 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7606 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
7607
7608 let a1_area = CorticalArea::new(
7609 a1_id,
7610 0,
7611 "A1".to_string(),
7612 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7613 (0, 0, 0).into(),
7614 CorticalAreaType::Custom(
7615 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7616 ),
7617 )
7618 .unwrap();
7619 let a2_area = CorticalArea::new(
7620 a2_id,
7621 0,
7622 "A2".to_string(),
7623 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7624 (0, 0, 0).into(),
7625 CorticalAreaType::Custom(
7626 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7627 ),
7628 )
7629 .unwrap();
7630
7631 let mut m1_area = CorticalArea::new(
7632 m1_id,
7633 0,
7634 "M1".to_string(),
7635 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7636 (0, 0, 0).into(),
7637 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7638 )
7639 .unwrap();
7640 m1_area
7641 .properties
7642 .insert("is_mem_type".to_string(), serde_json::json!(true));
7643 m1_area
7644 .properties
7645 .insert("temporal_depth".to_string(), serde_json::json!(1));
7646
7647 let mut m2_area = CorticalArea::new(
7648 m2_id,
7649 0,
7650 "M2".to_string(),
7651 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7652 (0, 0, 0).into(),
7653 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7654 )
7655 .unwrap();
7656 m2_area
7657 .properties
7658 .insert("is_mem_type".to_string(), serde_json::json!(true));
7659 m2_area
7660 .properties
7661 .insert("temporal_depth".to_string(), serde_json::json!(1));
7662
7663 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
7664 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
7665 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
7666 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
7667
7668 manager
7669 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7670 .unwrap();
7671 manager
7672 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7673 .unwrap();
7674
7675 let episodic_mapping = vec![serde_json::json!({
7676 "morphology_id": "episodic_memory",
7677 "morphology_scalar": 1,
7678 "postSynapticCurrent_multiplier": 1.0,
7679 })];
7680 manager
7681 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
7682 .unwrap();
7683 manager
7684 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
7685 .unwrap();
7686 manager
7687 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
7688 .unwrap();
7689 manager
7690 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
7691 .unwrap();
7692
7693 let assoc_mapping = vec![serde_json::json!({
7694 "morphology_id": "associative_memory",
7695 "morphology_scalar": 1,
7696 "postSynapticCurrent_multiplier": 1.0,
7697 "plasticity_flag": true,
7698 "plasticity_constant": 1,
7699 "ltp_multiplier": 1,
7700 "ltd_multiplier": 1,
7701 "plasticity_window": 5,
7702 })];
7703 manager
7704 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
7705 .unwrap();
7706 manager
7707 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7708 .unwrap();
7709 manager
7711 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
7712 .unwrap();
7713 manager
7714 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
7715 .unwrap();
7716
7717 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7718 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7719 assert_eq!(
7720 upstream_m1.len(),
7721 2,
7722 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
7723 );
7724 assert_eq!(
7725 upstream_m2.len(),
7726 2,
7727 "M2 should have A2 and M1 as upstreams"
7728 );
7729
7730 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
7731 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
7732
7733 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7734 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7735 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
7736 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
7737 assert!(upstream_m1.contains(&a1_idx));
7738 assert!(upstream_m1.contains(&m2_idx));
7739 assert!(upstream_m2.contains(&a2_idx));
7740 assert!(upstream_m2.contains(&m1_idx));
7741
7742 {
7744 let mut npu_lock = dyn_npu.lock().unwrap();
7745 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
7746 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
7747 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
7748 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
7749 npu_lock.process_burst().expect("Burst processing failed");
7750 }
7751
7752 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
7753 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
7754 assert_eq!(
7755 upstream_m1.len(),
7756 2,
7757 "M1 should keep 2 upstreams after firing"
7758 );
7759 assert_eq!(
7760 upstream_m2.len(),
7761 2,
7762 "M2 should keep 2 upstreams after firing"
7763 );
7764 }
7765
7766 #[test]
7767 fn test_memory_twin_created_for_memory_mapping() {
7768 use crate::models::cortical_area::CorticalArea;
7769 use feagi_npu_burst_engine::backend::CPUBackend;
7770 use feagi_npu_burst_engine::TracingMutex;
7771 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7772 use feagi_npu_runtime::StdRuntime;
7773 use feagi_structures::genomic::cortical_area::{
7774 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7775 MemoryCorticalType,
7776 };
7777 use std::sync::Arc;
7778
7779 let runtime = StdRuntime;
7780 let backend = CPUBackend::new();
7781 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7782 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7783 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7784 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7785
7786 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
7787 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
7788
7789 let src_area = CorticalArea::new(
7790 src_id,
7791 0,
7792 "Source Area".to_string(),
7793 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7794 (0, 0, 0).into(),
7795 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7796 )
7797 .unwrap();
7798 let mut dst_area = CorticalArea::new(
7799 dst_id,
7800 0,
7801 "Memory Area".to_string(),
7802 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7803 (0, 0, 0).into(),
7804 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7805 )
7806 .unwrap();
7807 dst_area
7808 .properties
7809 .insert("is_mem_type".to_string(), serde_json::json!(true));
7810 dst_area
7811 .properties
7812 .insert("temporal_depth".to_string(), serde_json::json!(1));
7813
7814 manager.add_cortical_area(src_area).unwrap();
7815 manager.add_cortical_area(dst_area).unwrap();
7816
7817 let mapping_data = vec![serde_json::json!({
7818 "morphology_id": "episodic_memory",
7819 "morphology_scalar": 1,
7820 "postSynapticCurrent_multiplier": 1.0,
7821 })];
7822 manager
7823 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7824 .unwrap();
7825 manager
7826 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7827 .unwrap();
7828
7829 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
7830 let twin_map = memory_area
7831 .properties
7832 .get("memory_twin_areas")
7833 .and_then(|v| v.as_object())
7834 .expect("memory_twin_areas should be set");
7835 let twin_id_str = twin_map
7836 .get(&src_id.as_base_64())
7837 .and_then(|v| v.as_str())
7838 .expect("Missing twin entry for upstream area");
7839 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
7840 let mapping = memory_area
7841 .properties
7842 .get("cortical_mapping_dst")
7843 .and_then(|v| v.as_object())
7844 .and_then(|map| map.get(&twin_id.as_base_64()))
7845 .and_then(|v| v.as_array())
7846 .expect("Missing memory replay mapping for twin area");
7847 let uses_replay = mapping.iter().any(|rule| {
7848 rule.get("morphology_id")
7849 .and_then(|v| v.as_str())
7850 .is_some_and(|id| id == "memory_replay")
7851 });
7852 assert!(uses_replay, "Expected memory_replay mapping for twin area");
7853
7854 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
7855 assert!(matches!(
7856 twin_area.cortical_type,
7857 CorticalAreaType::Custom(_)
7858 ));
7859 assert_eq!(
7860 twin_area
7861 .properties
7862 .get("memory_twin_of")
7863 .and_then(|v| v.as_str()),
7864 Some(src_id.as_base_64().as_str())
7865 );
7866 assert_eq!(
7867 twin_area
7868 .properties
7869 .get("memory_twin_for")
7870 .and_then(|v| v.as_str()),
7871 Some(dst_id.as_base_64().as_str())
7872 );
7873 }
7874
7875 #[test]
7876 fn test_associative_memory_between_memory_areas_creates_synapses() {
7877 use crate::models::cortical_area::CorticalArea;
7878 use feagi_npu_burst_engine::backend::CPUBackend;
7879 use feagi_npu_burst_engine::TracingMutex;
7880 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7881 use feagi_npu_runtime::StdRuntime;
7882 use feagi_structures::genomic::cortical_area::{
7883 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
7884 };
7885 use std::sync::Arc;
7886
7887 let runtime = StdRuntime;
7888 let backend = CPUBackend::new();
7889 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7890 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7891 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7892 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7893
7894 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
7895 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
7896
7897 let mut m1_area = CorticalArea::new(
7898 m1_id,
7899 0,
7900 "Memory M1".to_string(),
7901 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7902 (0, 0, 0).into(),
7903 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7904 )
7905 .unwrap();
7906 m1_area
7907 .properties
7908 .insert("is_mem_type".to_string(), serde_json::json!(true));
7909 m1_area
7910 .properties
7911 .insert("temporal_depth".to_string(), serde_json::json!(1));
7912
7913 let mut m2_area = CorticalArea::new(
7914 m2_id,
7915 0,
7916 "Memory M2".to_string(),
7917 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
7918 (0, 0, 0).into(),
7919 CorticalAreaType::Memory(MemoryCorticalType::Memory),
7920 )
7921 .unwrap();
7922 m2_area
7923 .properties
7924 .insert("is_mem_type".to_string(), serde_json::json!(true));
7925 m2_area
7926 .properties
7927 .insert("temporal_depth".to_string(), serde_json::json!(1));
7928
7929 manager.add_cortical_area(m1_area).unwrap();
7930 manager.add_cortical_area(m2_area).unwrap();
7931
7932 manager
7933 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7934 .unwrap();
7935 manager
7936 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7937 .unwrap();
7938
7939 let mapping_data = vec![serde_json::json!({
7940 "morphology_id": "associative_memory",
7941 "morphology_scalar": 1,
7942 "postSynapticCurrent_multiplier": 1.0,
7943 "plasticity_flag": true,
7944 "plasticity_constant": 1,
7945 "ltp_multiplier": 1,
7946 "ltd_multiplier": 1,
7947 "plasticity_window": 5,
7948 })];
7949 manager
7950 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
7951 .unwrap();
7952 let created = manager
7953 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
7954 .unwrap();
7955 assert!(
7956 created > 0,
7957 "Expected associative memory mapping between memory areas to create synapses"
7958 );
7959 let npu_guard = dyn_npu.lock().unwrap();
7960 let assoc_tagged =
7961 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
7962 assert!(
7963 assoc_tagged >= 1,
7964 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
7965 );
7966 }
7967
7968 #[test]
7969 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
7970 use crate::models::cortical_area::CorticalArea;
7971 use feagi_npu_burst_engine::backend::CPUBackend;
7972 use feagi_npu_burst_engine::TracingMutex;
7973 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7974 use feagi_npu_runtime::StdRuntime;
7975 use feagi_structures::genomic::cortical_area::{
7976 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
7977 MemoryCorticalType,
7978 };
7979 use std::sync::Arc;
7980
7981 let runtime = StdRuntime;
7982 let backend = CPUBackend::new();
7983 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7984 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7985 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7986 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7987
7988 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7989 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
7990
7991 let src_area = CorticalArea::new(
7992 src_id,
7993 0,
7994 "Source Area".to_string(),
7995 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7996 (0, 0, 0).into(),
7997 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7998 )
7999 .unwrap();
8000 let mut mem_area = CorticalArea::new(
8001 mem_id,
8002 0,
8003 "Memory Area".to_string(),
8004 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8005 (0, 0, 0).into(),
8006 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8007 )
8008 .unwrap();
8009 mem_area
8010 .properties
8011 .insert("is_mem_type".to_string(), serde_json::json!(true));
8012 mem_area
8013 .properties
8014 .insert("temporal_depth".to_string(), serde_json::json!(1));
8015
8016 manager.add_cortical_area(src_area).unwrap();
8017 manager.add_cortical_area(mem_area).unwrap();
8018
8019 let twin_id = manager
8020 .build_memory_twin_id(&mem_id, &src_id)
8021 .expect("Failed to build twin id");
8022 let twin_area = CorticalArea::new(
8023 twin_id,
8024 0,
8025 "Source Area_twin".to_string(),
8026 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8027 (0, 0, 0).into(),
8028 CorticalAreaType::Custom(
8029 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8030 ),
8031 )
8032 .unwrap();
8033 manager.add_cortical_area(twin_area).unwrap();
8034
8035 let repaired = manager
8036 .ensure_memory_twin_area(&mem_id, &src_id)
8037 .expect("Failed to repair twin");
8038 assert_eq!(repaired, twin_id);
8039
8040 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
8041 let twin_map = mem_area
8042 .properties
8043 .get("memory_twin_areas")
8044 .and_then(|v| v.as_object())
8045 .expect("memory_twin_areas should be set");
8046 let twin_id_str = twin_map
8047 .get(&src_id.as_base_64())
8048 .and_then(|v| v.as_str())
8049 .expect("Missing twin entry for upstream area");
8050 assert_eq!(twin_id_str, twin_id.as_base_64());
8051
8052 let replay_map = mem_area
8053 .properties
8054 .get("cortical_mapping_dst")
8055 .and_then(|v| v.as_object())
8056 .and_then(|map| map.get(&twin_id.as_base_64()))
8057 .and_then(|v| v.as_array())
8058 .expect("Missing memory replay mapping for twin area");
8059 let uses_replay = replay_map.iter().any(|rule| {
8060 rule.get("morphology_id")
8061 .and_then(|v| v.as_str())
8062 .is_some_and(|id| id == "memory_replay")
8063 });
8064 assert!(uses_replay, "Expected memory_replay mapping for twin area");
8065
8066 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8067 assert_eq!(
8068 twin_area
8069 .properties
8070 .get("memory_twin_of")
8071 .and_then(|v| v.as_str()),
8072 Some(src_id.as_base_64().as_str())
8073 );
8074 assert_eq!(
8075 twin_area
8076 .properties
8077 .get("memory_twin_for")
8078 .and_then(|v| v.as_str()),
8079 Some(mem_id.as_base_64().as_str())
8080 );
8081 }
8082
8083 fn build_max_weight_test_manager() -> (
8088 ConnectomeManager,
8089 CorticalID,
8090 CorticalID,
8091 CorticalID,
8092 CorticalID,
8093 ) {
8094 use feagi_npu_burst_engine::backend::CPUBackend;
8095 use feagi_npu_burst_engine::TracingMutex;
8096 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8097 use feagi_npu_runtime::StdRuntime;
8098 use feagi_structures::genomic::cortical_area::{
8099 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8100 };
8101
8102 let runtime = StdRuntime;
8103 let backend = CPUBackend::new();
8104 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("npu");
8105 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8106 let mut mgr = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
8107 feagi_evolutionary::templates::add_core_morphologies(&mut mgr.morphology_registry);
8111
8112 let src = CorticalID::try_from_bytes(b"cstmwsrc").unwrap();
8113 let dst = CorticalID::try_from_bytes(b"cstmwdst").unwrap();
8114 let reward = CorticalID::try_from_bytes(b"cstmwrwd").unwrap();
8115 let pain = CorticalID::try_from_bytes(b"cstmwpan").unwrap();
8116
8117 for (id, label, kind) in [
8118 (
8119 src,
8120 "src",
8121 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8122 ),
8123 (
8124 dst,
8125 "dst",
8126 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8127 ),
8128 (
8129 reward,
8130 "reward",
8131 CorticalAreaType::Custom(
8132 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8133 ),
8134 ),
8135 (
8136 pain,
8137 "pain",
8138 CorticalAreaType::Custom(
8139 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8140 ),
8141 ),
8142 ] {
8143 mgr.add_cortical_area(
8144 CorticalArea::new(
8145 id,
8146 0,
8147 label.to_string(),
8148 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8149 (0, 0, 0).into(),
8150 kind,
8151 )
8152 .unwrap(),
8153 )
8154 .unwrap();
8155 mgr.add_neuron(&id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8156 .unwrap();
8157 }
8158 (mgr, src, dst, reward, pain)
8159 }
8160
8161 fn write_and_regenerate_mapping(
8166 mgr: &mut ConnectomeManager,
8167 src: &CorticalID,
8168 dst: &CorticalID,
8169 rule: serde_json::Value,
8170 ) -> BduResult<usize> {
8171 mgr.update_cortical_mapping(src, dst, vec![rule])?;
8172 mgr.regenerate_synapses_for_mapping(src, dst)
8173 }
8174
8175 #[test]
8179 fn test_max_weight_finite_positive_accepted_on_rstdp_rule() {
8180 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8181
8182 let result = write_and_regenerate_mapping(
8183 &mut mgr,
8184 &src,
8185 &dst,
8186 serde_json::json!({
8187 "morphology_id": "block_to_block",
8188 "morphology_scalar": [1, 1, 1],
8189 "postSynapticCurrent_multiplier": 1,
8190 "plasticity_flag": true,
8191 "plasticity_constant": 1,
8192 "ltp_multiplier": 1,
8193 "ltd_multiplier": 1,
8194 "plasticity_window": 10,
8195 "synaptic_delay_bursts": 1,
8196 "plasticity_mode": "rstdp",
8197 "eligibility_decay_bursts": 50,
8198 "reward_source_area": reward.as_base_64(),
8199 "punishment_source_area": pain.as_base_64(),
8200 "max_weight": 12.5,
8201 }),
8202 );
8203 assert!(
8204 result.is_ok(),
8205 "valid max_weight=12.5 must be accepted, got {:?}",
8206 result
8207 );
8208 }
8209
8210 #[test]
8215 fn test_max_weight_invalid_values_rejected() {
8216 for bad in &[
8217 serde_json::json!(0.0),
8218 serde_json::json!(-1.5),
8219 serde_json::json!("not_a_number"),
8220 ] {
8221 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8222 let result = write_and_regenerate_mapping(
8223 &mut mgr,
8224 &src,
8225 &dst,
8226 serde_json::json!({
8227 "morphology_id": "block_to_block",
8228 "morphology_scalar": [1, 1, 1],
8229 "postSynapticCurrent_multiplier": 1,
8230 "plasticity_flag": true,
8231 "plasticity_constant": 1,
8232 "ltp_multiplier": 1,
8233 "ltd_multiplier": 1,
8234 "plasticity_window": 10,
8235 "synaptic_delay_bursts": 1,
8236 "plasticity_mode": "rstdp",
8237 "eligibility_decay_bursts": 50,
8238 "reward_source_area": reward.as_base_64(),
8239 "punishment_source_area": pain.as_base_64(),
8240 "max_weight": bad,
8241 }),
8242 );
8243 assert!(
8244 result.is_err(),
8245 "max_weight={:?} should have been rejected, got {:?}",
8246 bad,
8247 result
8248 );
8249 }
8250 }
8251
8252 #[test]
8255 fn test_ltp_ltd_multiplier_out_of_i8_range_rejected() {
8256 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8257 let result = write_and_regenerate_mapping(
8258 &mut mgr,
8259 &src,
8260 &dst,
8261 serde_json::json!({
8262 "morphology_id": "block_to_block",
8263 "morphology_scalar": [1, 1, 1],
8264 "postSynapticCurrent_multiplier": 1,
8265 "plasticity_flag": true,
8266 "plasticity_constant": 1,
8267 "ltp_multiplier": 200,
8268 "ltd_multiplier": 1,
8269 "plasticity_window": 10,
8270 "synaptic_delay_bursts": 1,
8271 "plasticity_mode": "rstdp",
8272 "eligibility_decay_bursts": 50,
8273 "reward_source_area": reward.as_base_64(),
8274 "punishment_source_area": pain.as_base_64(),
8275 }),
8276 );
8277 assert!(
8278 result.is_err(),
8279 "ltp_multiplier=200 must be rejected (i8 range); got {:?}",
8280 result
8281 );
8282 }
8283
8284 #[test]
8287 fn test_max_weight_rejected_when_plasticity_off() {
8288 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
8289
8290 let result = write_and_regenerate_mapping(
8291 &mut mgr,
8292 &src,
8293 &dst,
8294 serde_json::json!({
8295 "morphology_id": "block_to_block",
8296 "morphology_scalar": [1, 1, 1],
8297 "postSynapticCurrent_multiplier": 1,
8298 "plasticity_flag": true,
8303 "plasticity_constant": 0,
8304 "ltp_multiplier": 0,
8305 "ltd_multiplier": 0,
8306 "plasticity_window": 0,
8307 "synaptic_delay_bursts": 1,
8308 "plasticity_mode": "off",
8309 "max_weight": 10.0,
8310 }),
8311 );
8312 assert!(
8313 result.is_err(),
8314 "max_weight on off-mode rule must be rejected; got {:?}",
8315 result
8316 );
8317 }
8318
8319 #[test]
8320 fn test_plasticity_eta_rejected_when_plasticity_off() {
8321 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
8322
8323 let result = write_and_regenerate_mapping(
8324 &mut mgr,
8325 &src,
8326 &dst,
8327 serde_json::json!({
8328 "morphology_id": "block_to_block",
8329 "morphology_scalar": [1, 1, 1],
8330 "postSynapticCurrent_multiplier": 1,
8331 "plasticity_flag": true,
8332 "plasticity_constant": 0,
8333 "ltp_multiplier": 0,
8334 "ltd_multiplier": 0,
8335 "plasticity_window": 0,
8336 "synaptic_delay_bursts": 1,
8337 "plasticity_mode": "off",
8338 "plasticity_eta": 0.5,
8339 }),
8340 );
8341 assert!(
8342 result.is_err(),
8343 "plasticity_eta on off-mode rule must be rejected; got {:?}",
8344 result
8345 );
8346 }
8347
8348 #[test]
8349 fn test_plasticity_eta_non_positive_rejected() {
8350 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8351
8352 let result = write_and_regenerate_mapping(
8353 &mut mgr,
8354 &src,
8355 &dst,
8356 serde_json::json!({
8357 "morphology_id": "block_to_block",
8358 "morphology_scalar": [1, 1, 1],
8359 "postSynapticCurrent_multiplier": 1,
8360 "plasticity_flag": true,
8361 "plasticity_constant": 1,
8362 "ltp_multiplier": 1,
8363 "ltd_multiplier": 1,
8364 "plasticity_window": 10,
8365 "synaptic_delay_bursts": 1,
8366 "plasticity_mode": "rstdp",
8367 "eligibility_decay_bursts": 50,
8368 "reward_source_area": reward.as_base_64(),
8369 "punishment_source_area": pain.as_base_64(),
8370 "plasticity_eta": 0.0,
8371 }),
8372 );
8373 assert!(
8374 result.is_err(),
8375 "plasticity_eta=0 must be rejected; got {:?}",
8376 result
8377 );
8378 }
8379}