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
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct MemoryTwinDiagnostic {
94 pub src_cortical_area: String,
95 pub dst_cortical_area: String,
96 pub src_cortical_type: String,
97 pub dst_cortical_type: String,
98 pub mapping_exists: bool,
99 pub episodic_rule_count: usize,
100 pub twin_expected: bool,
101 pub twin_present: bool,
102 pub twin_cortical_area: Option<String>,
103 pub reason: Option<String>,
104 pub src_parent_region_id: Option<String>,
105 pub dst_parent_region_id: Option<String>,
106 pub twin_parent_region_id: Option<String>,
107 pub twin_parent_matches_src_parent: Option<bool>,
108}
109
110pub struct ConnectomeManager {
132 cortical_areas: HashMap<CorticalID, CorticalArea>,
134
135 cortical_id_to_idx: HashMap<CorticalID, u32>,
137
138 cortical_idx_to_id: HashMap<u32, CorticalID>,
140
141 next_cortical_idx: u32,
143
144 brain_regions: BrainRegionHierarchy,
146
147 morphology_registry: feagi_evolutionary::MorphologyRegistry,
149
150 config: ConnectomeConfig,
152
153 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
159
160 #[cfg(feature = "plasticity")]
162 plasticity_executor:
163 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
164
165 cached_neuron_count: Arc<AtomicUsize>,
168
169 cached_synapse_count: Arc<AtomicUsize>,
172
173 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
176
177 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
180
181 initialized: bool,
183
184 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
186}
187
188type NeuronData = (
190 u32,
191 u32,
192 u32,
193 f32,
194 f32,
195 f32,
196 f32,
197 i32,
198 u16,
199 f32,
200 u16,
201 u16,
202 bool,
203);
204
205impl ConnectomeManager {
206 fn get_mapping_rules_for_destination<'a>(
207 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
208 dst_area_id: &CorticalID,
209 ) -> Option<&'a Vec<serde_json::Value>> {
210 if let Some(rules) = mapping_dst
211 .get(&dst_area_id.as_base_64())
212 .and_then(|value| value.as_array())
213 {
214 return Some(rules);
215 }
216
217 for (raw_dst_key, rules_value) in mapping_dst {
220 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
221 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
222 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
223 continue;
224 }
225 if let Some(rules) = rules_value.as_array() {
226 return Some(rules);
227 }
228 }
229
230 None
231 }
232
233 fn new() -> Self {
235 Self {
236 cortical_areas: HashMap::new(),
237 cortical_id_to_idx: HashMap::new(),
238 cortical_idx_to_id: HashMap::new(),
239 next_cortical_idx: 7, brain_regions: BrainRegionHierarchy::new(),
242 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
243 config: ConnectomeConfig::default(),
244 npu: None,
245 #[cfg(feature = "plasticity")]
246 plasticity_executor: None,
247 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
248 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
249 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
250 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
251 initialized: false,
252 last_fatigue_calculation: Arc::new(Mutex::new(
253 std::time::Instant::now() - std::time::Duration::from_secs(10),
254 )), }
256 }
257
258 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
275 Arc::clone(&*INSTANCE)
278 }
279
280 pub fn new_for_testing() -> Self {
308 Self {
309 cortical_areas: HashMap::new(),
310 cortical_id_to_idx: HashMap::new(),
311 cortical_idx_to_id: HashMap::new(),
312 next_cortical_idx: 0,
313 brain_regions: BrainRegionHierarchy::new(),
314 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
315 config: ConnectomeConfig::default(),
316 npu: None,
317 #[cfg(feature = "plasticity")]
318 plasticity_executor: None,
319 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
320 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
321 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
322 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
323 initialized: false,
324 last_fatigue_calculation: Arc::new(Mutex::new(
325 std::time::Instant::now() - std::time::Duration::from_secs(10),
326 )),
327 }
328 }
329
330 pub fn new_for_testing_with_npu(
346 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
347 ) -> Self {
348 Self {
349 cortical_areas: HashMap::new(),
350 cortical_id_to_idx: HashMap::new(),
351 cortical_idx_to_id: HashMap::new(),
352 next_cortical_idx: 7,
353 brain_regions: BrainRegionHierarchy::new(),
354 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
355 config: ConnectomeConfig::default(),
356 npu: Some(npu),
357 #[cfg(feature = "plasticity")]
358 plasticity_executor: None,
359 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
360 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
361 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
362 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
363 initialized: false,
364 last_fatigue_calculation: Arc::new(Mutex::new(
365 std::time::Instant::now() - std::time::Duration::from_secs(10),
366 )),
367 }
368 }
369
370 pub fn setup_core_morphologies_for_testing(&mut self) {
380 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
381 }
382
383 #[cfg(test)]
392 pub fn reset_for_testing() {
393 let mut instance = INSTANCE.write();
394 *instance = Self::new();
395 }
396
397 fn update_state_hashes(
403 &self,
404 brain_regions: Option<u64>,
405 cortical_areas: Option<u64>,
406 brain_geometry: Option<u64>,
407 morphologies: Option<u64>,
408 cortical_mappings: Option<u64>,
409 ) {
410 let state_manager = StateManager::instance();
411 let state_manager = state_manager.read();
412 if let Some(value) = brain_regions {
413 state_manager.set_brain_regions_hash(value);
414 }
415 if let Some(value) = cortical_areas {
416 state_manager.set_cortical_areas_hash(value);
417 }
418 if let Some(value) = brain_geometry {
419 state_manager.set_brain_geometry_hash(value);
420 }
421 if let Some(value) = morphologies {
422 state_manager.set_morphologies_hash(value);
423 }
424 if let Some(value) = cortical_mappings {
425 state_manager.set_cortical_mappings_hash(value);
426 }
427 }
428
429 fn refresh_brain_regions_hash(&self) {
431 let hash = self.compute_brain_regions_hash();
432 self.update_state_hashes(Some(hash), None, None, None, None);
433 }
434
435 #[allow(dead_code)]
436 fn refresh_cortical_areas_hash(&self) {
438 let hash = self.compute_cortical_areas_hash();
439 self.update_state_hashes(None, Some(hash), None, None, None);
440 }
441
442 #[allow(dead_code)]
443 fn refresh_brain_geometry_hash(&self) {
445 let hash = self.compute_brain_geometry_hash();
446 self.update_state_hashes(None, None, Some(hash), None, None);
447 }
448
449 fn refresh_morphologies_hash(&self) {
451 let hash = self.compute_morphologies_hash();
452 self.update_state_hashes(None, None, None, Some(hash), None);
453 }
454
455 pub fn refresh_cortical_mappings_hash(&self) {
457 let hash = self.compute_cortical_mappings_hash();
458 self.update_state_hashes(None, None, None, None, Some(hash));
459 }
460
461 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
463 let cortical_hash = if properties_changed {
464 Some(self.compute_cortical_areas_hash())
465 } else {
466 None
467 };
468 let geometry_hash = if geometry_changed {
469 Some(self.compute_brain_geometry_hash())
470 } else {
471 None
472 };
473 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
474 }
475
476 fn compute_brain_regions_hash(&self) -> u64 {
478 let mut hasher = Xxh64::new(DATA_HASH_SEED);
479 let mut region_ids: Vec<String> = self
480 .brain_regions
481 .get_all_region_ids()
482 .into_iter()
483 .cloned()
484 .collect();
485 region_ids.sort();
486
487 for region_id in region_ids {
488 let Some(region) = self.brain_regions.get_region(®ion_id) else {
489 continue;
490 };
491 Self::hash_str(&mut hasher, ®ion_id);
492 Self::hash_str(&mut hasher, ®ion.name);
493 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
494 let parent_id = self.brain_regions.get_parent(®ion_id);
495 match parent_id {
496 Some(parent) => Self::hash_str(&mut hasher, parent),
497 None => Self::hash_str(&mut hasher, "null"),
498 }
499
500 let mut cortical_ids: Vec<String> = region
501 .cortical_areas
502 .iter()
503 .map(|id| id.as_base_64())
504 .collect();
505 cortical_ids.sort();
506 for cortical_id in cortical_ids {
507 Self::hash_str(&mut hasher, &cortical_id);
508 }
509
510 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
511 }
512
513 hasher.finish() & HASH_SAFE_MASK
514 }
515
516 fn compute_cortical_areas_hash(&self) -> u64 {
518 let mut hasher = Xxh64::new(DATA_HASH_SEED);
519 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
520 areas.sort_by_key(|area| area.cortical_id.as_base_64());
521
522 for area in areas {
523 let cortical_id = area.cortical_id.as_base_64();
524 Self::hash_str(&mut hasher, &cortical_id);
525 hasher.write_u32(area.cortical_idx);
526 Self::hash_str(&mut hasher, &area.name);
527 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
528
529 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
530 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
531 }
532
533 hasher.finish() & HASH_SAFE_MASK
534 }
535
536 fn compute_brain_geometry_hash(&self) -> u64 {
538 let mut hasher = Xxh64::new(DATA_HASH_SEED);
539 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
540 areas.sort_by_key(|area| area.cortical_id.as_base_64());
541
542 for area in areas {
543 let cortical_id = area.cortical_id.as_base_64();
544 Self::hash_str(&mut hasher, &cortical_id);
545
546 Self::hash_i32(&mut hasher, area.position.x);
547 Self::hash_i32(&mut hasher, area.position.y);
548 Self::hash_i32(&mut hasher, area.position.z);
549
550 Self::hash_u32(&mut hasher, area.dimensions.width);
551 Self::hash_u32(&mut hasher, area.dimensions.height);
552 Self::hash_u32(&mut hasher, area.dimensions.depth);
553
554 let coord_2d = area
555 .properties
556 .get("coordinate_2d")
557 .or_else(|| area.properties.get("coordinates_2d"));
558 match coord_2d {
559 Some(value) => Self::hash_json_value(&mut hasher, value),
560 None => Self::hash_str(&mut hasher, "null"),
561 }
562 }
563
564 hasher.finish() & HASH_SAFE_MASK
565 }
566
567 fn compute_morphologies_hash(&self) -> u64 {
569 let mut hasher = Xxh64::new(DATA_HASH_SEED);
570 let mut morphology_ids = self.morphology_registry.morphology_ids();
571 morphology_ids.sort();
572
573 for morphology_id in morphology_ids {
574 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
575 Self::hash_str(&mut hasher, &morphology_id);
576 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
577 Self::hash_str(&mut hasher, &morphology.class);
578 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
579 Self::hash_json_value(&mut hasher, &value);
580 }
581 }
582 }
583
584 hasher.finish() & HASH_SAFE_MASK
585 }
586
587 fn compute_cortical_mappings_hash(&self) -> u64 {
589 let mut hasher = Xxh64::new(DATA_HASH_SEED);
590 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
591 areas.sort_by_key(|area| area.cortical_id.as_base_64());
592
593 for area in areas {
594 let cortical_id = area.cortical_id.as_base_64();
595 Self::hash_str(&mut hasher, &cortical_id);
596 if let Some(serde_json::Value::Object(map)) =
597 area.properties.get("cortical_mapping_dst")
598 {
599 let mut dest_ids: Vec<&String> = map.keys().collect();
600 dest_ids.sort();
601 for dest_id in dest_ids {
602 Self::hash_str(&mut hasher, dest_id);
603 if let Some(value) = map.get(dest_id) {
604 Self::hash_json_value(&mut hasher, value);
605 }
606 }
607 } else {
608 Self::hash_str(&mut hasher, "null");
609 }
610 }
611
612 hasher.finish() & HASH_SAFE_MASK
613 }
614
615 fn hash_str(hasher: &mut Xxh64, value: &str) {
617 hasher.write(value.as_bytes());
618 hasher.write_u8(0);
619 }
620
621 fn hash_i32(hasher: &mut Xxh64, value: i32) {
623 hasher.write(&value.to_le_bytes());
624 }
625
626 fn hash_u32(hasher: &mut Xxh64, value: u32) {
628 hasher.write(&value.to_le_bytes());
629 }
630
631 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
633 match value {
634 serde_json::Value::Null => {
635 hasher.write_u8(0);
636 }
637 serde_json::Value::Bool(val) => {
638 hasher.write_u8(1);
639 hasher.write_u8(*val as u8);
640 }
641 serde_json::Value::Number(num) => {
642 hasher.write_u8(2);
643 Self::hash_str(hasher, &num.to_string());
644 }
645 serde_json::Value::String(val) => {
646 hasher.write_u8(3);
647 Self::hash_str(hasher, val);
648 }
649 serde_json::Value::Array(items) => {
650 hasher.write_u8(4);
651 for item in items {
652 Self::hash_json_value(hasher, item);
653 }
654 }
655 serde_json::Value::Object(map) => {
656 hasher.write_u8(5);
657 let mut keys: Vec<&String> = map.keys().collect();
658 keys.sort();
659 for key in keys {
660 Self::hash_str(hasher, key);
661 if let Some(val) = map.get(key) {
662 Self::hash_json_value(hasher, val);
663 }
664 }
665 }
666 }
667 }
668
669 fn hash_properties_filtered(
671 hasher: &mut Xxh64,
672 properties: &HashMap<String, serde_json::Value>,
673 excluded_keys: &[&str],
674 ) {
675 let mut keys: Vec<&String> = properties.keys().collect();
676 keys.sort();
677 for key in keys {
678 if excluded_keys.contains(&key.as_str()) {
679 continue;
680 }
681 Self::hash_str(hasher, key);
682 if let Some(value) = properties.get(key) {
683 Self::hash_json_value(hasher, value);
684 }
685 }
686 }
687
688 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
709 if self.cortical_areas.contains_key(&area.cortical_id) {
711 return Err(BduError::InvalidArea(format!(
712 "Cortical area {} already exists",
713 area.cortical_id
714 )));
715 }
716
717 use feagi_structures::genomic::cortical_area::CoreCorticalType;
720
721 let death_id = CoreCorticalType::Death.to_cortical_id();
722 let power_id = CoreCorticalType::Power.to_cortical_id();
723 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
724 let pain_id = CoreCorticalType::Pain.to_cortical_id();
725 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
726 let fear_id = CoreCorticalType::Fear.to_cortical_id();
727 let hope_id = CoreCorticalType::Hope.to_cortical_id();
728
729 let is_death_area = area.cortical_id == death_id;
730 let is_power_area = area.cortical_id == power_id;
731 let is_fatigue_area = area.cortical_id == fatigue_id;
732 let is_pain_area = area.cortical_id == pain_id;
733 let is_pleasure_area = area.cortical_id == pleasure_id;
734 let is_fear_area = area.cortical_id == fear_id;
735 let is_hope_area = area.cortical_id == hope_id;
736
737 if is_death_area {
738 trace!(
739 target: "feagi-bdu",
740 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
741 area.cortical_id
742 );
743 area.cortical_idx = 0;
744 } else if is_power_area {
745 trace!(
746 target: "feagi-bdu",
747 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
748 area.cortical_id
749 );
750 area.cortical_idx = 1;
751 } else if is_fatigue_area {
752 trace!(
753 target: "feagi-bdu",
754 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
755 area.cortical_id
756 );
757 area.cortical_idx = 2;
758 } else if is_pain_area {
759 trace!(
760 target: "feagi-bdu",
761 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
762 area.cortical_id
763 );
764 area.cortical_idx = 3;
765 } else if is_pleasure_area {
766 trace!(
767 target: "feagi-bdu",
768 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
769 area.cortical_id
770 );
771 area.cortical_idx = 4;
772 } else if is_fear_area {
773 trace!(
774 target: "feagi-bdu",
775 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
776 area.cortical_id
777 );
778 area.cortical_idx = 5;
779 } else if is_hope_area {
780 trace!(
781 target: "feagi-bdu",
782 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
783 area.cortical_id
784 );
785 area.cortical_idx = 6;
786 } else {
787 if area.cortical_idx == 0 {
789 area.cortical_idx = self.next_cortical_idx;
790 self.next_cortical_idx += 1;
791 trace!(
792 target: "feagi-bdu",
793 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
794 area.cortical_idx,
795 area.cortical_id.as_base_64()
796 );
797 } else {
798 if area.cortical_idx <= 6 {
800 warn!(
801 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
802 area.cortical_id, area.cortical_idx);
803 area.cortical_idx = self.next_cortical_idx;
804 self.next_cortical_idx += 1;
805 info!(
806 " Reassigned '{}' to cortical_idx={}",
807 area.cortical_id, area.cortical_idx
808 );
809 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
810 return Err(BduError::InvalidArea(format!(
811 "Cortical index {} is already in use",
812 area.cortical_idx
813 )));
814 }
815
816 if area.cortical_idx >= self.next_cortical_idx {
818 self.next_cortical_idx = area.cortical_idx + 1;
819 }
820 }
821 }
822
823 let cortical_id = area.cortical_id;
824 let cortical_idx = area.cortical_idx;
825
826 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
828 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
829
830 area.properties
832 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
833
834 let parent_region_id = area
843 .properties
844 .get("parent_region_id")
845 .and_then(|v| v.as_str())
846 .map(|s| s.to_string());
847
848 self.cortical_areas.insert(cortical_id, area);
850
851 if let Some(region_id) = parent_region_id {
853 let region = self
854 .brain_regions
855 .get_region_mut(®ion_id)
856 .ok_or_else(|| {
857 BduError::InvalidArea(format!(
858 "Unknown parent_region_id '{}' for cortical area {}",
859 region_id,
860 cortical_id.as_base_64()
861 ))
862 })?;
863 region.add_area(cortical_id);
864 }
865
866 {
869 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
870 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
871 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
872 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
873 }
874 let state_manager = StateManager::instance();
876 let state_manager = state_manager.read();
877 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
878
879 if let Some(ref npu) = self.npu {
883 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
884 if let Ok(mut npu_lock) = npu.lock() {
885 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
886 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
887 trace!(
888 target: "feagi-bdu",
889 "Registered cortical area idx={} -> '{}' in NPU",
890 cortical_idx,
891 cortical_id.as_base_64()
892 );
893 }
894 }
895
896 self.sync_cortical_area_flags_to_npu()?;
898
899 self.initialized = true;
900
901 self.refresh_cortical_area_hashes(true, true);
902 self.refresh_brain_regions_hash();
903
904 Ok(cortical_idx)
905 }
906
907 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
922 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
923 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
924 })?;
925
926 self.cortical_id_to_idx.remove(cortical_id);
928 self.cortical_idx_to_id.remove(&area.cortical_idx);
929
930 self.refresh_cortical_area_hashes(true, true);
931 Ok(())
932 }
933
934 pub fn rename_cortical_area_id(
938 &mut self,
939 old_id: &CorticalID,
940 new_id: CorticalID,
941 new_cortical_type: CorticalAreaType,
942 ) -> BduResult<()> {
943 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
944 }
945
946 pub fn rename_cortical_area_id_with_options(
948 &mut self,
949 old_id: &CorticalID,
950 new_id: CorticalID,
951 new_cortical_type: CorticalAreaType,
952 update_npu_registry: bool,
953 ) -> BduResult<()> {
954 if !self.cortical_areas.contains_key(old_id) {
955 return Err(BduError::InvalidArea(format!(
956 "Cortical area {} does not exist",
957 old_id
958 )));
959 }
960 if self.cortical_areas.contains_key(&new_id) {
961 return Err(BduError::InvalidArea(format!(
962 "Cortical area {} already exists",
963 new_id
964 )));
965 }
966
967 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
968 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
969 })?;
970 let cortical_idx = area.cortical_idx;
971 area.cortical_id = new_id;
972 area.cortical_type = new_cortical_type;
973
974 self.cortical_areas.insert(new_id, area);
975 self.cortical_id_to_idx.remove(old_id);
976 self.cortical_id_to_idx.insert(new_id, cortical_idx);
977 self.cortical_idx_to_id.insert(cortical_idx, new_id);
978
979 {
981 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
982 if let Some(value) = neuron_cache.remove(old_id) {
983 neuron_cache.insert(new_id, value);
984 }
985 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
986 if let Some(value) = synapse_cache.remove(old_id) {
987 synapse_cache.insert(new_id, value);
988 }
989 }
990
991 self.brain_regions.rename_cortical_area_id(old_id, new_id);
993
994 let old_id_str = old_id.as_base_64();
996 let new_id_str = new_id.as_base_64();
997 for area in self.cortical_areas.values_mut() {
998 if let Some(mapping) = area
999 .properties
1000 .get_mut("cortical_mapping_dst")
1001 .and_then(|v| v.as_object_mut())
1002 {
1003 if let Some(value) = mapping.remove(&old_id_str) {
1004 mapping.insert(new_id_str.clone(), value);
1005 }
1006 }
1007 }
1008
1009 if update_npu_registry {
1011 if let Some(ref npu) = self.npu {
1012 if let Ok(mut npu_lock) = npu.lock() {
1013 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
1014 }
1015 }
1016 }
1017
1018 self.refresh_cortical_area_hashes(true, true);
1019 self.refresh_brain_regions_hash();
1020 self.refresh_cortical_mappings_hash();
1021
1022 Ok(())
1023 }
1024
1025 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1027 self.cortical_areas.get(cortical_id)
1028 }
1029
1030 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1032 self.cortical_areas.get_mut(cortical_id)
1033 }
1034
1035 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1037 self.cortical_id_to_idx.get(cortical_id).copied()
1038 }
1039
1040 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1052 self.brain_regions.find_region_containing_area(cortical_id)
1053 }
1054
1055 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1058 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1059 return false;
1060 };
1061 let Some(src_area) = self.cortical_areas.get(area) else {
1062 return false;
1063 };
1064 let Some(dst_obj) = src_area
1065 .properties
1066 .get("cortical_mapping_dst")
1067 .and_then(|v| v.as_object())
1068 else {
1069 return false;
1070 };
1071 for dst_key in dst_obj.keys() {
1072 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1073 continue;
1074 };
1075 match self.brain_regions.find_region_containing_area(&dst_id) {
1076 None => return true,
1077 Some(rid) if rid != my_region => return true,
1078 _ => {}
1079 }
1080 }
1081 false
1082 }
1083
1084 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1086 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1087 return false;
1088 };
1089 let my_b64 = area.as_base_64();
1090 for (src_id, src_area) in &self.cortical_areas {
1091 if src_id == area {
1092 continue;
1093 }
1094 let Some(dst_map) = src_area
1095 .properties
1096 .get("cortical_mapping_dst")
1097 .and_then(|v| v.as_object())
1098 else {
1099 continue;
1100 };
1101 if !dst_map.contains_key(&my_b64) {
1102 continue;
1103 }
1104 match self.brain_regions.find_region_containing_area(src_id) {
1105 None => return true,
1106 Some(rid) if rid != my_region => return true,
1107 _ => {}
1108 }
1109 }
1110 false
1111 }
1112
1113 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1124 use std::collections::HashSet;
1125
1126 let region_ids: Vec<String> = self
1127 .brain_regions
1128 .get_all_region_ids()
1129 .into_iter()
1130 .cloned()
1131 .collect();
1132
1133 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1134 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1135
1136 for rid in ®ion_ids {
1138 inputs_by_region.insert(rid.clone(), HashSet::new());
1139 outputs_by_region.insert(rid.clone(), HashSet::new());
1140 }
1141
1142 for (src_id, src_area) in &self.cortical_areas {
1143 let Some(dstmap) = src_area
1144 .properties
1145 .get("cortical_mapping_dst")
1146 .and_then(|v| v.as_object())
1147 else {
1148 continue;
1149 };
1150
1151 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1152 debug!(
1153 target: "feagi-bdu",
1154 "Skipping region IO for source area {} (not in any region)",
1155 src_id.as_base_64()
1156 );
1157 continue;
1158 };
1159
1160 for dst_id_str in dstmap.keys() {
1161 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1162 BduError::InvalidArea(format!(
1163 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1164 dst_id_str,
1165 src_id.as_base_64(),
1166 e
1167 ))
1168 })?;
1169
1170 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1171 else {
1172 warn!(
1173 target: "feagi-bdu",
1174 "Skipping region IO for destination area {} (not in any region)",
1175 dst_id.as_base_64()
1176 );
1177 continue;
1178 };
1179
1180 if src_region_id == dst_region_id {
1181 continue;
1182 }
1183
1184 outputs_by_region
1185 .entry(src_region_id.clone())
1186 .or_default()
1187 .insert(src_id.as_base_64());
1188 inputs_by_region
1189 .entry(dst_region_id.clone())
1190 .or_default()
1191 .insert(dst_id.as_base_64());
1192 }
1193 }
1194
1195 for rid in ®ion_ids {
1197 let Some(region) = self.brain_regions.get_region(rid) else {
1198 continue;
1199 };
1200 let in_ids = crate::region_io_designation::parse_designated_id_list(
1201 region
1202 .properties
1203 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1204 )?;
1205 let out_ids = crate::region_io_designation::parse_designated_id_list(
1206 region
1207 .properties
1208 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1209 )?;
1210 for id in in_ids {
1211 inputs_by_region
1212 .entry(rid.clone())
1213 .or_default()
1214 .insert(id.as_base_64());
1215 }
1216 for id in out_ids {
1217 outputs_by_region
1218 .entry(rid.clone())
1219 .or_default()
1220 .insert(id.as_base_64());
1221 }
1222 }
1223
1224 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1225 for rid in region_ids {
1226 let mut inputs: Vec<String> = inputs_by_region
1227 .remove(&rid)
1228 .unwrap_or_default()
1229 .into_iter()
1230 .collect();
1231 let mut outputs: Vec<String> = outputs_by_region
1232 .remove(&rid)
1233 .unwrap_or_default()
1234 .into_iter()
1235 .collect();
1236
1237 inputs.sort();
1238 outputs.sort();
1239
1240 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1241 BduError::InvalidArea(format!(
1242 "Unable to recompute region IO: region '{}' not found in hierarchy",
1243 rid
1244 ))
1245 })?;
1246
1247 if inputs.is_empty() {
1248 region.properties.remove("inputs");
1249 } else {
1250 region
1251 .properties
1252 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1253 }
1254
1255 if outputs.is_empty() {
1256 region.properties.remove("outputs");
1257 } else {
1258 region
1259 .properties
1260 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1261 }
1262
1263 computed.insert(rid, (inputs, outputs));
1264 }
1265
1266 self.refresh_brain_regions_hash();
1267
1268 Ok(computed)
1269 }
1270
1271 pub fn get_root_region_id(&self) -> Option<String> {
1277 self.brain_regions.get_root_region_id()
1278 }
1279
1280 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1282 self.cortical_idx_to_id.get(&cortical_idx)
1283 }
1284
1285 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1288 self.cortical_idx_to_id
1289 .iter()
1290 .map(|(idx, id)| (*idx, id.as_base_64()))
1291 .collect()
1292 }
1293
1294 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1299 let mut granularities = ahash::AHashMap::new();
1300 for (cortical_id, area) in &self.cortical_areas {
1301 let cortical_idx = self
1302 .cortical_id_to_idx
1303 .get(cortical_id)
1304 .copied()
1305 .unwrap_or(0);
1306
1307 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1310 if let Some(arr) = granularity_json.as_array() {
1311 if arr.len() == 3 {
1312 let x_opt = arr[0]
1313 .as_u64()
1314 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1315 let y_opt = arr[1]
1316 .as_u64()
1317 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1318 let z_opt = arr[2]
1319 .as_u64()
1320 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1321
1322 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1323 let granularity = (x as u32, y as u32, z as u32);
1324 if granularity != (1, 1, 1) {
1326 granularities.insert(cortical_idx, granularity);
1327 }
1328 }
1329 }
1330 }
1331 }
1332 }
1333 granularities
1334 }
1335
1336 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1338 self.cortical_areas.keys().collect()
1339 }
1340
1341 pub fn get_cortical_area_count(&self) -> usize {
1343 self.cortical_areas.len()
1344 }
1345
1346 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1360 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1361 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1362 if let Some(upstream_array) = upstream_prop.as_array() {
1363 return upstream_array
1364 .iter()
1365 .filter_map(|v| v.as_u64().map(|n| n as u32))
1366 .collect();
1367 }
1368 }
1369
1370 warn!(target: "feagi-bdu",
1372 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1373 target_cortical_id.as_base_64()
1374 );
1375 }
1376
1377 Vec::new()
1378 }
1379
1380 pub fn get_episodic_memory_upstream_cortical_areas(
1385 &self,
1386 target_cortical_id: &CorticalID,
1387 ) -> Vec<u32> {
1388 let mut episodic_upstream = Vec::new();
1389 let upstream = self.get_upstream_cortical_areas(target_cortical_id);
1390
1391 for src_idx in upstream {
1392 let Some(src_id) = self.cortical_idx_to_id.get(&src_idx) else {
1393 continue;
1394 };
1395 let Some(src_area) = self.cortical_areas.get(src_id) else {
1396 continue;
1397 };
1398 let Some(mapping_dst) = src_area
1399 .properties
1400 .get("cortical_mapping_dst")
1401 .and_then(|v| v.as_object())
1402 else {
1403 continue;
1404 };
1405 let Some(rules) =
1406 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1407 else {
1408 continue;
1409 };
1410
1411 let has_episodic_rule = rules.iter().any(|rule| {
1412 let morphology_id = rule
1413 .as_object()
1414 .and_then(|obj| obj.get("morphology_id"))
1415 .and_then(|v| v.as_str())
1416 .or_else(|| {
1417 rule.as_array()
1418 .and_then(|arr| arr.first())
1419 .and_then(|v| v.as_str())
1420 });
1421 morphology_id == Some("episodic_memory")
1422 });
1423
1424 if has_episodic_rule {
1425 episodic_upstream.push(src_idx);
1426 }
1427 }
1428
1429 episodic_upstream.sort_unstable();
1430 episodic_upstream.dedup();
1431 episodic_upstream
1432 }
1433
1434 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1436 upstream
1437 .iter()
1438 .filter_map(|idx| {
1439 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1440 let area = self.cortical_areas.get(cortical_id)?;
1441 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1442 None
1443 } else {
1444 Some(*idx)
1445 }
1446 })
1447 .collect()
1448 }
1449
1450 pub fn refresh_upstream_cortical_areas_from_mappings(
1454 &mut self,
1455 target_cortical_id: &CorticalID,
1456 ) -> Vec<u32> {
1457 use std::collections::HashSet;
1458 let target_id_str = target_cortical_id.as_base_64();
1459 let mut upstream_idxs = HashSet::new();
1460 for (src_id, src_area) in &self.cortical_areas {
1461 if src_id == target_cortical_id {
1462 continue;
1463 }
1464 if let Some(mapping) = src_area
1465 .properties
1466 .get("cortical_mapping_dst")
1467 .and_then(|v| v.as_object())
1468 {
1469 if mapping.contains_key(&target_id_str) {
1470 upstream_idxs.insert(src_area.cortical_idx);
1471 }
1472 }
1473 }
1474
1475 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1476 upstream_list.sort_unstable();
1477
1478 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1479 target_area.properties.insert(
1480 "upstream_cortical_areas".to_string(),
1481 serde_json::json!(upstream_list),
1482 );
1483 }
1484
1485 self.get_upstream_cortical_areas(target_cortical_id)
1486 }
1487
1488 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1498 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1499 let upstream_array = area
1500 .properties
1501 .entry("upstream_cortical_areas".to_string())
1502 .or_insert_with(|| serde_json::json!([]));
1503
1504 if let Some(arr) = upstream_array.as_array_mut() {
1505 let src_value = serde_json::json!(src_cortical_idx);
1506 if !arr.contains(&src_value) {
1507 arr.push(src_value);
1508 debug!(target: "feagi-bdu",
1509 "Added upstream area idx={} to cortical area '{}'",
1510 src_cortical_idx, target_cortical_id.as_base_64()
1511 );
1512 }
1513 }
1514 }
1515 }
1516
1517 pub fn get_memory_twin_for_upstream_idx(
1519 &self,
1520 memory_area_idx: u32,
1521 upstream_idx: u32,
1522 ) -> Option<CorticalID> {
1523 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1524 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1525 let area = self.cortical_areas.get(memory_id)?;
1526 let mapping = area
1527 .properties
1528 .get("memory_twin_areas")
1529 .and_then(|v| v.as_object())?;
1530 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1531 CorticalID::try_from_base_64(twin_b64).ok()
1532 }
1533
1534 pub fn diagnose_memory_twin_for_mapping(
1541 &self,
1542 src_area_id: &CorticalID,
1543 dst_area_id: &CorticalID,
1544 ) -> BduResult<MemoryTwinDiagnostic> {
1545 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
1546 BduError::InvalidArea(format!(
1547 "Source area not found: {}",
1548 src_area_id.as_base_64()
1549 ))
1550 })?;
1551 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
1552 BduError::InvalidArea(format!(
1553 "Destination area not found: {}",
1554 dst_area_id.as_base_64()
1555 ))
1556 })?;
1557
1558 let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
1559 let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
1560
1561 let rules_opt = src_area
1562 .properties
1563 .get("cortical_mapping_dst")
1564 .and_then(|v| v.as_object())
1565 .and_then(|mapping_dst| {
1566 Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
1567 });
1568 let mapping_exists = rules_opt.is_some();
1569 let episodic_rule_count = rules_opt
1570 .map(|rules| {
1571 rules
1572 .iter()
1573 .filter(|rule| {
1574 let morphology_id = rule
1575 .as_object()
1576 .and_then(|obj| obj.get("morphology_id"))
1577 .and_then(|v| v.as_str())
1578 .or_else(|| {
1579 rule.as_array()
1580 .and_then(|arr| arr.first())
1581 .and_then(|v| v.as_str())
1582 });
1583 morphology_id == Some("episodic_memory")
1584 })
1585 .count()
1586 })
1587 .unwrap_or(0);
1588
1589 let twin_expected =
1590 mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
1591 let twin_cortical_area = dst_area
1592 .properties
1593 .get("memory_twin_areas")
1594 .and_then(|v| v.as_object())
1595 .and_then(|map| map.get(&src_area_id.as_base_64()))
1596 .and_then(|v| v.as_str())
1597 .map(ToString::to_string);
1598 let twin_present = twin_cortical_area.is_some();
1599
1600 let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
1601 CorticalID::try_from_base_64(twin_b64)
1602 .ok()
1603 .and_then(|twin_id| {
1604 self.cortical_areas.get(&twin_id).and_then(|area| {
1605 area.properties
1606 .get("parent_region_id")
1607 .and_then(|v| v.as_str())
1608 .map(ToString::to_string)
1609 })
1610 })
1611 });
1612 let src_parent_region_id = src_area
1613 .properties
1614 .get("parent_region_id")
1615 .and_then(|v| v.as_str())
1616 .map(ToString::to_string);
1617 let dst_parent_region_id = dst_area
1618 .properties
1619 .get("parent_region_id")
1620 .and_then(|v| v.as_str())
1621 .map(ToString::to_string);
1622 let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
1623 (Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
1624 (None, None) if twin_present => Some(true),
1625 _ if twin_present => Some(false),
1626 _ => None,
1627 };
1628
1629 let reason = if !mapping_exists {
1630 Some("no_mapping_rule_between_src_and_dst".to_string())
1631 } else if episodic_rule_count == 0 {
1632 Some("mapping_exists_but_not_episodic_memory".to_string())
1633 } else if !dst_is_memory {
1634 Some("destination_is_not_memory_area".to_string())
1635 } else if src_is_memory {
1636 Some("upstream_is_memory_area_twin_not_supported".to_string())
1637 } else if !twin_present {
1638 Some("twin_expected_but_missing".to_string())
1639 } else if twin_parent_matches_src_parent == Some(false) {
1640 Some("twin_parent_region_mismatch_with_source".to_string())
1641 } else {
1642 None
1643 };
1644
1645 Ok(MemoryTwinDiagnostic {
1646 src_cortical_area: src_area_id.as_base_64(),
1647 dst_cortical_area: dst_area_id.as_base_64(),
1648 src_cortical_type: src_area.cortical_type.to_string(),
1649 dst_cortical_type: dst_area.cortical_type.to_string(),
1650 mapping_exists,
1651 episodic_rule_count,
1652 twin_expected,
1653 twin_present,
1654 twin_cortical_area,
1655 reason,
1656 src_parent_region_id,
1657 dst_parent_region_id,
1658 twin_parent_region_id,
1659 twin_parent_matches_src_parent,
1660 })
1661 }
1662
1663 pub fn ensure_memory_twin_area(
1665 &mut self,
1666 memory_area_id: &CorticalID,
1667 upstream_area_id: &CorticalID,
1668 ) -> BduResult<CorticalID> {
1669 use crate::models::CorticalAreaExt;
1670
1671 let register_replay_mapping = |manager: &mut ConnectomeManager,
1672 twin_id: &CorticalID|
1673 -> BduResult<()> {
1674 let Some(npu) = manager.npu.as_ref() else {
1675 return Ok(());
1676 };
1677 let memory_area_idx =
1678 *manager
1679 .cortical_id_to_idx
1680 .get(memory_area_id)
1681 .ok_or_else(|| {
1682 BduError::InvalidArea(format!(
1683 "Memory area idx missing for {}",
1684 memory_area_id.as_base_64()
1685 ))
1686 })?;
1687 let upstream_area_idx = *manager
1688 .cortical_id_to_idx
1689 .get(upstream_area_id)
1690 .ok_or_else(|| {
1691 BduError::InvalidArea(format!(
1692 "Upstream area idx missing for {}",
1693 upstream_area_id.as_base_64()
1694 ))
1695 })?;
1696 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1697 BduError::InvalidArea(format!(
1698 "Twin area idx missing for {}",
1699 twin_id.as_base_64()
1700 ))
1701 })?;
1702 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1703 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1704 })?;
1705 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1706 if let Ok(mut npu_lock) = npu.lock() {
1707 npu_lock.register_memory_twin_mapping(
1708 memory_area_idx,
1709 upstream_area_idx,
1710 twin_area_idx,
1711 potential,
1712 );
1713 }
1714 Ok(())
1715 };
1716
1717 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1718 BduError::InvalidArea(format!(
1719 "Memory area {} not found",
1720 memory_area_id.as_base_64()
1721 ))
1722 })?;
1723 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1724 BduError::InvalidArea(format!(
1725 "Upstream area {} not found",
1726 upstream_area_id.as_base_64()
1727 ))
1728 })?;
1729
1730 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1731 return Err(BduError::InvalidArea(format!(
1732 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1733 upstream_area_id.as_base_64()
1734 )));
1735 }
1736
1737 if let Some(existing) = memory_area
1738 .properties
1739 .get("memory_twin_areas")
1740 .and_then(|v| v.as_object())
1741 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1742 .and_then(|v| v.as_str())
1743 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1744 {
1745 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1746 register_replay_mapping(self, &existing)?;
1747 self.refresh_cortical_mappings_hash();
1748 return Ok(existing);
1749 }
1750
1751 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1752 if self.cortical_areas.contains_key(&twin_id) {
1753 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1754 let expected_source = upstream_area_id.as_base_64();
1755 let expected_target = memory_area_id.as_base_64();
1756 let existing_source = existing
1757 .properties
1758 .get("memory_twin_of")
1759 .and_then(|v| v.as_str());
1760 let existing_target = existing
1761 .properties
1762 .get("memory_twin_for")
1763 .and_then(|v| v.as_str());
1764 if existing_source != Some(expected_source.as_str())
1765 || existing_target != Some(expected_target.as_str())
1766 {
1767 warn!(
1768 target: "feagi-bdu",
1769 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1770 upstream_area_id.as_base_64(),
1771 memory_area_id.as_base_64()
1772 );
1773 existing.properties.insert(
1774 "memory_twin_of".to_string(),
1775 serde_json::json!(expected_source),
1776 );
1777 existing.properties.insert(
1778 "memory_twin_for".to_string(),
1779 serde_json::json!(expected_target),
1780 );
1781 }
1782 }
1783 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1784 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1785 register_replay_mapping(self, &twin_id)?;
1786 self.refresh_cortical_mappings_hash();
1787 return Ok(twin_id);
1788 }
1789
1790 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1791 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1792 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1793 let mut twin_area = CorticalArea::new(
1794 twin_id,
1795 0,
1796 twin_name,
1797 upstream_area.dimensions,
1798 twin_position,
1799 twin_type,
1800 )?;
1801 twin_area.properties = self.build_memory_twin_properties(
1802 memory_area,
1803 upstream_area,
1804 memory_area_id,
1805 upstream_area_id,
1806 );
1807
1808 let _twin_idx = self.add_cortical_area(twin_area)?;
1809 let _ = self.create_neurons_for_area(&twin_id);
1810
1811 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1812 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1813 register_replay_mapping(self, &twin_id)?;
1814 self.refresh_cortical_mappings_hash();
1815 Ok(twin_id)
1816 }
1817
1818 fn build_memory_twin_position(
1819 &self,
1820 _memory_area: &CorticalArea,
1821 upstream_area: &CorticalArea,
1822 ) -> GenomeCoordinate3D {
1823 let width = upstream_area.dimensions.width as f32;
1824 let margin = (width * 0.25).ceil() as i32;
1825 let offset = upstream_area.dimensions.width as i32 + margin;
1826 GenomeCoordinate3D::new(
1827 upstream_area.position.x + offset,
1828 upstream_area.position.y,
1829 upstream_area.position.z,
1830 )
1831 }
1832
1833 fn build_memory_twin_id(
1834 &self,
1835 memory_area_id: &CorticalID,
1836 upstream_area_id: &CorticalID,
1837 ) -> BduResult<CorticalID> {
1838 let mut hasher = Xxh64::new(DATA_HASH_SEED);
1839 hasher.write(memory_area_id.as_base_64().as_bytes());
1840 hasher.write(upstream_area_id.as_base_64().as_bytes());
1841 hasher.write(b"memory_twin");
1842 let hash = hasher.finish();
1843 let mut bytes = hash.to_be_bytes();
1844 bytes[0] = b'c';
1845 CorticalID::try_from_bytes(&bytes)
1846 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
1847 }
1848
1849 fn build_memory_twin_properties(
1850 &self,
1851 _memory_area: &CorticalArea,
1852 upstream_area: &CorticalArea,
1853 memory_area_id: &CorticalID,
1854 upstream_area_id: &CorticalID,
1855 ) -> HashMap<String, serde_json::Value> {
1856 let mut props = upstream_area.properties.clone();
1857 props.remove("cortical_mapping_dst");
1858 props.remove("upstream_cortical_areas");
1859 props.remove("parent_region_id");
1860 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
1861 props.insert("is_mem_type".to_string(), serde_json::json!(false));
1862 props.insert(
1863 "memory_twin_of".to_string(),
1864 serde_json::json!(upstream_area_id.as_base_64()),
1865 );
1866 props.insert(
1867 "memory_twin_for".to_string(),
1868 serde_json::json!(memory_area_id.as_base_64()),
1869 );
1870 if let Some(parent_region_id) = upstream_area
1871 .properties
1872 .get("parent_region_id")
1873 .and_then(|v| v.as_str())
1874 {
1875 props.insert(
1876 "parent_region_id".to_string(),
1877 serde_json::json!(parent_region_id),
1878 );
1879 }
1880 props
1881 }
1882
1883 fn set_memory_twin_mapping(
1884 &mut self,
1885 memory_area_id: &CorticalID,
1886 upstream_area_id: &CorticalID,
1887 twin_id: &CorticalID,
1888 ) {
1889 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
1890 let mapping = memory_area
1891 .properties
1892 .entry("memory_twin_areas".to_string())
1893 .or_insert_with(|| serde_json::json!({}));
1894 if let Some(map) = mapping.as_object_mut() {
1895 map.insert(
1896 upstream_area_id.as_base_64(),
1897 serde_json::json!(twin_id.as_base_64()),
1898 );
1899 }
1900 }
1901 }
1902
1903 fn ensure_memory_replay_mapping(
1904 &mut self,
1905 memory_area_id: &CorticalID,
1906 twin_id: &CorticalID,
1907 ) -> BduResult<()> {
1908 if !self.morphology_registry.contains("memory_replay") {
1909 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
1910 }
1911 self.refresh_morphologies_hash();
1912 let mapping_data = vec![serde_json::json!({
1913 "morphology_id": "memory_replay",
1914 "morphology_scalar": [1, 1, 1],
1915 "postSynapticCurrent_multiplier": 1,
1916 "plasticity_flag": false,
1917 "plasticity_constant": 0,
1918 "ltp_multiplier": 0,
1919 "ltd_multiplier": 0,
1920 "plasticity_window": 0,
1921 })];
1922 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
1923 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
1924 self.refresh_cortical_area_hashes(true, false);
1926 Ok(())
1927 }
1928
1929 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1939 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1940 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
1941 if let Some(arr) = upstream_prop.as_array_mut() {
1942 let src_value = serde_json::json!(src_cortical_idx);
1943 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
1944 arr.remove(pos);
1945 debug!(target: "feagi-bdu",
1946 "Removed upstream area idx={} from cortical area '{}'",
1947 src_cortical_idx, target_cortical_id.as_base_64()
1948 );
1949 }
1950 }
1951 }
1952 }
1953 }
1954
1955 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
1957 self.cortical_areas.contains_key(cortical_id)
1958 }
1959
1960 pub fn is_initialized(&self) -> bool {
1962 self.initialized && !self.cortical_areas.is_empty()
1963 }
1964
1965 pub fn add_brain_region(
1971 &mut self,
1972 region: BrainRegion,
1973 parent_id: Option<String>,
1974 ) -> BduResult<()> {
1975 self.brain_regions.add_region(region, parent_id)?;
1976 self.refresh_brain_regions_hash();
1977 Ok(())
1978 }
1979
1980 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
1982 self.brain_regions.remove_region(region_id)?;
1983 self.refresh_brain_regions_hash();
1984 Ok(())
1985 }
1986
1987 pub fn change_brain_region_parent(
1989 &mut self,
1990 region_id: &str,
1991 new_parent_id: &str,
1992 ) -> BduResult<()> {
1993 self.brain_regions.change_parent(region_id, new_parent_id)?;
1994 self.refresh_brain_regions_hash();
1995 Ok(())
1996 }
1997
1998 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
2000 self.brain_regions.get_region(region_id)
2001 }
2002
2003 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
2005 self.brain_regions.get_region_mut(region_id)
2006 }
2007
2008 pub fn get_brain_region_ids(&self) -> Vec<&String> {
2010 self.brain_regions.get_all_region_ids()
2011 }
2012
2013 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
2015 &self.brain_regions
2016 }
2017
2018 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
2024 &self.morphology_registry
2025 }
2026
2027 pub fn get_morphology_count(&self) -> usize {
2029 self.morphology_registry.count()
2030 }
2031
2032 pub fn upsert_morphology(
2037 &mut self,
2038 morphology_id: String,
2039 morphology: feagi_evolutionary::Morphology,
2040 ) {
2041 self.morphology_registry
2042 .add_morphology(morphology_id, morphology);
2043 self.refresh_morphologies_hash();
2044 }
2045
2046 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
2052 let removed = self.morphology_registry.remove_morphology(morphology_id);
2053 if removed {
2054 self.refresh_morphologies_hash();
2055 }
2056 removed
2057 }
2058
2059 pub fn update_cortical_mapping(
2077 &mut self,
2078 src_area_id: &CorticalID,
2079 dst_area_id: &CorticalID,
2080 mapping_data: Vec<serde_json::Value>,
2081 ) -> BduResult<()> {
2082 use tracing::info;
2083
2084 crate::region_io_designation::validate_cross_region_mapping_proposal(
2085 self,
2086 src_area_id,
2087 dst_area_id,
2088 &mapping_data,
2089 )?;
2090
2091 debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
2092
2093 {
2094 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
2096 crate::types::BduError::InvalidArea(format!(
2097 "Source area not found: {}",
2098 src_area_id
2099 ))
2100 })?;
2101
2102 let cortical_mapping_dst =
2104 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
2105 existing.as_object_mut().ok_or_else(|| {
2106 crate::types::BduError::InvalidMorphology(
2107 "cortical_mapping_dst is not an object".to_string(),
2108 )
2109 })?
2110 } else {
2111 src_area
2113 .properties
2114 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
2115 src_area
2116 .properties
2117 .get_mut("cortical_mapping_dst")
2118 .unwrap()
2119 .as_object_mut()
2120 .unwrap()
2121 };
2122
2123 if mapping_data.is_empty() {
2125 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
2127 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
2128 } else {
2129 cortical_mapping_dst.insert(
2130 dst_area_id.as_base_64(),
2131 serde_json::Value::Array(mapping_data.clone()),
2132 );
2133 debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
2134 src_area_id, dst_area_id, mapping_data.len());
2135 }
2136 }
2137
2138 self.refresh_cortical_mappings_hash();
2139
2140 Ok(())
2141 }
2142
2143 pub fn regenerate_synapses_for_mapping(
2156 &mut self,
2157 src_area_id: &CorticalID,
2158 dst_area_id: &CorticalID,
2159 ) -> BduResult<usize> {
2160 use tracing::info;
2161
2162 debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
2163
2164 let mapping_rules_len = self
2165 .cortical_areas
2166 .get(src_area_id)
2167 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2168 .and_then(|v| v.as_object())
2169 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2170 .and_then(|v| v.as_array())
2171 .map(|arr| arr.len())
2172 .unwrap_or(0);
2173 tracing::debug!(
2174 target: "feagi-bdu",
2175 "Mapping rules for {} -> {}: {}",
2176 src_area_id,
2177 dst_area_id,
2178 mapping_rules_len
2179 );
2180
2181 let Some(npu_arc) = self.npu.clone() else {
2183 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
2184 return Ok(0);
2185 };
2186
2187 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2197 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
2198 })?;
2199 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2200 BduError::InvalidArea(format!(
2201 "No cortical idx for destination area {}",
2202 dst_area_id
2203 ))
2204 })?;
2205
2206 let mut pruned_synapse_count: usize = 0;
2209 use std::time::Instant;
2210 let start = Instant::now();
2211
2212 let (sources, targets) = {
2218 let lock_start = std::time::Instant::now();
2219 let npu = npu_arc.lock().unwrap();
2220 let lock_wait = lock_start.elapsed();
2221 tracing::debug!(
2222 target: "feagi-bdu",
2223 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
2224 lock_wait.as_secs_f64() * 1000.0,
2225 src_area_id,
2226 dst_area_id
2227 );
2228 let sources: Vec<NeuronId> = npu
2229 .get_neurons_in_cortical_area(src_idx)
2230 .into_iter()
2231 .map(NeuronId)
2232 .collect();
2233 let targets: Vec<NeuronId> = npu
2234 .get_neurons_in_cortical_area(dst_idx)
2235 .into_iter()
2236 .map(NeuronId)
2237 .collect();
2238 (sources, targets)
2239 };
2240
2241 tracing::debug!(
2242 target: "feagi-bdu",
2243 "Prune synapses: {} sources, {} targets",
2244 sources.len(),
2245 targets.len()
2246 );
2247
2248 if !sources.is_empty() && !targets.is_empty() {
2249 let remove_start = Instant::now();
2250 pruned_synapse_count = {
2251 let lock_start = std::time::Instant::now();
2252 let mut npu = npu_arc.lock().unwrap();
2253 let lock_wait = lock_start.elapsed();
2254 tracing::debug!(
2255 target: "feagi-bdu",
2256 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
2257 lock_wait.as_secs_f64() * 1000.0,
2258 src_area_id,
2259 dst_area_id
2260 );
2261 npu.remove_synapses_between(sources, targets)
2265 };
2266 let remove_time = remove_start.elapsed();
2267 let total_time = start.elapsed();
2268
2269 info!(
2270 target: "feagi-bdu",
2271 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
2272 pruned_synapse_count,
2273 src_area_id,
2274 dst_area_id,
2275 total_time.as_millis(),
2276 remove_time.as_millis()
2277 );
2278
2279 if pruned_synapse_count > 0 {
2281 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
2282 BduError::Internal(format!(
2283 "Pruned synapse count overflow (usize -> u32): {}",
2284 pruned_synapse_count
2285 ))
2286 })?;
2287 let state_manager = StateManager::instance();
2288 let state_manager = state_manager.read();
2289 let core_state = state_manager.get_core_state();
2290 core_state.subtract_synapse_count(pruned_u32);
2291 state_manager.subtract_cortical_area_outgoing_synapses(
2292 &src_area_id.as_base_64(),
2293 pruned_synapse_count,
2294 );
2295 state_manager.subtract_cortical_area_incoming_synapses(
2296 &dst_area_id.as_base_64(),
2297 pruned_synapse_count,
2298 );
2299
2300 {
2304 let mut cache = self.cached_synapse_counts_per_area.write();
2305 let entry = cache
2306 .entry(*src_area_id)
2307 .or_insert_with(|| AtomicUsize::new(0));
2308 let mut current = entry.load(Ordering::Relaxed);
2309 loop {
2310 let next = current.saturating_sub(pruned_synapse_count);
2311 match entry.compare_exchange(
2312 current,
2313 next,
2314 Ordering::Relaxed,
2315 Ordering::Relaxed,
2316 ) {
2317 Ok(_) => break,
2318 Err(v) => current = v,
2319 }
2320 }
2321 }
2322 }
2323 }
2324
2325 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
2332 tracing::debug!(
2333 target: "feagi-bdu",
2334 "Synaptogenesis created {} synapses for {} -> {}",
2335 synapse_count,
2336 src_area_id,
2337 dst_area_id
2338 );
2339
2340 if synapse_count > 0 {
2343 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
2344 BduError::Internal(format!(
2345 "Created synapse count overflow (usize -> u32): {}",
2346 synapse_count
2347 ))
2348 })?;
2349
2350 {
2352 let mut cache = self.cached_synapse_counts_per_area.write();
2353 cache
2354 .entry(*src_area_id)
2355 .or_insert_with(|| AtomicUsize::new(0))
2356 .fetch_add(synapse_count, Ordering::Relaxed);
2357 }
2358
2359 let state_manager = StateManager::instance();
2361 let state_manager = state_manager.read();
2362 let core_state = state_manager.get_core_state();
2363 core_state.add_synapse_count(created_u32);
2364 state_manager
2365 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
2366 state_manager
2367 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
2368 }
2369
2370 let src_idx_for_upstream = src_idx;
2373
2374 let has_mapping = self
2376 .cortical_areas
2377 .get(src_area_id)
2378 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2379 .and_then(|v| v.as_object())
2380 .and_then(|map| map.get(&dst_area_id.as_base_64()))
2381 .is_some();
2382
2383 debug!(target: "feagi-bdu",
2384 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
2385 synapse_count,
2386 src_area_id.as_base_64(),
2387 dst_area_id.as_base_64(),
2388 has_mapping,
2389 if has_mapping { "" } else { "NOT " }
2390 );
2391
2392 if has_mapping {
2393 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
2395
2396 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2397 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_)) {
2398 if let Err(e) = self.ensure_memory_twin_area(dst_area_id, src_area_id) {
2399 warn!(
2400 target: "feagi-bdu",
2401 "Failed to ensure memory twin for {} -> {}: {}",
2402 src_area_id.as_base_64(),
2403 dst_area_id.as_base_64(),
2404 e
2405 );
2406 }
2407 }
2408 }
2409
2410 #[cfg(feature = "plasticity")]
2412 if let Some(ref executor) = self.plasticity_executor {
2413 use feagi_evolutionary::extract_memory_properties;
2414
2415 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
2416 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
2417 let upstream_areas =
2418 self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
2419 debug!(
2420 target: "feagi-bdu",
2421 "Registering memory area idx={} id={} upstream={} depth={}",
2422 dst_area.cortical_idx,
2423 dst_area_id.as_base_64(),
2424 upstream_areas.len(),
2425 mem_props.temporal_depth
2426 );
2427
2428 if let Some(ref npu_arc) = self.npu {
2431 if let Ok(mut npu) = npu_arc.lock() {
2432 let existing_configs = npu.get_all_fire_ledger_configs();
2433 for &upstream_idx in &upstream_areas {
2434 let existing = existing_configs
2435 .iter()
2436 .find(|(idx, _)| *idx == upstream_idx)
2437 .map(|(_, w)| *w)
2438 .unwrap_or(0);
2439
2440 let desired = mem_props.temporal_depth as usize;
2441 let resolved = existing.max(desired);
2442 if resolved != existing {
2443 if let Err(e) =
2444 npu.configure_fire_ledger_window(upstream_idx, resolved)
2445 {
2446 warn!(
2447 target: "feagi-bdu",
2448 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
2449 upstream_idx,
2450 resolved,
2451 e
2452 );
2453 }
2454 }
2455 }
2456 } else {
2457 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
2458 }
2459 }
2460
2461 if let Ok(exec) = executor.lock() {
2462 use feagi_npu_plasticity::{
2463 MemoryNeuronLifecycleConfig, PlasticityExecutor,
2464 };
2465
2466 let lifecycle_config = MemoryNeuronLifecycleConfig {
2467 initial_lifespan: mem_props.init_lifespan,
2468 lifespan_growth_rate: mem_props.lifespan_growth_rate,
2469 longterm_threshold: mem_props.longterm_threshold,
2470 max_reactivations: 1000,
2471 };
2472
2473 exec.register_memory_area(
2474 dst_area.cortical_idx,
2475 dst_area_id.as_base_64(),
2476 mem_props.temporal_depth,
2477 upstream_areas.clone(),
2478 Some(lifecycle_config),
2479 mem_props.mp_learning_enabled,
2480 );
2481 } else {
2482 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
2483 }
2484 } else {
2485 debug!(
2486 target: "feagi-bdu",
2487 "Skipping plasticity registration: no memory properties for area {}",
2488 dst_area_id.as_base_64()
2489 );
2490 }
2491 } else {
2492 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
2493 }
2494 } else {
2495 warn!(
2496 target: "feagi-bdu",
2497 "PlasticityExecutor not available; memory area {} not registered",
2498 dst_area_id.as_base_64()
2499 );
2500 }
2501
2502 #[cfg(not(feature = "plasticity"))]
2503 {
2504 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
2505 }
2506 } else {
2507 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
2509
2510 let mut npu = npu_arc.lock().unwrap();
2512 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
2513 }
2514
2515 debug!(
2516 target: "feagi-bdu",
2517 "Created {} new synapses: {} -> {}",
2518 synapse_count,
2519 src_area_id,
2520 dst_area_id
2521 );
2522
2523 if pruned_synapse_count > 0 || synapse_count == 0 {
2526 let mut npu = npu_arc.lock().unwrap();
2527 npu.rebuild_synapse_index();
2528 info!(
2529 target: "feagi-bdu",
2530 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
2531 src_area_id,
2532 dst_area_id,
2533 pruned_synapse_count,
2534 synapse_count
2535 );
2536 } else {
2537 debug!(
2538 target: "feagi-bdu",
2539 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
2540 src_area_id,
2541 dst_area_id,
2542 synapse_count
2543 );
2544 }
2545
2546 {
2548 let npu = npu_arc.lock().unwrap();
2549 let fresh_count = npu.get_synapse_count();
2550 self.cached_synapse_count
2551 .store(fresh_count, Ordering::Relaxed);
2552 }
2553
2554 Ok(synapse_count)
2555 }
2556
2557 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
2559 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
2560 }
2561
2562 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
2563 v.as_u64()
2564 .map(|n| n as usize)
2565 .or_else(|| v.as_f64().map(|f| f as usize))
2566 }
2567
2568 #[allow(clippy::too_many_arguments)]
2570 fn register_stdp_mapping_for_rule(
2571 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
2572 src_area_id: &CorticalID,
2573 dst_area_id: &CorticalID,
2574 src_cortical_idx: u32,
2575 dst_cortical_idx: u32,
2576 rule_obj: &serde_json::Map<String, serde_json::Value>,
2577 bidirectional_stdp: bool,
2578 synapse_psp: f32,
2579 synapse_type: feagi_npu_neural::SynapseType,
2580 ) -> BduResult<()> {
2581 let plasticity_window = rule_obj
2582 .get("plasticity_window")
2583 .and_then(Self::json_number_as_usize_for_stdp)
2584 .ok_or_else(|| {
2585 BduError::Internal(format!(
2586 "Missing plasticity_window in plastic mapping rule {} -> {}",
2587 src_area_id, dst_area_id
2588 ))
2589 })?;
2590 let plasticity_constant = rule_obj
2591 .get("plasticity_constant")
2592 .and_then(Self::json_number_as_i64_for_stdp)
2593 .ok_or_else(|| {
2594 BduError::Internal(format!(
2595 "Missing plasticity_constant in plastic mapping rule {} -> {}",
2596 src_area_id, dst_area_id
2597 ))
2598 })?;
2599 let ltp_i64 = rule_obj
2600 .get("ltp_multiplier")
2601 .and_then(Self::json_number_as_i64_for_stdp)
2602 .ok_or_else(|| {
2603 BduError::Internal(format!(
2604 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
2605 src_area_id, dst_area_id
2606 ))
2607 })?;
2608 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
2609 BduError::Internal(format!(
2610 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2611 i8::MIN,
2612 i8::MAX,
2613 ltp_i64,
2614 src_area_id,
2615 dst_area_id
2616 ))
2617 })?;
2618 let ltd_i64 = rule_obj
2619 .get("ltd_multiplier")
2620 .and_then(Self::json_number_as_i64_for_stdp)
2621 .ok_or_else(|| {
2622 BduError::Internal(format!(
2623 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
2624 src_area_id, dst_area_id
2625 ))
2626 })?;
2627 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
2628 BduError::Internal(format!(
2629 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
2630 i8::MIN,
2631 i8::MAX,
2632 ltd_i64,
2633 src_area_id,
2634 dst_area_id
2635 ))
2636 })?;
2637
2638 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
2642 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
2643 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2644 }
2645 Some(s) if s.eq_ignore_ascii_case("stdp") => {
2646 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
2647 }
2648 Some(s) if s.eq_ignore_ascii_case("off") => {
2649 feagi_npu_burst_engine::npu::PlasticityMode::Off
2650 }
2651 Some(other) => {
2652 return Err(BduError::Internal(format!(
2653 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
2654 other, src_area_id, dst_area_id
2655 )));
2656 }
2657 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
2658 };
2659
2660 let eligibility_decay_bursts = rule_obj
2662 .get("eligibility_decay_bursts")
2663 .and_then(|v| v.as_u64())
2664 .map(|n| n as u32)
2665 .unwrap_or(0);
2666 let reward_source_area_id = rule_obj
2667 .get("reward_source_area")
2668 .and_then(|v| v.as_str())
2669 .map(str::to_string);
2670 let punishment_source_area_id = rule_obj
2671 .get("punishment_source_area")
2672 .and_then(|v| v.as_str())
2673 .map(str::to_string);
2674
2675 let max_weight_provided = rule_obj.get("max_weight").is_some()
2680 && !rule_obj
2681 .get("max_weight")
2682 .map(|v| v.is_null())
2683 .unwrap_or(true);
2684 let max_weight: f32 = if max_weight_provided {
2685 let raw = rule_obj
2686 .get("max_weight")
2687 .and_then(|v| v.as_f64())
2688 .ok_or_else(|| {
2689 BduError::Internal(format!(
2690 "max_weight must be a number on mapping {} -> {}",
2691 src_area_id, dst_area_id
2692 ))
2693 })?;
2694 if raw.is_nan() || raw <= 0.0 {
2695 return Err(BduError::Internal(format!(
2696 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
2697 raw, src_area_id, dst_area_id
2698 )));
2699 }
2700 raw as f32
2701 } else {
2702 f32::INFINITY
2703 };
2704
2705 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
2708 && !rule_obj
2709 .get("plasticity_eta")
2710 .map(|v| v.is_null())
2711 .unwrap_or(true);
2712 let plasticity_eta: f32 = if plasticity_eta_provided {
2713 let raw = rule_obj
2714 .get("plasticity_eta")
2715 .and_then(|v| v.as_f64())
2716 .ok_or_else(|| {
2717 BduError::Internal(format!(
2718 "plasticity_eta must be a number on mapping {} -> {}",
2719 src_area_id, dst_area_id
2720 ))
2721 })?;
2722 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
2723 return Err(BduError::Internal(format!(
2724 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
2725 raw, src_area_id, dst_area_id
2726 )));
2727 }
2728 raw as f32
2729 } else {
2730 1.0
2731 };
2732
2733 if !matches!(
2735 plasticity_mode,
2736 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
2737 ) && (reward_source_area_id.is_some()
2738 || punishment_source_area_id.is_some()
2739 || eligibility_decay_bursts != 0)
2740 {
2741 return Err(BduError::Internal(format!(
2742 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
2743 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
2744 src_area_id, dst_area_id
2745 )));
2746 }
2747
2748 if matches!(
2752 plasticity_mode,
2753 feagi_npu_burst_engine::npu::PlasticityMode::Off
2754 ) && max_weight_provided
2755 {
2756 return Err(BduError::Internal(format!(
2757 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2758 src_area_id, dst_area_id
2759 )));
2760 }
2761
2762 if matches!(
2763 plasticity_mode,
2764 feagi_npu_burst_engine::npu::PlasticityMode::Off
2765 ) && plasticity_eta_provided
2766 {
2767 return Err(BduError::Internal(format!(
2768 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
2769 src_area_id, dst_area_id
2770 )));
2771 }
2772
2773 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
2774 let mut npu_lock = npu
2775 .lock()
2776 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
2777 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
2778
2779 let resolve_optional_area =
2784 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
2785 let Some(name) = name_opt else {
2786 return Ok(None);
2787 };
2788 match npu_lock.get_cortical_area_id(name.as_str()) {
2789 Some(idx) => Ok(Some(idx)),
2790 None => Err(BduError::Internal(format!(
2791 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
2792 label, name, src_area_id, dst_area_id
2793 ))),
2794 }
2795 };
2796 let reward_source_area =
2797 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
2798 let punishment_source_area =
2799 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
2800
2801 if matches!(
2803 plasticity_mode,
2804 feagi_npu_burst_engine::npu::PlasticityMode::Off
2805 ) {
2806 return Ok(());
2807 }
2808
2809 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
2810 plasticity_window,
2811 plasticity_constant,
2812 ltp_multiplier,
2813 ltd_multiplier,
2814 bidirectional_stdp,
2815 synapse_psp,
2816 synapse_type,
2817 plasticity_mode,
2818 eligibility_decay_bursts,
2819 reward_source_area,
2820 punishment_source_area,
2821 max_weight,
2822 plasticity_eta,
2823 };
2824
2825 npu_lock
2826 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
2827 .map_err(|e| {
2828 BduError::Internal(format!(
2829 "Failed to register STDP mapping {} -> {}: {}",
2830 src_area_id, dst_area_id, e
2831 ))
2832 })?;
2833
2834 let mut areas_to_track: Vec<(u32, usize)> = vec![
2838 (src_cortical_idx, plasticity_window),
2839 (dst_cortical_idx, plasticity_window),
2840 ];
2841 if let Some(area) = reward_source_area {
2842 areas_to_track.push((area, 1));
2843 }
2844 if let Some(area) = punishment_source_area {
2845 areas_to_track.push((area, 1));
2846 }
2847
2848 let existing_configs = npu_lock.get_all_fire_ledger_configs();
2849 for (area_idx, required_depth) in areas_to_track {
2850 let existing = existing_configs
2851 .iter()
2852 .find(|(idx, _)| *idx == area_idx)
2853 .map(|(_, w)| *w)
2854 .unwrap_or(0);
2855 let resolved = existing.max(required_depth);
2856 if resolved != existing {
2857 npu_lock
2858 .configure_fire_ledger_window(area_idx, resolved)
2859 .map_err(|e| {
2860 BduError::Internal(format!(
2861 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
2862 area_idx, resolved, e
2863 ))
2864 })?;
2865 }
2866 }
2867
2868 Ok(())
2869 }
2870
2871 fn resolve_synapse_params_for_rule(
2873 &self,
2874 src_area_id: &CorticalID,
2875 rule: &serde_json::Value,
2876 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
2877 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2879 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
2880 })?;
2881
2882 let (weight, synapse_type) = {
2884 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
2885 if let Some(i) = v.as_i64() {
2886 return Some(i as f64);
2887 }
2888 v.as_f64()
2889 };
2890
2891 let mult: f64 = if let Some(obj) = rule.as_object() {
2892 obj.get("postSynapticCurrent_multiplier")
2893 .and_then(parse_f64)
2894 .unwrap_or(1.0)
2895 } else if let Some(arr) = rule.as_array() {
2896 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
2897 } else {
2898 128.0
2899 };
2900
2901 if mult < 0.0 {
2902 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
2903 } else {
2904 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
2905 }
2906 };
2907
2908 use crate::models::cortical_area::CorticalAreaExt;
2910 let psp_f32 = src_area.postsynaptic_current();
2911
2912 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
2913 obj.get("synaptic_delay_bursts")
2914 .and_then(|v| v.as_u64())
2915 .map(|d| u8::try_from(d).unwrap_or(1))
2916 .unwrap_or(1)
2917 } else if let Some(arr) = rule.as_array() {
2918 arr.get(8)
2919 .and_then(|v| v.as_u64())
2920 .map(|d| u8::try_from(d).unwrap_or(1))
2921 .unwrap_or(1)
2922 } else {
2923 1
2924 };
2925 if delay_bursts < 1 {
2926 return Err(crate::types::BduError::Internal(format!(
2927 "synaptic_delay_bursts must be >= 1 (src area {})",
2928 src_area_id.as_base_64()
2929 )));
2930 }
2931
2932 tracing::debug!(
2933 target: "feagi-bdu",
2934 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
2935 src_area_id.as_base_64(),
2936 weight,
2937 psp_f32,
2938 synapse_type,
2939 delay_bursts
2940 );
2941
2942 Ok((weight, psp_f32, synapse_type, delay_bursts))
2943 }
2944
2945 fn apply_cortical_mapping_for_pair(
2947 &mut self,
2948 src_area_id: &CorticalID,
2949 dst_area_id: &CorticalID,
2950 ) -> BduResult<usize> {
2951 let rules = {
2957 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
2958 crate::types::BduError::InvalidArea(format!(
2959 "Source area not found: {}",
2960 src_area_id
2961 ))
2962 })?;
2963
2964 let Some(mapping_dst) = src_area
2965 .properties
2966 .get("cortical_mapping_dst")
2967 .and_then(|v| v.as_object())
2968 else {
2969 return Ok(0);
2970 };
2971
2972 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
2973 else {
2974 return Ok(0);
2975 };
2976
2977 rules.clone()
2978 }; if rules.is_empty() {
2981 return Ok(0);
2982 }
2983
2984 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
2986 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
2987 })?;
2988 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
2989 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
2990 })?;
2991
2992 let npu_arc = self
2994 .npu
2995 .as_ref()
2996 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
2997 .clone();
2998
2999 tracing::debug!(
3000 target: "feagi-bdu",
3001 "Applying {} mapping rule(s) for {} -> {}",
3002 rules.len(),
3003 src_area_id,
3004 dst_area_id
3005 );
3006 let mut total_synapses = 0;
3008 for rule in &rules {
3009 let morphology_id = if let Some(rule_obj) = rule.as_object() {
3010 rule_obj
3011 .get("morphology_id")
3012 .and_then(|v| v.as_str())
3013 .unwrap_or("unknown")
3014 .to_string()
3015 } else if let Some(rule_arr) = rule.as_array() {
3016 rule_arr
3017 .first()
3018 .and_then(|v| v.as_str())
3019 .unwrap_or("unknown")
3020 .to_string()
3021 } else {
3022 "unknown".to_string()
3023 };
3024
3025 let rule_keys: Vec<String> = rule
3026 .as_object()
3027 .map(|obj| obj.keys().cloned().collect())
3028 .unwrap_or_default();
3029
3030 let mut plasticity_flag = rule
3032 .as_object()
3033 .and_then(|obj| obj.get("plasticity_flag"))
3034 .and_then(|v| v.as_bool())
3035 .unwrap_or(false);
3036 if morphology_id == "associative_memory" {
3037 plasticity_flag = true;
3038 }
3039 if plasticity_flag {
3040 let Some(rule_obj) = rule.as_object() else {
3041 return Err(crate::types::BduError::InvalidMorphology(
3042 "Plasticity mapping rule must be an object format".to_string(),
3043 ));
3044 };
3045 let (_weight, psp, synapse_type, _delay_bursts) =
3046 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3047 let bidirectional_stdp = false;
3050 if let Err(e) = Self::register_stdp_mapping_for_rule(
3051 &npu_arc,
3052 src_area_id,
3053 dst_area_id,
3054 src_cortical_idx,
3055 dst_cortical_idx,
3056 rule_obj,
3057 bidirectional_stdp,
3058 psp,
3059 synapse_type,
3060 ) {
3061 tracing::error!(
3062 target: "feagi-bdu",
3063 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
3064 src_area_id,
3065 dst_area_id,
3066 morphology_id,
3067 rule_keys,
3068 e
3069 );
3070 return Err(e);
3071 }
3072 }
3073
3074 if let Some(gate_area_str) = rule
3078 .as_object()
3079 .and_then(|obj| obj.get("gate_source_area"))
3080 .and_then(|v| v.as_str())
3081 {
3082 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
3083 crate::types::BduError::Internal(format!(
3084 "Invalid gate_source_area '{}' on mapping {} -> {}",
3085 gate_area_str, src_area_id, dst_area_id
3086 ))
3087 })?;
3088 let gate_cortical_idx =
3089 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
3090 crate::types::BduError::Internal(format!(
3091 "Unknown gate_source_area '{}' on mapping {} -> {}",
3092 gate_area_str, src_area_id, dst_area_id
3093 ))
3094 })?;
3095
3096 let mut npu_lock = npu_arc.lock().map_err(|_| {
3097 crate::types::BduError::Internal(
3098 "Failed to acquire NPU lock for gate registration".to_string(),
3099 )
3100 })?;
3101 if let Err(e) = npu_lock.register_gate_mapping(
3102 src_cortical_idx,
3103 dst_cortical_idx,
3104 *gate_cortical_idx,
3105 ) {
3106 tracing::error!(
3107 target: "feagi-bdu",
3108 "Gate mapping registration failed for {} -> {} (gate={}): {}",
3109 src_area_id,
3110 dst_area_id,
3111 gate_area_str,
3112 e
3113 );
3114 return Err(crate::types::BduError::Internal(format!(
3115 "Gate registration failed: {}",
3116 e
3117 )));
3118 }
3119 }
3120
3121 let synapse_count = match self.apply_single_morphology_rule(
3123 src_area_id,
3124 dst_area_id,
3125 rule,
3126 ) {
3127 Ok(count) => count,
3128 Err(e) => {
3129 tracing::error!(
3130 target: "feagi-bdu",
3131 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
3132 src_area_id,
3133 dst_area_id,
3134 morphology_id,
3135 rule_keys,
3136 e
3137 );
3138 return Err(e);
3139 }
3140 };
3141 total_synapses += synapse_count;
3142 tracing::debug!(
3143 target: "feagi-bdu",
3144 "Rule {} created {} synapses for {} -> {}",
3145 morphology_id,
3146 synapse_count,
3147 src_area_id,
3148 dst_area_id
3149 );
3150 }
3151
3152 Ok(total_synapses)
3153 }
3154
3155 #[allow(clippy::too_many_arguments)]
3169 fn apply_function_morphology(
3170 &self,
3171 morphology_id: &str,
3172 rule: &serde_json::Value,
3173 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3174 npu: &mut feagi_npu_burst_engine::DynamicNPU,
3175 src_area_id: &CorticalID,
3176 dst_area_id: &CorticalID,
3177 src_idx: u32,
3178 dst_idx: u32,
3179 weight: f32,
3180 psp: f32,
3181 synapse_attractivity: u8,
3182 synapse_type: feagi_npu_neural::SynapseType,
3183 delay_bursts: u8,
3184 ) -> BduResult<usize> {
3185 match morphology_id {
3186 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
3187 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3189 crate::types::BduError::InvalidArea(format!(
3190 "Source area not found: {}",
3191 src_area_id
3192 ))
3193 })?;
3194 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3195 crate::types::BduError::InvalidArea(format!(
3196 "Destination area not found: {}",
3197 dst_area_id
3198 ))
3199 })?;
3200
3201 let src_dimensions = (
3202 src_area.dimensions.width as usize,
3203 src_area.dimensions.height as usize,
3204 src_area.dimensions.depth as usize,
3205 );
3206 let dst_dimensions = (
3207 dst_area.dimensions.width as usize,
3208 dst_area.dimensions.height as usize,
3209 dst_area.dimensions.depth as usize,
3210 );
3211
3212 let transpose = match morphology_id {
3215 "transpose_xy" => Some((1, 0, 2)),
3216 "transpose_yz" => Some((0, 2, 1)),
3217 "transpose_xz" => Some((2, 1, 0)),
3218 _ => None,
3219 };
3220
3221 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3222 let count = apply_projector_morphology_with_dimensions(
3223 npu,
3224 src_idx,
3225 dst_idx,
3226 src_dimensions,
3227 dst_dimensions,
3228 transpose,
3229 None, weight,
3231 psp,
3232 synapse_attractivity,
3233 synapse_type,
3234 0,
3235 delay_bursts,
3236 )?;
3237 npu.rebuild_synapse_index();
3239 Ok(count as usize)
3240 }
3241 "centered_projector" => {
3242 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3243 crate::types::BduError::InvalidArea(format!(
3244 "Source area not found: {}",
3245 src_area_id
3246 ))
3247 })?;
3248 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3249 crate::types::BduError::InvalidArea(format!(
3250 "Destination area not found: {}",
3251 dst_area_id
3252 ))
3253 })?;
3254
3255 let src_dimensions = (
3256 src_area.dimensions.width as usize,
3257 src_area.dimensions.height as usize,
3258 src_area.dimensions.depth as usize,
3259 );
3260 let dst_dimensions = (
3261 dst_area.dimensions.width as usize,
3262 dst_area.dimensions.height as usize,
3263 dst_area.dimensions.depth as usize,
3264 );
3265
3266 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
3267 npu,
3268 src_idx,
3269 dst_idx,
3270 src_dimensions,
3271 dst_dimensions,
3272 weight,
3273 psp,
3274 synapse_attractivity,
3275 synapse_type,
3276 delay_bursts,
3277 )?;
3278 if count > 0 {
3279 npu.rebuild_synapse_index();
3280 }
3281 Ok(count as usize)
3282 }
3283 "episodic_memory" => {
3284 use tracing::trace;
3287 trace!(
3288 target: "feagi-bdu",
3289 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
3290 src_idx, dst_idx
3291 );
3292 Ok(0)
3293 }
3294 "memory_replay" => {
3295 use tracing::trace;
3297 trace!(
3298 target: "feagi-bdu",
3299 "Memory replay morphology: {} -> {} (no physical synapses)",
3300 src_idx, dst_idx
3301 );
3302 Ok(0)
3303 }
3304 "associative_memory" => {
3305 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3309 crate::types::BduError::InvalidArea(format!(
3310 "Source area not found: {}",
3311 src_area_id
3312 ))
3313 })?;
3314 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3315 crate::types::BduError::InvalidArea(format!(
3316 "Destination area not found: {}",
3317 dst_area_id
3318 ))
3319 })?;
3320
3321 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3322 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3323 {
3324 let src_dimensions = (
3325 src_area.dimensions.width as usize,
3326 src_area.dimensions.height as usize,
3327 src_area.dimensions.depth as usize,
3328 );
3329 let dst_dimensions = (
3330 dst_area.dimensions.width as usize,
3331 dst_area.dimensions.height as usize,
3332 dst_area.dimensions.depth as usize,
3333 );
3334 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
3335 let count = apply_projector_morphology_with_dimensions(
3336 npu,
3337 src_idx,
3338 dst_idx,
3339 src_dimensions,
3340 dst_dimensions,
3341 None,
3342 None,
3343 weight,
3344 psp,
3345 synapse_attractivity,
3346 synapse_type,
3347 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
3348 delay_bursts,
3349 )?;
3350 npu.rebuild_synapse_index();
3351 Ok(count as usize)
3352 } else {
3353 Ok(0)
3354 }
3355 }
3356 "block_to_block" => {
3357 tracing::warn!(
3358 target: "feagi-bdu",
3359 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
3360 src_idx, dst_idx
3361 );
3362 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3364 crate::types::BduError::InvalidArea(format!(
3365 "Source area not found: {}",
3366 src_area_id
3367 ))
3368 })?;
3369 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3370 crate::types::BduError::InvalidArea(format!(
3371 "Destination area not found: {}",
3372 dst_area_id
3373 ))
3374 })?;
3375
3376 let src_dimensions = (
3377 src_area.dimensions.width as usize,
3378 src_area.dimensions.height as usize,
3379 src_area.dimensions.depth as usize,
3380 );
3381 let dst_dimensions = (
3382 dst_area.dimensions.width as usize,
3383 dst_area.dimensions.height as usize,
3384 dst_area.dimensions.depth as usize,
3385 );
3386
3387 let scalar = if let Some(obj) = rule.as_object() {
3389 if let Some(scalar_arr) =
3391 obj.get("morphology_scalar").and_then(|v| v.as_array())
3392 {
3393 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
3395 } else {
3396 1 }
3398 } else if let Some(arr) = rule.as_array() {
3399 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
3401 } else {
3402 1 };
3404
3405 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
3408 let count = if estimated_neurons > 100_000 {
3409 let _ = npu;
3411
3412 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
3413 npu_arc,
3414 src_idx,
3415 dst_idx,
3416 src_dimensions,
3417 dst_dimensions,
3418 scalar, weight,
3420 psp,
3421 synapse_attractivity,
3422 synapse_type,
3423 delay_bursts,
3424 )? as usize
3425 } else {
3426 tracing::warn!(
3428 target: "feagi-bdu",
3429 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
3430 src_idx, dst_idx, src_dimensions, dst_dimensions
3431 );
3432 let count =
3433 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
3434 npu,
3435 src_idx,
3436 dst_idx,
3437 src_dimensions,
3438 dst_dimensions,
3439 scalar, weight,
3441 psp,
3442 synapse_attractivity,
3443 synapse_type,
3444 delay_bursts,
3445 )? as usize;
3446 tracing::warn!(
3447 target: "feagi-bdu",
3448 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
3449 count
3450 );
3451 if count > 0 {
3453 npu.rebuild_synapse_index();
3454 }
3455 count
3456 };
3457
3458 if count > 0 && estimated_neurons > 100_000 {
3460 let mut npu_lock = npu_arc.lock().unwrap();
3461 npu_lock.rebuild_synapse_index();
3462 }
3463
3464 Ok(count)
3465 }
3466 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
3467 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
3468 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3469 crate::types::BduError::InvalidArea(format!(
3470 "Source area not found: {}",
3471 src_area_id
3472 ))
3473 })?;
3474 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3475 crate::types::BduError::InvalidArea(format!(
3476 "Destination area not found: {}",
3477 dst_area_id
3478 ))
3479 })?;
3480
3481 let src_dimensions = (
3482 src_area.dimensions.width as usize,
3483 src_area.dimensions.height as usize,
3484 src_area.dimensions.depth as usize,
3485 );
3486 let dst_dimensions = (
3487 dst_area.dimensions.width as usize,
3488 dst_area.dimensions.height as usize,
3489 dst_area.dimensions.depth as usize,
3490 );
3491
3492 let (axis, mode) = match morphology_id {
3493 "bitmask_encoder_x" => (
3494 crate::connectivity::core_morphologies::BitmaskAxis::X,
3495 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3496 ),
3497 "bitmask_encoder_y" => (
3498 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3499 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3500 ),
3501 "bitmask_encoder_z" => (
3502 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3503 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
3504 ),
3505 "bitmask_decoder_x" => (
3506 crate::connectivity::core_morphologies::BitmaskAxis::X,
3507 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3508 ),
3509 "bitmask_decoder_y" => (
3510 crate::connectivity::core_morphologies::BitmaskAxis::Y,
3511 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3512 ),
3513 "bitmask_decoder_z" => (
3514 crate::connectivity::core_morphologies::BitmaskAxis::Z,
3515 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
3516 ),
3517 _ => unreachable!("matched bitmask morphology above"),
3518 };
3519
3520 let count =
3521 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
3522 npu,
3523 src_idx,
3524 dst_idx,
3525 src_dimensions,
3526 dst_dimensions,
3527 axis,
3528 mode,
3529 weight,
3530 psp,
3531 synapse_attractivity,
3532 synapse_type,
3533 delay_bursts,
3534 )?;
3535 if count > 0 {
3536 npu.rebuild_synapse_index();
3537 }
3538 Ok(count as usize)
3539 }
3540 "sweeper" => {
3541 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3542 crate::types::BduError::InvalidArea(format!(
3543 "Destination area not found: {}",
3544 dst_area_id
3545 ))
3546 })?;
3547 let dst_dimensions = (
3548 dst_area.dimensions.width as usize,
3549 dst_area.dimensions.height as usize,
3550 dst_area.dimensions.depth as usize,
3551 );
3552
3553 let count =
3554 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
3555 npu,
3556 src_idx,
3557 dst_idx,
3558 dst_dimensions,
3559 weight,
3560 psp,
3561 synapse_attractivity,
3562 synapse_type,
3563 delay_bursts,
3564 )?;
3565 if count > 0 {
3566 npu.rebuild_synapse_index();
3567 }
3568 Ok(count as usize)
3569 }
3570 "last_to_first" => {
3571 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3572 crate::types::BduError::InvalidArea(format!(
3573 "Source area not found: {}",
3574 src_area_id
3575 ))
3576 })?;
3577 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3578 crate::types::BduError::InvalidArea(format!(
3579 "Destination area not found: {}",
3580 dst_area_id
3581 ))
3582 })?;
3583 let src_dimensions = (
3584 src_area.dimensions.width as usize,
3585 src_area.dimensions.height as usize,
3586 src_area.dimensions.depth as usize,
3587 );
3588 let dst_dimensions = (
3589 dst_area.dimensions.width as usize,
3590 dst_area.dimensions.height as usize,
3591 dst_area.dimensions.depth as usize,
3592 );
3593
3594 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
3595 npu,
3596 src_idx,
3597 dst_idx,
3598 src_dimensions,
3599 dst_dimensions,
3600 weight,
3601 psp,
3602 synapse_attractivity,
3603 synapse_type,
3604 delay_bursts,
3605 )?;
3606 if count > 0 {
3607 npu.rebuild_synapse_index();
3608 }
3609 Ok(count as usize)
3610 }
3611 "first_to_last" => {
3612 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3613 crate::types::BduError::InvalidArea(format!(
3614 "Source area not found: {}",
3615 src_area_id
3616 ))
3617 })?;
3618 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3619 crate::types::BduError::InvalidArea(format!(
3620 "Destination area not found: {}",
3621 dst_area_id
3622 ))
3623 })?;
3624 let src_dimensions = (
3625 src_area.dimensions.width as usize,
3626 src_area.dimensions.height as usize,
3627 src_area.dimensions.depth as usize,
3628 );
3629 let dst_dimensions = (
3630 dst_area.dimensions.width as usize,
3631 dst_area.dimensions.height as usize,
3632 dst_area.dimensions.depth as usize,
3633 );
3634
3635 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
3636 npu,
3637 src_idx,
3638 dst_idx,
3639 src_dimensions,
3640 dst_dimensions,
3641 weight,
3642 psp,
3643 synapse_attractivity,
3644 synapse_type,
3645 delay_bursts,
3646 )?;
3647 if count > 0 {
3648 npu.rebuild_synapse_index();
3649 }
3650 Ok(count as usize)
3651 }
3652 "rotator_z" => {
3653 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3654 crate::types::BduError::InvalidArea(format!(
3655 "Source area not found: {}",
3656 src_area_id
3657 ))
3658 })?;
3659 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3660 crate::types::BduError::InvalidArea(format!(
3661 "Destination area not found: {}",
3662 dst_area_id
3663 ))
3664 })?;
3665 let src_dimensions = (
3666 src_area.dimensions.width as usize,
3667 src_area.dimensions.height as usize,
3668 src_area.dimensions.depth as usize,
3669 );
3670 let dst_dimensions = (
3671 dst_area.dimensions.width as usize,
3672 dst_area.dimensions.height as usize,
3673 dst_area.dimensions.depth as usize,
3674 );
3675
3676 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
3677 npu,
3678 src_idx,
3679 dst_idx,
3680 src_dimensions,
3681 dst_dimensions,
3682 weight,
3683 psp,
3684 synapse_attractivity,
3685 synapse_type,
3686 delay_bursts,
3687 )?;
3688 if count > 0 {
3689 npu.rebuild_synapse_index();
3690 }
3691 Ok(count as usize)
3692 }
3693 _ => {
3694 use tracing::debug;
3697 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
3698 Ok(0)
3699 }
3700 }
3701 }
3702
3703 fn apply_single_morphology_rule(
3705 &mut self,
3706 src_area_id: &CorticalID,
3707 dst_area_id: &CorticalID,
3708 rule: &serde_json::Value,
3709 ) -> BduResult<usize> {
3710 let morphology_id = if let Some(arr) = rule.as_array() {
3712 arr.first().and_then(|v| v.as_str()).unwrap_or("")
3713 } else if let Some(obj) = rule.as_object() {
3714 obj.get("morphology_id")
3715 .and_then(|v| v.as_str())
3716 .unwrap_or("")
3717 } else {
3718 return Ok(0);
3719 };
3720
3721 if morphology_id.is_empty() {
3722 return Ok(0);
3723 }
3724
3725 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
3727 crate::types::BduError::InvalidMorphology(format!(
3728 "Morphology not found: {}",
3729 morphology_id
3730 ))
3731 })?;
3732
3733 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3735 crate::types::BduError::InvalidArea(format!(
3736 "Source area ID not found: {}",
3737 src_area_id
3738 ))
3739 })?;
3740 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3741 crate::types::BduError::InvalidArea(format!(
3742 "Destination area ID not found: {}",
3743 dst_area_id
3744 ))
3745 })?;
3746
3747 if let Some(ref npu_arc) = self.npu {
3749 let lock_start = std::time::Instant::now();
3750 let mut npu = npu_arc.lock().unwrap();
3751 let lock_wait = lock_start.elapsed();
3752 tracing::debug!(
3753 target: "feagi-bdu",
3754 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
3755 lock_wait.as_secs_f64() * 1000.0,
3756 src_area_id,
3757 dst_area_id,
3758 morphology_id
3759 );
3760
3761 let (weight, psp, synapse_type, delay_bursts) =
3762 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
3763
3764 let synapse_attractivity = if let Some(obj) = rule.as_object() {
3766 obj.get("synapse_attractivity")
3767 .and_then(|v| v.as_u64())
3768 .unwrap_or(100) as u8
3769 } else {
3770 100 };
3772
3773 match morphology.morphology_type {
3774 feagi_evolutionary::MorphologyType::Functions => {
3775 tracing::debug!(
3776 target: "feagi-bdu",
3777 "apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
3778 morphology_id
3779 );
3780 self.apply_function_morphology(
3783 morphology_id,
3784 rule,
3785 npu_arc,
3786 &mut npu,
3787 src_area_id,
3788 dst_area_id,
3789 *src_idx,
3790 *dst_idx,
3791 weight,
3792 psp,
3793 synapse_attractivity,
3794 synapse_type,
3795 delay_bursts,
3796 )
3797 }
3798 feagi_evolutionary::MorphologyType::Vectors => {
3799 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
3800
3801 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3803 crate::types::BduError::InvalidArea(format!(
3804 "Destination area not found: {}",
3805 dst_area_id
3806 ))
3807 })?;
3808
3809 let dst_dimensions = (
3810 dst_area.dimensions.width as usize,
3811 dst_area.dimensions.height as usize,
3812 dst_area.dimensions.depth as usize,
3813 );
3814
3815 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
3816 morphology.parameters
3817 {
3818 let vectors_tuples: Vec<(i32, i32, i32)> =
3820 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
3821
3822 let count = apply_vectors_morphology_with_dimensions(
3823 &mut npu,
3824 *src_idx,
3825 *dst_idx,
3826 vectors_tuples,
3827 dst_dimensions,
3828 weight, psp, synapse_attractivity, synapse_type,
3832 delay_bursts,
3833 )?;
3834 npu.rebuild_synapse_index();
3837 Ok(count as usize)
3838 } else {
3839 Ok(0)
3840 }
3841 }
3842 feagi_evolutionary::MorphologyType::Patterns => {
3843 use crate::connectivity::core_morphologies::apply_patterns_morphology;
3844 use crate::connectivity::rules::patterns::{
3845 Pattern3D, PatternElement as RulePatternElement,
3846 };
3847 use feagi_evolutionary::PatternElement as EvoPatternElement;
3848
3849 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
3850 morphology.parameters
3851 else {
3852 return Ok(0);
3853 };
3854
3855 let convert_element =
3856 |element: &EvoPatternElement|
3857 -> crate::types::BduResult<RulePatternElement> {
3858 match element {
3859 EvoPatternElement::Value(value) => {
3860 if *value < 0 {
3861 return Err(crate::types::BduError::InvalidMorphology(
3862 format!(
3863 "Pattern morphology {} contains negative voxel coordinate {}",
3864 morphology_id, value
3865 ),
3866 ));
3867 }
3868 Ok(RulePatternElement::Exact(*value))
3869 }
3870 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
3871 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
3872 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
3873 EvoPatternElement::DirectionPositive => {
3874 Ok(RulePatternElement::DirectionExclusive(
3875 crate::connectivity::rules::patterns::Direction::Positive,
3876 ))
3877 }
3878 EvoPatternElement::DirectionNegative => {
3879 Ok(RulePatternElement::DirectionExclusive(
3880 crate::connectivity::rules::patterns::Direction::Negative,
3881 ))
3882 }
3883 EvoPatternElement::DirectionPositiveInclusive => {
3884 Ok(RulePatternElement::DirectionInclusive(
3885 crate::connectivity::rules::patterns::Direction::Positive,
3886 ))
3887 }
3888 EvoPatternElement::DirectionNegativeInclusive => {
3889 Ok(RulePatternElement::DirectionInclusive(
3890 crate::connectivity::rules::patterns::Direction::Negative,
3891 ))
3892 }
3893 EvoPatternElement::Offset(off) => {
3894 Ok(RulePatternElement::Offset(*off))
3895 }
3896 EvoPatternElement::Range(lo, hi) => {
3897 Ok(RulePatternElement::Range(*lo, *hi))
3898 }
3899 }
3900 };
3901
3902 let mut converted_patterns = Vec::with_capacity(patterns.len());
3903 for pattern_pair in patterns {
3904 if pattern_pair.len() != 2 {
3905 return Err(crate::types::BduError::InvalidMorphology(format!(
3906 "Pattern morphology {} must contain [src, dst] pairs",
3907 morphology_id
3908 )));
3909 }
3910
3911 let src_pattern = &pattern_pair[0];
3912 let dst_pattern = &pattern_pair[1];
3913
3914 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
3915 return Err(crate::types::BduError::InvalidMorphology(format!(
3916 "Pattern morphology {} requires 3-axis patterns",
3917 morphology_id
3918 )));
3919 }
3920
3921 let src: Pattern3D = (
3922 convert_element(&src_pattern[0])?,
3923 convert_element(&src_pattern[1])?,
3924 convert_element(&src_pattern[2])?,
3925 );
3926 let dst: Pattern3D = (
3927 convert_element(&dst_pattern[0])?,
3928 convert_element(&dst_pattern[1])?,
3929 convert_element(&dst_pattern[2])?,
3930 );
3931
3932 converted_patterns.push((src, dst));
3933 }
3934
3935 let count = apply_patterns_morphology(
3936 &mut npu,
3937 *src_idx,
3938 *dst_idx,
3939 converted_patterns,
3940 weight,
3941 psp,
3942 synapse_attractivity,
3943 synapse_type,
3944 delay_bursts,
3945 )?;
3946 if count > 0 {
3947 npu.rebuild_synapse_index();
3948 }
3949 Ok(count as usize)
3950 }
3951 feagi_evolutionary::MorphologyType::Composite => {
3952 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
3953 morphology.parameters
3954 else {
3955 return Ok(0);
3956 };
3957
3958 if morphology_id != "tile" {
3959 use tracing::debug;
3960 debug!(
3961 target: "feagi-bdu",
3962 "Composite morphology {} not yet implemented",
3963 morphology_id
3964 );
3965 return Ok(0);
3966 }
3967
3968 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3969 crate::types::BduError::InvalidArea(format!(
3970 "Source area not found: {}",
3971 src_area_id
3972 ))
3973 })?;
3974 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3975 crate::types::BduError::InvalidArea(format!(
3976 "Destination area not found: {}",
3977 dst_area_id
3978 ))
3979 })?;
3980 let src_dimensions = (
3981 src_area.dimensions.width as usize,
3982 src_area.dimensions.height as usize,
3983 src_area.dimensions.depth as usize,
3984 );
3985 let dst_dimensions = (
3986 dst_area.dimensions.width as usize,
3987 dst_area.dimensions.height as usize,
3988 dst_area.dimensions.depth as usize,
3989 );
3990
3991 let count =
3992 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
3993 &mut npu,
3994 *src_idx,
3995 *dst_idx,
3996 src_dimensions,
3997 dst_dimensions,
3998 weight,
3999 psp,
4000 synapse_attractivity,
4001 synapse_type,
4002 delay_bursts,
4003 )?;
4004 if count > 0 {
4005 npu.rebuild_synapse_index();
4006 }
4007 Ok(count as usize)
4008 }
4009 }
4010 } else {
4011 Ok(0) }
4013 }
4014
4015 pub fn set_npu(
4028 &mut self,
4029 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4030 ) {
4031 self.npu = Some(Arc::clone(&npu));
4032 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
4033
4034 #[cfg(not(feature = "wasm"))]
4037 {
4038 use feagi_state_manager::StateManager;
4039 let state_manager = StateManager::instance();
4040 let state_manager = state_manager.read();
4041 let core_state = state_manager.get_core_state();
4042 core_state.set_neuron_capacity(self.config.max_neurons as u32);
4044 core_state.set_synapse_capacity(self.config.max_synapses as u32);
4045 info!(
4046 target: "feagi-bdu",
4047 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
4048 self.config.max_neurons, self.config.max_synapses
4049 );
4050 }
4051
4052 let existing_area_count = self.cortical_id_to_idx.len();
4059 if existing_area_count > 0 {
4060 match npu.lock() {
4061 Ok(mut npu_lock) => {
4062 for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
4063 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
4064 }
4065 info!(
4066 target: "feagi-bdu",
4067 "🔁 Backfilled {} cortical area registrations into NPU",
4068 existing_area_count
4069 );
4070 }
4071 Err(e) => {
4072 warn!(
4073 target: "feagi-bdu",
4074 "⚠️ Failed to lock NPU for cortical area backfill registration: {}",
4075 e
4076 );
4077 }
4078 }
4079 }
4080
4081 self.update_all_cached_stats();
4083 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
4084 self.get_neuron_count(), self.get_synapse_count());
4085 }
4086
4087 pub fn has_npu(&self) -> bool {
4089 self.npu.is_some()
4090 }
4091
4092 pub fn get_npu(
4099 &self,
4100 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
4101 {
4102 self.npu.as_ref()
4103 }
4104
4105 #[cfg(feature = "plasticity")]
4108 pub fn set_plasticity_executor(
4109 &mut self,
4110 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
4111 ) {
4112 if let Ok(mut exec) = executor.lock() {
4113 use feagi_npu_plasticity::executor::PlasticityExecutor;
4114 if !exec.is_running() {
4115 exec.start();
4116 }
4117 } else {
4118 warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
4119 }
4120 self.plasticity_executor = Some(executor);
4121 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
4122 }
4123
4124 #[cfg(feature = "plasticity")]
4126 pub fn get_plasticity_executor(
4127 &self,
4128 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
4129 self.plasticity_executor.as_ref()
4130 }
4131
4132 pub fn get_neuron_capacity(&self) -> usize {
4144 self.config.max_neurons
4146 }
4147
4148 pub fn get_synapse_capacity(&self) -> usize {
4160 self.config.max_synapses
4162 }
4163
4164 pub fn update_fatigue_index(&self) -> Option<u8> {
4179 let mut last_calc = match self.last_fatigue_calculation.lock() {
4181 Ok(guard) => guard,
4182 Err(_) => return None, };
4184
4185 let now = std::time::Instant::now();
4186 if now.duration_since(*last_calc).as_secs() < 2 {
4187 return None; }
4189 *last_calc = now;
4190 drop(last_calc);
4191
4192 let regular_neuron_count = self.get_neuron_count();
4194 let regular_neuron_capacity = self.get_neuron_capacity();
4195 let regular_neuron_util = if regular_neuron_capacity > 0 {
4196 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
4197 } else {
4198 0
4199 };
4200
4201 let memory_neuron_util = match StateManager::instance().try_read() {
4205 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
4206 None => {
4207 return None;
4209 }
4210 };
4211
4212 let synapse_count = self.get_synapse_count();
4214 let synapse_capacity = self.get_synapse_capacity();
4215 let synapse_util = if synapse_capacity > 0 {
4216 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
4217 } else {
4218 0
4219 };
4220
4221 let fatigue_index = regular_neuron_util
4223 .max(memory_neuron_util)
4224 .max(synapse_util);
4225
4226 let current_fatigue_active = {
4228 StateManager::instance()
4230 .try_read()
4231 .map(|m| m.get_core_state().is_fatigue_active())
4232 .unwrap_or(false)
4233 };
4234
4235 let new_fatigue_active = if fatigue_index >= 85 {
4236 true
4237 } else if fatigue_index < 80 {
4238 false
4239 } else {
4240 current_fatigue_active };
4242
4243 if let Some(state_manager) = StateManager::instance().try_write() {
4247 let core_state = state_manager.get_core_state();
4248 core_state.set_fatigue_index(fatigue_index);
4249 core_state.set_fatigue_active(new_fatigue_active);
4250 core_state.set_regular_neuron_util(regular_neuron_util);
4251 core_state.set_memory_neuron_util(memory_neuron_util);
4252 core_state.set_synapse_util(synapse_util);
4253 } else {
4254 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
4256 }
4257
4258 if let Some(ref npu) = self.npu {
4260 if let Ok(mut npu_lock) = npu.lock() {
4261 npu_lock.set_fatigue_active(new_fatigue_active);
4262 }
4263 }
4264
4265 trace!(
4266 target: "feagi-bdu",
4267 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
4268 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
4269 );
4270
4271 Some(fatigue_index)
4272 }
4273
4274 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
4291 let area = self
4293 .cortical_areas
4294 .get(cortical_id)
4295 .ok_or_else(|| {
4296 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
4297 })?
4298 .clone();
4299
4300 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
4302 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
4303 })?;
4304
4305 let npu = self
4307 .npu
4308 .as_ref()
4309 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4310
4311 use crate::models::CorticalAreaExt;
4314 let per_voxel_cnt = area.neurons_per_voxel();
4315 let firing_threshold = area.firing_threshold();
4316 let firing_threshold_increment_x = area.firing_threshold_increment_x();
4317 let firing_threshold_increment_y = area.firing_threshold_increment_y();
4318 let firing_threshold_increment_z = area.firing_threshold_increment_z();
4319 let firing_threshold_limit_raw = area.firing_threshold_limit();
4321 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
4322 f32::MAX } else {
4324 firing_threshold_limit_raw
4325 };
4326
4327 if firing_threshold_increment_x != 0.0
4329 || firing_threshold_increment_y != 0.0
4330 || firing_threshold_increment_z != 0.0
4331 {
4332 info!(
4333 target: "feagi-bdu",
4334 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
4335 cortical_id.as_base_64(),
4336 firing_threshold_increment_x,
4337 firing_threshold_increment_y,
4338 firing_threshold_increment_z
4339 );
4340 } else {
4341 if area.properties.contains_key("firing_threshold_increment_x")
4343 || area.properties.contains_key("firing_threshold_increment_y")
4344 || area.properties.contains_key("firing_threshold_increment_z")
4345 {
4346 info!(
4347 target: "feagi-bdu",
4348 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
4349 cortical_id.as_base_64(),
4350 area.properties.get("firing_threshold_increment_x"),
4351 area.properties.get("firing_threshold_increment_y"),
4352 area.properties.get("firing_threshold_increment_z")
4353 );
4354 }
4355 }
4356
4357 let leak_coefficient = area.leak_coefficient();
4358 let excitability = area.neuron_excitability();
4359 let refractory_period = area.refractory_period();
4360 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
4362 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
4363 u16::MAX } else {
4365 consecutive_fire_limit_raw
4366 };
4367 let snooze_length = area.snooze_period();
4368 let mp_charge_accumulation = area.mp_charge_accumulation();
4369
4370 let voxels = area.dimensions.width as usize
4372 * area.dimensions.height as usize
4373 * area.dimensions.depth as usize;
4374 let expected_neurons = voxels * per_voxel_cnt as usize;
4375
4376 trace!(
4377 target: "feagi-bdu",
4378 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
4379 cortical_id.as_base_64(),
4380 area.dimensions.width,
4381 area.dimensions.height,
4382 area.dimensions.depth,
4383 per_voxel_cnt,
4384 expected_neurons
4385 );
4386
4387 let neuron_count: u32 = {
4392 let mut npu_lock = npu
4393 .lock()
4394 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4395 npu_lock
4396 .create_cortical_area_neurons(
4397 *cortical_idx,
4398 area.dimensions.width,
4399 area.dimensions.height,
4400 area.dimensions.depth,
4401 per_voxel_cnt,
4402 firing_threshold,
4403 firing_threshold_increment_x,
4404 firing_threshold_increment_y,
4405 firing_threshold_increment_z,
4406 firing_threshold_limit,
4407 leak_coefficient,
4408 0.0, 0, refractory_period,
4411 excitability,
4412 consecutive_fire_limit,
4413 snooze_length,
4414 mp_charge_accumulation,
4415 )
4416 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
4417 };
4418
4419 trace!(
4420 target: "feagi-bdu",
4421 "Created {} neurons for area {} via NPU",
4422 neuron_count,
4423 cortical_id.as_base_64()
4424 );
4425
4426 {
4429 let mut cache = self.cached_neuron_counts_per_area.write();
4430 cache
4431 .entry(*cortical_id)
4432 .or_insert_with(|| AtomicUsize::new(0))
4433 .store(neuron_count as usize, Ordering::Relaxed);
4434 }
4435
4436 let state_manager = StateManager::instance();
4438 let state_manager = state_manager.read();
4439 state_manager
4440 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
4441
4442 self.cached_neuron_count
4444 .fetch_add(neuron_count as usize, Ordering::Relaxed);
4445
4446 let state_manager = StateManager::instance();
4448 let state_manager = state_manager.read();
4449 let core_state = state_manager.get_core_state();
4450 core_state.add_neuron_count(neuron_count);
4451 core_state.add_regular_neuron_count(neuron_count);
4452
4453 if let Some(npu) = &self.npu {
4455 if let Ok(mut npl) = npu.lock() {
4456 if let Some(v) = area.properties.get("rate_modulated_leak") {
4457 use crate::models::CorticalAreaExt;
4458 let idxs: Vec<usize> = npl
4459 .get_neurons_in_cortical_area(*cortical_idx)
4460 .into_iter()
4461 .map(|id| id as usize)
4462 .collect();
4463 npl.sync_rate_modulated_leak_from_cortical_property(
4464 *cortical_idx,
4465 v,
4466 area.leak_coefficient(),
4467 idxs,
4468 );
4469 } else {
4470 npl.remove_rate_modulated_leak(*cortical_idx);
4471 }
4472 }
4473 }
4474
4475 Ok(neuron_count)
4483 }
4484
4485 #[allow(clippy::too_many_arguments)]
4509 pub fn add_neuron(
4510 &mut self,
4511 cortical_id: &CorticalID,
4512 x: u32,
4513 y: u32,
4514 z: u32,
4515 firing_threshold: f32,
4516 firing_threshold_limit: f32,
4517 leak_coefficient: f32,
4518 resting_potential: f32,
4519 neuron_type: u8,
4520 refractory_period: u16,
4521 excitability: f32,
4522 consecutive_fire_limit: u16,
4523 snooze_length: u16,
4524 mp_charge_accumulation: bool,
4525 ) -> BduResult<u64> {
4526 self.add_neuron_with_area_stats(
4527 cortical_id,
4528 x,
4529 y,
4530 z,
4531 firing_threshold,
4532 firing_threshold_limit,
4533 leak_coefficient,
4534 resting_potential,
4535 neuron_type,
4536 refractory_period,
4537 excitability,
4538 consecutive_fire_limit,
4539 snooze_length,
4540 mp_charge_accumulation,
4541 true,
4542 )
4543 }
4544
4545 #[allow(clippy::too_many_arguments)]
4551 pub fn add_auxiliary_neuron(
4552 &mut self,
4553 cortical_id: &CorticalID,
4554 x: u32,
4555 y: u32,
4556 z: u32,
4557 firing_threshold: f32,
4558 firing_threshold_limit: f32,
4559 leak_coefficient: f32,
4560 resting_potential: f32,
4561 neuron_type: u8,
4562 refractory_period: u16,
4563 excitability: f32,
4564 consecutive_fire_limit: u16,
4565 snooze_length: u16,
4566 mp_charge_accumulation: bool,
4567 ) -> BduResult<u64> {
4568 self.add_neuron_with_area_stats(
4569 cortical_id,
4570 x,
4571 y,
4572 z,
4573 firing_threshold,
4574 firing_threshold_limit,
4575 leak_coefficient,
4576 resting_potential,
4577 neuron_type,
4578 refractory_period,
4579 excitability,
4580 consecutive_fire_limit,
4581 snooze_length,
4582 mp_charge_accumulation,
4583 false,
4584 )
4585 }
4586
4587 #[allow(clippy::too_many_arguments)]
4588 fn add_neuron_with_area_stats(
4589 &mut self,
4590 cortical_id: &CorticalID,
4591 x: u32,
4592 y: u32,
4593 z: u32,
4594 firing_threshold: f32,
4595 firing_threshold_limit: f32,
4596 leak_coefficient: f32,
4597 resting_potential: f32,
4598 neuron_type: u8,
4599 refractory_period: u16,
4600 excitability: f32,
4601 consecutive_fire_limit: u16,
4602 snooze_length: u16,
4603 mp_charge_accumulation: bool,
4604 update_area_stats: bool,
4605 ) -> BduResult<u64> {
4606 if !self.cortical_areas.contains_key(cortical_id) {
4608 return Err(BduError::InvalidArea(format!(
4609 "Cortical area {} not found",
4610 cortical_id
4611 )));
4612 }
4613
4614 let cortical_idx = *self
4615 .cortical_id_to_idx
4616 .get(cortical_id)
4617 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
4618
4619 let npu = self
4621 .npu
4622 .as_ref()
4623 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4624
4625 let mut npu_lock = npu
4626 .lock()
4627 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4628
4629 let neuron_id = npu_lock
4631 .add_neuron(
4632 firing_threshold,
4633 firing_threshold_limit,
4634 leak_coefficient,
4635 resting_potential,
4636 neuron_type as i32,
4637 refractory_period,
4638 excitability,
4639 consecutive_fire_limit,
4640 snooze_length,
4641 mp_charge_accumulation,
4642 cortical_idx,
4643 x,
4644 y,
4645 z,
4646 )
4647 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
4648
4649 trace!(
4650 target: "feagi-bdu",
4651 "Created neuron {} in area {} at ({}, {}, {})",
4652 neuron_id.0,
4653 cortical_id,
4654 x,
4655 y,
4656 z
4657 );
4658
4659 let state_manager = StateManager::instance();
4661 let state_manager = state_manager.read();
4662 let core_state = state_manager.get_core_state();
4663 core_state.add_neuron_count(1);
4664 core_state.add_regular_neuron_count(1);
4665 if update_area_stats {
4666 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4667 }
4668
4669 Ok(neuron_id.0 as u64)
4670 }
4671
4672 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
4683 let npu = self
4685 .npu
4686 .as_ref()
4687 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
4688
4689 let mut npu_lock = npu
4690 .lock()
4691 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
4692
4693 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
4694 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
4695
4696 let deleted = npu_lock.delete_neuron(neuron_id as u32);
4697
4698 if deleted {
4699 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
4700
4701 let state_manager = StateManager::instance();
4703 let state_manager = state_manager.read();
4704 let core_state = state_manager.get_core_state();
4705 core_state.subtract_neuron_count(1);
4706 core_state.subtract_regular_neuron_count(1);
4707 if let Some(cortical_id) = cortical_id {
4708 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
4709 }
4710
4711 }
4715
4716 Ok(deleted)
4717 }
4718
4719 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
4733 let src_area = self
4735 .cortical_areas
4736 .get(src_cortical_id)
4737 .ok_or_else(|| {
4738 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
4739 })?
4740 .clone();
4741
4742 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
4744 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
4745 _ => return Ok(0), };
4747
4748 let src_cortical_idx = *self
4749 .cortical_id_to_idx
4750 .get(src_cortical_id)
4751 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
4752
4753 let mut total_synapses = 0u32;
4754 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
4758 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
4760 Ok(id) => id,
4761 Err(_) => {
4762 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
4763 continue;
4764 }
4765 };
4766
4767 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
4769 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
4770 continue;
4771 }
4772
4773 let synapse_count =
4775 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
4776 total_synapses += synapse_count as u32;
4777
4778 upstream_updates.push((dst_cortical_id, src_cortical_idx));
4781 }
4782
4783 for (dst_id, src_idx) in upstream_updates {
4785 self.add_upstream_area(&dst_id, src_idx);
4786 }
4787
4788 trace!(
4789 target: "feagi-bdu",
4790 "Created {} synapses for area {} via NPU",
4791 total_synapses,
4792 src_cortical_id
4793 );
4794
4795 if total_synapses > 0 {
4798 let mut cache = self.cached_synapse_counts_per_area.write();
4799 cache
4800 .entry(*src_cortical_id)
4801 .or_insert_with(|| AtomicUsize::new(0))
4802 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4803 }
4804
4805 self.cached_synapse_count
4807 .fetch_add(total_synapses as usize, Ordering::Relaxed);
4808
4809 if total_synapses > 0 {
4811 let state_manager = StateManager::instance();
4812 let state_manager = state_manager.read();
4813 let core_state = state_manager.get_core_state();
4814 core_state.add_synapse_count(total_synapses);
4815 }
4816
4817 Ok(total_synapses)
4818 }
4819
4820 pub fn has_neuron(&self, neuron_id: u64) -> bool {
4839 if let Some(ref npu) = self.npu {
4840 if let Ok(npu_lock) = npu.lock() {
4841 npu_lock.is_neuron_valid(neuron_id as u32)
4843 } else {
4844 false
4845 }
4846 } else {
4847 false
4848 }
4849 }
4850
4851 pub fn get_neuron_count(&self) -> usize {
4863 if let Some(ref npu) = self.npu {
4865 if let Ok(npu_lock) = npu.try_lock() {
4866 let fresh_count = npu_lock.get_neuron_count();
4867 self.cached_neuron_count
4868 .store(fresh_count, Ordering::Relaxed);
4869 }
4870 }
4872
4873 self.cached_neuron_count.load(Ordering::Relaxed)
4875 }
4876
4877 pub fn update_cached_neuron_count(&self) {
4883 if let Some(ref npu) = self.npu {
4884 if let Ok(npu_lock) = npu.try_lock() {
4885 let count = npu_lock.get_neuron_count();
4886 self.cached_neuron_count.store(count, Ordering::Relaxed);
4887 }
4888 }
4889 }
4890
4891 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
4895 let npu = self.npu.as_ref()?;
4896 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
4897 let npu_lock = npu.lock().ok()?;
4898 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
4899 drop(npu_lock);
4900
4901 let mut cache = self.cached_neuron_counts_per_area.write();
4902 cache
4903 .entry(*cortical_id)
4904 .or_insert_with(|| AtomicUsize::new(0))
4905 .store(count, Ordering::Relaxed);
4906
4907 let state_manager = StateManager::instance();
4909 let state_manager = state_manager.read();
4910 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
4911
4912 self.update_cached_neuron_count();
4913
4914 Some(count)
4915 }
4916
4917 pub fn get_synapse_count(&self) -> usize {
4929 if let Some(ref npu) = self.npu {
4931 if let Ok(npu_lock) = npu.try_lock() {
4932 let fresh_count = npu_lock.get_synapse_count();
4933 self.cached_synapse_count
4934 .store(fresh_count, Ordering::Relaxed);
4935 }
4936 }
4938
4939 self.cached_synapse_count.load(Ordering::Relaxed)
4941 }
4942
4943 pub fn update_cached_synapse_count(&self) {
4949 if let Some(ref npu) = self.npu {
4950 if let Ok(npu_lock) = npu.try_lock() {
4951 let count = npu_lock.get_synapse_count();
4952 self.cached_synapse_count.store(count, Ordering::Relaxed);
4953 }
4954 }
4955 }
4956
4957 pub fn update_all_cached_stats(&self) {
4963 self.update_cached_neuron_count();
4964 self.update_cached_synapse_count();
4965 }
4966
4967 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
4978 #[cfg(feature = "plasticity")]
4983 {
4984 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
4985 return (0, 0, 0);
4986 }
4987 }
4988 if let Some(ref npu) = self.npu {
4989 if let Ok(npu_lock) = npu.lock() {
4990 npu_lock
4991 .get_neuron_coordinates(neuron_id as u32)
4992 .unwrap_or((0, 0, 0))
4993 } else {
4994 (0, 0, 0)
4995 }
4996 } else {
4997 (0, 0, 0)
4998 }
4999 }
5000
5001 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
5012 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
5013 }
5014
5015 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
5021 #[cfg(feature = "plasticity")]
5022 {
5023 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
5024 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
5025 }
5026 }
5027 if let Some(ref npu) = self.npu {
5028 if let Ok(npu_lock) = npu.lock() {
5029 npu_lock.get_neuron_cortical_area(neuron_id as u32)
5030 } else {
5031 None
5032 }
5033 } else {
5034 None
5035 }
5036 }
5037
5038 #[cfg(feature = "plasticity")]
5040 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
5041 let exec = self.get_plasticity_executor()?;
5042 let guard = exec.lock().ok()?;
5043 guard
5044 .memory_neuron_detail(neuron_id)
5045 .map(|d| d.cortical_area_idx)
5046 }
5047
5048 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
5059 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
5061 Some(idx) => *idx,
5062 None => return Vec::new(),
5063 };
5064
5065 if let Some(ref npu) = self.npu {
5066 if let Ok(npu_lock) = npu.lock() {
5067 npu_lock
5069 .get_neurons_in_cortical_area(cortical_idx)
5070 .into_iter()
5071 .map(|id| id as u64)
5072 .collect()
5073 } else {
5074 Vec::new()
5075 }
5076 } else {
5077 Vec::new()
5078 }
5079 }
5080
5081 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
5092 if let Some(ref npu) = self.npu {
5093 if let Ok(npu_lock) = npu.lock() {
5094 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
5095 } else {
5096 Vec::new()
5097 }
5098 } else {
5099 Vec::new()
5100 }
5101 }
5102
5103 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
5114 if let Some(ref npu) = self.npu {
5115 if let Ok(npu_lock) = npu.lock() {
5116 npu_lock.get_incoming_synapses(target_neuron_id as u32)
5117 } else {
5118 Vec::new()
5119 }
5120 } else {
5121 Vec::new()
5122 }
5123 }
5124
5125 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5151 let is_memory_area = self
5152 .cortical_areas
5153 .get(cortical_id)
5154 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
5155 .is_some();
5156
5157 if is_memory_area {
5158 #[cfg(feature = "plasticity")]
5162 if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
5163 return count;
5164 }
5165
5166 return StateManager::instance()
5168 .try_read()
5169 .and_then(|state_manager| {
5170 state_manager
5171 .get_cortical_area_stats(&cortical_id.as_base_64())
5172 .map(|stats| stats.neuron_count)
5173 })
5174 .unwrap_or(0);
5175 }
5176
5177 let cache = self.cached_neuron_counts_per_area.read();
5179 cache
5180 .get(cortical_id)
5181 .map(|count| count.load(Ordering::Relaxed))
5182 .unwrap_or(0)
5183 }
5184
5185 #[cfg(feature = "plasticity")]
5187 fn active_memory_neuron_count_from_plasticity(
5188 &self,
5189 cortical_id: &CorticalID,
5190 ) -> Option<usize> {
5191 let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
5192 let exec = self.get_plasticity_executor()?;
5193 let guard = exec.lock().ok()?;
5194 let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
5195 Some(runtime.active_memory_neuron_count())
5196 }
5197
5198 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
5205 let mut result = Vec::new();
5206
5207 for cortical_id in self.cortical_areas.keys() {
5208 let count = self.get_neuron_count_in_area(cortical_id);
5209 if count > 0 {
5210 result.push((cortical_id.to_string(), count));
5211 }
5212 }
5213
5214 result
5215 }
5216
5217 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
5228 self.get_neuron_count_in_area(cortical_id) > 0
5229 }
5230
5231 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5247 let cache = self.cached_synapse_counts_per_area.read();
5249 cache
5250 .get(cortical_id)
5251 .map(|count| count.load(Ordering::Relaxed))
5252 .unwrap_or(0)
5253 }
5254
5255 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5265 if !self.cortical_id_to_idx.contains_key(cortical_id) {
5266 return 0;
5267 }
5268
5269 if let Some(state_manager) = StateManager::instance().try_read() {
5270 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
5271 return stats.incoming_synapse_count;
5272 }
5273 }
5274
5275 0
5276 }
5277
5278 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
5288 if !self.cortical_id_to_idx.contains_key(cortical_id) {
5289 return 0;
5290 }
5291
5292 if let Some(state_manager) = StateManager::instance().try_read() {
5293 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
5294 return stats.outgoing_synapse_count;
5295 }
5296 }
5297
5298 0
5299 }
5300
5301 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
5313 let synapses = self.get_outgoing_synapses(source_neuron_id);
5314 synapses
5315 .iter()
5316 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
5317 }
5318
5319 pub fn get_connection_weight(
5331 &self,
5332 source_neuron_id: u64,
5333 target_neuron_id: u64,
5334 ) -> Option<f32> {
5335 let synapses = self.get_outgoing_synapses(source_neuron_id);
5336 synapses
5337 .iter()
5338 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
5339 .map(|(_, weight, _, _)| *weight)
5340 }
5341
5342 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
5353 let neurons = self.get_neurons_in_area(cortical_id);
5354 let neuron_count = neurons.len();
5355
5356 if neuron_count == 0 {
5357 return (0, 0, 0.0);
5358 }
5359
5360 let mut total_synapses = 0;
5361 for neuron_id in neurons {
5362 total_synapses += self.get_outgoing_synapses(neuron_id).len();
5363 }
5364
5365 let avg_synapses = total_synapses as f32 / neuron_count as f32;
5366
5367 (neuron_count, total_synapses, avg_synapses)
5368 }
5369
5370 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
5381 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
5382 self.cortical_idx_to_id.get(&cortical_idx).copied()
5383 }
5384
5385 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
5396 let area = match self.cortical_areas.get(cortical_id) {
5397 Some(a) => a,
5398 None => return 0.0,
5399 };
5400
5401 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5402 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5403
5404 if volume == 0 {
5405 return 0.0;
5406 }
5407
5408 neuron_count as f32 / volume as f32
5409 }
5410
5411 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
5418 let mut stats = Vec::new();
5419
5420 for cortical_id in self.cortical_areas.keys() {
5421 let neuron_count = self.get_neuron_count_in_area(cortical_id);
5422 let synapse_count = self.get_synapse_count_in_area(cortical_id);
5423 let density = self.get_neuron_density(cortical_id);
5424
5425 stats.push((
5426 cortical_id.to_string(),
5427 neuron_count,
5428 synapse_count,
5429 density,
5430 ));
5431 }
5432
5433 stats
5434 }
5435
5436 pub fn get_config(&self) -> &ConnectomeConfig {
5442 &self.config
5443 }
5444
5445 pub fn set_config(&mut self, config: ConnectomeConfig) {
5447 self.config = config;
5448 }
5449
5450 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
5473 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
5474
5475 use feagi_structures::genomic::cortical_area::{
5476 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
5477 };
5478
5479 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
5481 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
5482 })?;
5483
5484 let core_position = (0, 0, 0).into();
5486
5487 let death_id = CoreCorticalType::Death.to_cortical_id();
5489 if !self.cortical_areas.contains_key(&death_id) {
5490 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
5491 let death_area = CorticalArea::new(
5492 death_id,
5493 0, "_death".to_string(),
5495 core_dimensions,
5496 core_position,
5497 CorticalAreaType::Core(CoreCorticalType::Death),
5498 )
5499 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
5500 match self.add_cortical_area(death_area) {
5501 Ok(idx) => {
5502 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
5503 }
5504 Err(e) => {
5505 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
5506 return Err(e);
5507 }
5508 }
5509 } else {
5510 info!(target: "feagi-bdu", " ✓ _death area already exists");
5511 }
5512
5513 let power_id = CoreCorticalType::Power.to_cortical_id();
5515 if !self.cortical_areas.contains_key(&power_id) {
5516 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
5517 let power_area = CorticalArea::new(
5518 power_id,
5519 1, "_power".to_string(),
5521 core_dimensions,
5522 core_position,
5523 CorticalAreaType::Core(CoreCorticalType::Power),
5524 )
5525 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
5526 match self.add_cortical_area(power_area) {
5527 Ok(idx) => {
5528 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
5529 }
5530 Err(e) => {
5531 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
5532 return Err(e);
5533 }
5534 }
5535 } else {
5536 info!(target: "feagi-bdu", " ✓ _power area already exists");
5537 }
5538
5539 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
5541 let pain_id = CoreCorticalType::Pain.to_cortical_id();
5542 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
5543 let fear_id = CoreCorticalType::Fear.to_cortical_id();
5544 let hope_id = CoreCorticalType::Hope.to_cortical_id();
5545 if !self.cortical_areas.contains_key(&fatigue_id) {
5546 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
5547 let fatigue_area = CorticalArea::new(
5548 fatigue_id,
5549 2, "_fatigue".to_string(),
5551 core_dimensions,
5552 core_position,
5553 CorticalAreaType::Core(CoreCorticalType::Fatigue),
5554 )
5555 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
5556 match self.add_cortical_area(fatigue_area) {
5557 Ok(idx) => {
5558 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
5559 }
5560 Err(e) => {
5561 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
5562 return Err(e);
5563 }
5564 }
5565 } else {
5566 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
5567 }
5568
5569 if !self.cortical_areas.contains_key(&pain_id) {
5571 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
5572 let pain_area = CorticalArea::new(
5573 pain_id,
5574 3, "_pain".to_string(),
5576 core_dimensions,
5577 core_position,
5578 CorticalAreaType::Core(CoreCorticalType::Pain),
5579 )
5580 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
5581 match self.add_cortical_area(pain_area) {
5582 Ok(idx) => {
5583 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
5584 }
5585 Err(e) => {
5586 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
5587 return Err(e);
5588 }
5589 }
5590 } else {
5591 info!(target: "feagi-bdu", " ✓ _pain area already exists");
5592 }
5593
5594 if !self.cortical_areas.contains_key(&pleasure_id) {
5596 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
5597 let pleasure_area = CorticalArea::new(
5598 pleasure_id,
5599 4, "_pleasure".to_string(),
5601 core_dimensions,
5602 core_position,
5603 CorticalAreaType::Core(CoreCorticalType::Pleasure),
5604 )
5605 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
5606 match self.add_cortical_area(pleasure_area) {
5607 Ok(idx) => {
5608 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
5609 }
5610 Err(e) => {
5611 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
5612 return Err(e);
5613 }
5614 }
5615 } else {
5616 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
5617 }
5618
5619 if !self.cortical_areas.contains_key(&fear_id) {
5621 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
5622 let fear_area = CorticalArea::new(
5623 fear_id,
5624 5, "_fear".to_string(),
5626 core_dimensions,
5627 core_position,
5628 CorticalAreaType::Core(CoreCorticalType::Fear),
5629 )
5630 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
5631 match self.add_cortical_area(fear_area) {
5632 Ok(idx) => {
5633 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
5634 }
5635 Err(e) => {
5636 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
5637 return Err(e);
5638 }
5639 }
5640 } else {
5641 info!(target: "feagi-bdu", " ✓ _fear area already exists");
5642 }
5643
5644 if !self.cortical_areas.contains_key(&hope_id) {
5646 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
5647 let hope_area = CorticalArea::new(
5648 hope_id,
5649 6, "_hope".to_string(),
5651 core_dimensions,
5652 core_position,
5653 CorticalAreaType::Core(CoreCorticalType::Hope),
5654 )
5655 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
5656 match self.add_cortical_area(hope_area) {
5657 Ok(idx) => {
5658 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
5659 }
5660 Err(e) => {
5661 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
5662 return Err(e);
5663 }
5664 }
5665 } else {
5666 info!(target: "feagi-bdu", " ✓ _hope area already exists");
5667 }
5668
5669 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
5670 Ok(())
5671 }
5672
5673 #[deprecated(
5690 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
5691 )]
5692 #[allow(deprecated)]
5693 pub fn save_genome_to_json(
5694 &self,
5695 genome_id: Option<String>,
5696 genome_title: Option<String>,
5697 ) -> BduResult<String> {
5698 let mut brain_regions_with_parents = std::collections::HashMap::new();
5700
5701 for region_id in self.brain_regions.get_all_region_ids() {
5702 if let Some(region) = self.brain_regions.get_region(region_id) {
5703 let parent_id = self
5704 .brain_regions
5705 .get_parent(region_id)
5706 .map(|s| s.to_string());
5707 brain_regions_with_parents
5708 .insert(region_id.to_string(), (region.clone(), parent_id));
5709 }
5710 }
5711
5712 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
5714 &self.cortical_areas,
5715 &brain_regions_with_parents,
5716 genome_id,
5717 genome_title,
5718 )?)
5719 }
5720
5721 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
5752 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
5753
5754 self.cortical_areas.clear();
5756 self.cortical_id_to_idx.clear();
5757 self.cortical_idx_to_id.clear();
5758 self.next_cortical_idx = 7;
5760 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)");
5761
5762 self.brain_regions = BrainRegionHierarchy::new();
5764
5765 if let Some(ref npu) = self.npu {
5767 let mut npu_lock = npu
5768 .lock()
5769 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5770 npu_lock
5771 .reset_for_new_genome()
5772 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
5773 }
5774
5775 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
5776 Ok(())
5777 }
5778
5779 pub fn resize_for_genome(
5789 &mut self,
5790 genome: &feagi_evolutionary::RuntimeGenome,
5791 ) -> BduResult<()> {
5792 self.morphology_registry = genome.morphologies.clone();
5794 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
5795
5796 let required_neurons = genome.stats.innate_neuron_count;
5798 let required_synapses = genome.stats.innate_synapse_count;
5799
5800 info!(target: "feagi-bdu",
5801 "Genome requires: {} neurons, {} synapses",
5802 required_neurons,
5803 required_synapses
5804 );
5805
5806 let mut total_voxels = 0;
5808 for area in genome.cortical_areas.values() {
5809 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
5810 }
5811
5812 info!(target: "feagi-bdu",
5813 "Genome has {} cortical areas with {} total voxels",
5814 genome.cortical_areas.len(),
5815 total_voxels
5816 );
5817
5818 Ok(())
5823 }
5824
5825 pub fn create_synapse(
5844 &mut self,
5845 source_neuron_id: u64,
5846 target_neuron_id: u64,
5847 weight: f32,
5848 psp: f32,
5849 synapse_type: u8,
5850 ) -> BduResult<()> {
5851 let npu = self
5853 .npu
5854 .as_ref()
5855 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5856
5857 let mut npu_lock = npu
5858 .lock()
5859 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
5860
5861 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5863 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
5864
5865 if !source_exists {
5866 return Err(BduError::InvalidNeuron(format!(
5867 "Source neuron {} not found",
5868 source_neuron_id
5869 )));
5870 }
5871 if !target_exists {
5872 return Err(BduError::InvalidNeuron(format!(
5873 "Target neuron {} not found",
5874 target_neuron_id
5875 )));
5876 }
5877
5878 let syn_type = if synapse_type == 0 {
5880 feagi_npu_neural::synapse::SynapseType::Excitatory
5881 } else {
5882 feagi_npu_neural::synapse::SynapseType::Inhibitory
5883 };
5884
5885 let synapse_idx = npu_lock
5886 .add_synapse(
5887 NeuronId(source_neuron_id as u32),
5888 NeuronId(target_neuron_id as u32),
5889 feagi_npu_neural::types::SynapticWeight(weight),
5890 feagi_npu_neural::types::SynapticPsp(psp),
5891 syn_type,
5892 0,
5893 1,
5894 )
5895 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
5896
5897 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
5898 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
5899
5900 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
5901 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
5902 let source_cortical_id =
5903 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5904 let target_cortical_id =
5905 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
5906
5907 let state_manager = StateManager::instance();
5908 let state_manager = state_manager.read();
5909 let core_state = state_manager.get_core_state();
5910 core_state.add_synapse_count(1);
5911 if let Some(cortical_id) = source_cortical_id {
5912 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
5913 }
5914 if let Some(cortical_id) = target_cortical_id {
5915 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
5916 }
5917
5918 Ok(())
5923 }
5924
5925 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
5928 if let Some(ref npu) = self.npu {
5929 if let Ok(mut npu_lock) = npu.lock() {
5930 let mut psp_uniform_flags = ahash::AHashMap::new();
5932 let mut mp_driven_psp_flags = ahash::AHashMap::new();
5933 let mut postsynaptic_current_flags = ahash::AHashMap::new();
5934 let mut degeneration_flags = ahash::AHashMap::new();
5935
5936 for (cortical_id, area) in &self.cortical_areas {
5937 let default_psp_uniform = *cortical_id
5940 == CoreCorticalType::Power.to_cortical_id()
5941 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
5942 let psp_uniform = area
5943 .get_property("psp_uniform_distribution")
5944 .and_then(|v| v.as_bool())
5945 .unwrap_or(default_psp_uniform);
5946 psp_uniform_flags.insert(*cortical_id, psp_uniform);
5947
5948 let mp_driven_psp = area
5950 .get_property("mp_driven_psp")
5951 .and_then(|v| v.as_bool())
5952 .unwrap_or(false);
5953 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
5954
5955 let postsynaptic_current = area
5957 .get_property("postsynaptic_current")
5958 .and_then(|v| v.as_f64())
5959 .unwrap_or(1.0) as f32;
5960 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
5961
5962 let degeneration = area
5964 .get_property("degeneration")
5965 .and_then(|v| v.as_f64())
5966 .unwrap_or(0.0) as f32;
5967 if degeneration > 0.0 {
5968 degeneration_flags.insert(*cortical_id, degeneration);
5969 }
5970 }
5971
5972 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
5974 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
5975 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
5976 npu_lock.set_degeneration_flags(degeneration_flags);
5977
5978 trace!(
5979 target: "feagi-bdu",
5980 "Synchronized cortical area flags to NPU ({} areas)",
5981 self.cortical_areas.len()
5982 );
5983 }
5984 }
5985
5986 Ok(())
5987 }
5988
5989 pub fn get_synapse(
6001 &self,
6002 source_neuron_id: u64,
6003 target_neuron_id: u64,
6004 ) -> Option<(f32, f32, u8)> {
6005 let npu = self.npu.as_ref()?;
6007 let npu_lock = npu.lock().ok()?;
6008
6009 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
6012
6013 for (source_id, weight, psp, synapse_type) in incoming {
6015 if source_id == source_neuron_id as u32 {
6016 return Some((weight, psp, synapse_type));
6017 }
6018 }
6019
6020 None
6021 }
6022
6023 pub fn update_synapse_weight(
6036 &mut self,
6037 source_neuron_id: u64,
6038 target_neuron_id: u64,
6039 new_weight: f32,
6040 ) -> BduResult<()> {
6041 let npu = self
6043 .npu
6044 .as_ref()
6045 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6046
6047 let mut npu_lock = npu
6048 .lock()
6049 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6050
6051 let updated = npu_lock.update_synapse_weight(
6053 NeuronId(source_neuron_id as u32),
6054 NeuronId(target_neuron_id as u32),
6055 feagi_npu_neural::types::SynapticWeight(new_weight),
6056 );
6057
6058 if updated {
6059 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
6060 Ok(())
6061 } else {
6062 Err(BduError::InvalidSynapse(format!(
6063 "Synapse {} -> {} not found",
6064 source_neuron_id, target_neuron_id
6065 )))
6066 }
6067 }
6068
6069 pub fn remove_synapse(
6081 &mut self,
6082 source_neuron_id: u64,
6083 target_neuron_id: u64,
6084 ) -> BduResult<bool> {
6085 let npu = self
6087 .npu
6088 .as_ref()
6089 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6090
6091 let mut npu_lock = npu
6092 .lock()
6093 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6094
6095 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
6096 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
6097 let source_cortical_id =
6098 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6099 let target_cortical_id =
6100 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6101
6102 let removed = npu_lock.remove_synapse(
6104 NeuronId(source_neuron_id as u32),
6105 NeuronId(target_neuron_id as u32),
6106 );
6107
6108 if removed {
6109 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
6110
6111 let state_manager = StateManager::instance();
6113 let state_manager = state_manager.read();
6114 let core_state = state_manager.get_core_state();
6115 core_state.subtract_synapse_count(1);
6116 if let Some(cortical_id) = source_cortical_id {
6117 state_manager
6118 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
6119 }
6120 if let Some(cortical_id) = target_cortical_id {
6121 state_manager
6122 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
6123 }
6124 }
6125
6126 Ok(removed)
6127 }
6128
6129 pub fn batch_create_neurons(
6147 &mut self,
6148 cortical_id: &CorticalID,
6149 neurons: Vec<NeuronData>,
6150 ) -> BduResult<Vec<u64>> {
6151 let npu = self
6153 .npu
6154 .as_ref()
6155 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6156
6157 let mut npu_lock = npu
6158 .lock()
6159 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6160
6161 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
6163 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6164 })?;
6165 let cortical_idx = area.cortical_idx;
6166
6167 let count = neurons.len();
6168
6169 let mut x_coords = Vec::with_capacity(count);
6171 let mut y_coords = Vec::with_capacity(count);
6172 let mut z_coords = Vec::with_capacity(count);
6173 let mut firing_thresholds = Vec::with_capacity(count);
6174 let mut threshold_limits = Vec::with_capacity(count);
6175 let mut leak_coeffs = Vec::with_capacity(count);
6176 let mut resting_potentials = Vec::with_capacity(count);
6177 let mut neuron_types = Vec::with_capacity(count);
6178 let mut refractory_periods = Vec::with_capacity(count);
6179 let mut excitabilities = Vec::with_capacity(count);
6180 let mut consec_fire_limits = Vec::with_capacity(count);
6181 let mut snooze_lengths = Vec::with_capacity(count);
6182 let mut mp_accums = Vec::with_capacity(count);
6183 let mut cortical_areas = Vec::with_capacity(count);
6184
6185 for (
6186 x,
6187 y,
6188 z,
6189 threshold,
6190 threshold_limit,
6191 leak,
6192 resting,
6193 ntype,
6194 refract,
6195 excit,
6196 consec_limit,
6197 snooze,
6198 mp_accum,
6199 ) in neurons
6200 {
6201 x_coords.push(x);
6202 y_coords.push(y);
6203 z_coords.push(z);
6204 firing_thresholds.push(threshold);
6205 threshold_limits.push(threshold_limit);
6206 leak_coeffs.push(leak);
6207 resting_potentials.push(resting);
6208 neuron_types.push(ntype);
6209 refractory_periods.push(refract);
6210 excitabilities.push(excit);
6211 consec_fire_limits.push(consec_limit);
6212 snooze_lengths.push(snooze);
6213 mp_accums.push(mp_accum);
6214 cortical_areas.push(cortical_idx);
6215 }
6216
6217 let first_neuron_id = npu_lock.get_neuron_count() as u32;
6219
6220 let firing_thresholds_t = firing_thresholds;
6225 let threshold_limits_t = threshold_limits;
6226 let resting_potentials_t = resting_potentials;
6227 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
6228 firing_thresholds_t,
6229 threshold_limits_t,
6230 leak_coeffs,
6231 resting_potentials_t,
6232 neuron_types,
6233 refractory_periods,
6234 excitabilities,
6235 consec_fire_limits,
6236 snooze_lengths,
6237 mp_accums,
6238 cortical_areas,
6239 x_coords,
6240 y_coords,
6241 z_coords,
6242 );
6243
6244 let mut neuron_ids = Vec::with_capacity(count);
6246 for i in 0..neurons_created {
6247 neuron_ids.push((first_neuron_id + i) as u64);
6248 }
6249
6250 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
6251
6252 let state_manager = StateManager::instance();
6254 let state_manager = state_manager.read();
6255 let core_state = state_manager.get_core_state();
6256 core_state.add_neuron_count(neurons_created);
6257 core_state.add_regular_neuron_count(neurons_created);
6258 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
6259
6260 {
6262 let mut cache = self.cached_neuron_counts_per_area.write();
6263 cache
6264 .entry(*cortical_id)
6265 .or_insert_with(|| AtomicUsize::new(0))
6266 .fetch_add(count, Ordering::Relaxed);
6267 }
6268
6269 Ok(neuron_ids)
6270 }
6271
6272 pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
6283 let npu = self
6285 .npu
6286 .as_ref()
6287 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6288
6289 let mut npu_lock = npu
6290 .lock()
6291 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6292
6293 let mut deleted_count = 0;
6294 let mut per_area_deleted: std::collections::HashMap<String, usize> =
6295 std::collections::HashMap::new();
6296
6297 for neuron_id in neuron_ids {
6300 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
6301 let cortical_id =
6302 cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6303
6304 if npu_lock.delete_neuron(neuron_id as u32) {
6305 deleted_count += 1;
6306 if let Some(cortical_id) = cortical_id {
6307 let key = cortical_id.as_base_64();
6308 *per_area_deleted.entry(key).or_insert(0) += 1;
6309 }
6310 }
6311 }
6312
6313 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
6314
6315 if deleted_count > 0 {
6317 let state_manager = StateManager::instance();
6318 let state_manager = state_manager.read();
6319 let core_state = state_manager.get_core_state();
6320 core_state.subtract_neuron_count(deleted_count as u32);
6321 core_state.subtract_regular_neuron_count(deleted_count as u32);
6322 for (cortical_id, count) in per_area_deleted {
6323 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
6324 }
6325 }
6326
6327 Ok(deleted_count)
6334 }
6335
6336 pub fn update_neuron_properties(
6355 &mut self,
6356 neuron_id: u64,
6357 firing_threshold: Option<f32>,
6358 leak_coefficient: Option<f32>,
6359 resting_potential: Option<f32>,
6360 excitability: Option<f32>,
6361 ) -> BduResult<()> {
6362 let npu = self
6364 .npu
6365 .as_ref()
6366 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6367
6368 let mut npu_lock = npu
6369 .lock()
6370 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6371
6372 let neuron_id_u32 = neuron_id as u32;
6373
6374 let mut updated = false;
6376
6377 if let Some(threshold) = firing_threshold {
6379 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
6380 updated = true;
6381 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
6382 } else if !updated {
6383 return Err(BduError::InvalidNeuron(format!(
6384 "Neuron {} not found",
6385 neuron_id
6386 )));
6387 }
6388 }
6389
6390 if let Some(leak) = leak_coefficient {
6391 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
6392 updated = true;
6393 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
6394 } else if !updated {
6395 return Err(BduError::InvalidNeuron(format!(
6396 "Neuron {} not found",
6397 neuron_id
6398 )));
6399 }
6400 }
6401
6402 if let Some(resting) = resting_potential {
6403 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
6404 updated = true;
6405 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
6406 } else if !updated {
6407 return Err(BduError::InvalidNeuron(format!(
6408 "Neuron {} not found",
6409 neuron_id
6410 )));
6411 }
6412 }
6413
6414 if let Some(excit) = excitability {
6415 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
6416 updated = true;
6417 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
6418 } else if !updated {
6419 return Err(BduError::InvalidNeuron(format!(
6420 "Neuron {} not found",
6421 neuron_id
6422 )));
6423 }
6424 }
6425
6426 if !updated {
6427 return Err(BduError::Internal(
6428 "No properties provided for update".to_string(),
6429 ));
6430 }
6431
6432 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
6433
6434 Ok(())
6435 }
6436
6437 pub fn set_neuron_firing_threshold(
6449 &mut self,
6450 neuron_id: u64,
6451 new_threshold: f32,
6452 ) -> BduResult<()> {
6453 let npu = self
6455 .npu
6456 .as_ref()
6457 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6458
6459 let mut npu_lock = npu
6460 .lock()
6461 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6462
6463 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
6465 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
6466 Ok(())
6467 } else {
6468 Err(BduError::InvalidNeuron(format!(
6469 "Neuron {} not found",
6470 neuron_id
6471 )))
6472 }
6473 }
6474
6475 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
6490 self.cortical_areas
6491 .values()
6492 .find(|area| area.name == name)
6493 .cloned()
6494 }
6495
6496 pub fn resize_cortical_area(
6513 &mut self,
6514 cortical_id: &CorticalID,
6515 new_dimensions: CorticalAreaDimensions,
6516 ) -> BduResult<()> {
6517 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
6519 return Err(BduError::InvalidArea(format!(
6520 "Invalid dimensions: {:?} (all must be > 0)",
6521 new_dimensions
6522 )));
6523 }
6524
6525 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
6527 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6528 })?;
6529
6530 let old_dimensions = area.dimensions;
6531 area.dimensions = new_dimensions;
6532
6533 info!(target: "feagi-bdu",
6537 "Resized cortical area {} from {:?} to {:?}",
6538 cortical_id,
6539 old_dimensions,
6540 new_dimensions
6541 );
6542
6543 self.refresh_cortical_area_hashes(false, true);
6544
6545 Ok(())
6546 }
6547
6548 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
6559 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
6560 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6561 })?;
6562
6563 Ok(region
6565 .cortical_areas
6566 .iter()
6567 .map(|id| id.as_base_64())
6568 .collect())
6569 }
6570
6571 pub fn update_brain_region(
6584 &mut self,
6585 region_id: &str,
6586 new_name: Option<String>,
6587 new_description: Option<String>,
6588 ) -> BduResult<()> {
6589 let region = self
6590 .brain_regions
6591 .get_region_mut(region_id)
6592 .ok_or_else(|| {
6593 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6594 })?;
6595
6596 if let Some(name) = new_name {
6597 region.name = name;
6598 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
6599 }
6600
6601 if let Some(desc) = new_description {
6602 region
6604 .properties
6605 .insert("description".to_string(), serde_json::json!(desc));
6606 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
6607 }
6608
6609 info!(target: "feagi-bdu","Updated brain region {}", region_id);
6610
6611 self.refresh_brain_regions_hash();
6612
6613 Ok(())
6614 }
6615
6616 pub fn update_brain_region_properties(
6630 &mut self,
6631 region_id: &str,
6632 properties: std::collections::HashMap<String, serde_json::Value>,
6633 ) -> BduResult<Option<BrainRegionIoRegistry>> {
6634 use tracing::{debug, info};
6635
6636 let should_recompute_io = properties
6637 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6638 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
6639
6640 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
6641 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
6642 {
6643 let region_snapshot = self
6644 .brain_regions
6645 .get_region(region_id)
6646 .ok_or_else(|| {
6647 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6648 })?
6649 .clone();
6650 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
6651 ®ion_snapshot,
6652 &properties,
6653 )?;
6654 crate::region_io_designation::validate_merged_designations_against_connectivity(
6655 self,
6656 ®ion_snapshot,
6657 &merged_in,
6658 &merged_out,
6659 )?;
6660 }
6661
6662 let region = self
6663 .brain_regions
6664 .get_region_mut(region_id)
6665 .ok_or_else(|| {
6666 BduError::InvalidArea(format!("Brain region {} not found", region_id))
6667 })?;
6668
6669 for (key, value) in properties {
6670 match key.as_str() {
6671 "title" | "name" | "region_title" => {
6673 if let Some(name) = value.as_str() {
6674 region.name = name.to_string();
6675 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
6676 }
6677 }
6678 "coordinate_3d" | "coordinates_3d" => {
6679 region
6680 .properties
6681 .insert("coordinate_3d".to_string(), value.clone());
6682 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
6683 }
6684 "coordinate_2d" | "coordinates_2d" => {
6685 region
6686 .properties
6687 .insert("coordinate_2d".to_string(), value.clone());
6688 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
6689 }
6690 "description" => {
6691 region
6692 .properties
6693 .insert("description".to_string(), value.clone());
6694 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
6695 }
6696 "region_type" => {
6697 if let Some(type_str) = value.as_str() {
6698 region.region_type = feagi_structures::genomic::RegionType::Undefined;
6701 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
6702 }
6703 }
6704 _ => {
6706 region.properties.insert(key.clone(), value.clone());
6707 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
6708 }
6709 }
6710 }
6711
6712 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
6713
6714 if should_recompute_io {
6717 let registry = self.recompute_brain_region_io_registry()?;
6718 return Ok(Some(registry));
6719 }
6720
6721 self.refresh_brain_regions_hash();
6725
6726 Ok(None)
6727 }
6728
6729 pub fn get_neuron_by_coordinates(
6747 &self,
6748 cortical_id: &CorticalID,
6749 x: u32,
6750 y: u32,
6751 z: u32,
6752 ) -> Option<u64> {
6753 let area = self.get_cortical_area(cortical_id)?;
6755 let cortical_idx = area.cortical_idx;
6756
6757 let npu = self.npu.as_ref()?;
6759 let npu_lock = npu.lock().ok()?;
6760
6761 npu_lock
6762 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
6763 .map(|id| id as u64)
6764 }
6765
6766 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
6777 let npu = self.npu.as_ref()?;
6778 let npu_lock = npu.lock().ok()?;
6779
6780 let neuron_count = npu_lock.get_neuron_count();
6782 if (neuron_id as usize) >= neuron_count {
6783 return None;
6784 }
6785
6786 Some(
6787 npu_lock
6788 .get_neuron_coordinates(neuron_id as u32)
6789 .unwrap_or((0, 0, 0)),
6790 )
6791 }
6792
6793 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
6804 let npu = self.npu.as_ref()?;
6805 let npu_lock = npu.lock().ok()?;
6806
6807 let neuron_count = npu_lock.get_neuron_count();
6809 if (neuron_id as usize) >= neuron_count {
6810 return None;
6811 }
6812
6813 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
6814
6815 self.cortical_areas
6817 .values()
6818 .find(|area| area.cortical_idx == cortical_idx)
6819 .map(|area| area.cortical_id)
6820 }
6821
6822 pub fn get_neuron_properties(
6833 &self,
6834 neuron_id: u64,
6835 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
6836 let npu = self.npu.as_ref()?;
6837 let npu_lock = npu.lock().ok()?;
6838
6839 let neuron_id_u32 = neuron_id as u32;
6840 let idx = neuron_id as usize;
6841
6842 let neuron_count = npu_lock.get_neuron_count();
6844 if idx >= neuron_count {
6845 return None;
6846 }
6847
6848 let mut properties = std::collections::HashMap::new();
6849
6850 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
6852
6853 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
6855 properties.insert("x".to_string(), serde_json::json!(x));
6856 properties.insert("y".to_string(), serde_json::json!(y));
6857 properties.insert("z".to_string(), serde_json::json!(z));
6858
6859 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
6861 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
6862
6863 properties.insert(
6865 "mp_charge_accumulation".to_string(),
6866 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
6867 );
6868 properties.insert(
6869 "neuron_type".to_string(),
6870 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
6871 );
6872 let (mp_drv, psp_uni) = self
6873 .cortical_idx_to_id
6874 .get(&cortical_idx)
6875 .map(|cid| {
6876 (
6877 npu_lock.get_mp_driven_psp_for_cortical(cid),
6878 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
6879 )
6880 })
6881 .unwrap_or((false, false));
6882 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
6883 properties.insert(
6884 "psp_uniform_distribution".to_string(),
6885 serde_json::json!(psp_uni),
6886 );
6887
6888 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
6891 .get_neuron_state(NeuronId(neuron_id_u32))
6892 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
6893 properties.insert(
6894 "consecutive_fire_count".to_string(),
6895 serde_json::json!(consec_count),
6896 );
6897 properties.insert(
6898 "consecutive_fire_limit".to_string(),
6899 serde_json::json!(consec_limit),
6900 );
6901 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
6902 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
6903 properties.insert("threshold".to_string(), serde_json::json!(threshold));
6904 properties.insert(
6905 "refractory_countdown".to_string(),
6906 serde_json::json!(refract_countdown),
6907 );
6908
6909 properties.insert(
6911 "leak_coefficient".to_string(),
6912 serde_json::json!(npu_lock
6913 .get_neuron_property_by_index(idx, "leak_coefficient")
6914 .unwrap_or(0.0)),
6915 );
6916 properties.insert(
6917 "resting_potential".to_string(),
6918 serde_json::json!(npu_lock
6919 .get_neuron_property_by_index(idx, "resting_potential")
6920 .unwrap_or(0.0)),
6921 );
6922 properties.insert(
6923 "excitability".to_string(),
6924 serde_json::json!(npu_lock
6925 .get_neuron_property_by_index(idx, "excitability")
6926 .unwrap_or(0.0)),
6927 );
6928 properties.insert(
6929 "threshold_limit".to_string(),
6930 serde_json::json!(npu_lock
6931 .get_neuron_property_by_index(idx, "threshold_limit")
6932 .unwrap_or(0.0)),
6933 );
6934 properties.insert(
6935 "refractory_period".to_string(),
6936 serde_json::json!(npu_lock
6937 .get_neuron_property_u16_by_index(idx, "refractory_period")
6938 .unwrap_or(0)),
6939 );
6940
6941 Some(properties)
6942 }
6943
6944 pub fn get_neuron_property(
6956 &self,
6957 neuron_id: u64,
6958 property_name: &str,
6959 ) -> Option<serde_json::Value> {
6960 self.get_neuron_properties(neuron_id)?
6961 .get(property_name)
6962 .cloned()
6963 }
6964
6965 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
6976 self.cortical_areas.keys().copied().collect()
6977 }
6978
6979 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
6986 self.cortical_areas
6987 .values()
6988 .map(|area| area.cortical_idx)
6989 .collect()
6990 }
6991
6992 pub fn get_cortical_area_names(&self) -> Vec<String> {
6999 self.cortical_areas
7000 .values()
7001 .map(|area| area.name.clone())
7002 .collect()
7003 }
7004
7005 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
7012 use crate::models::CorticalAreaExt;
7013 self.cortical_areas
7014 .values()
7015 .filter(|area| area.is_input_area())
7016 .map(|area| area.cortical_id)
7017 .collect()
7018 }
7019
7020 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
7027 use crate::models::CorticalAreaExt;
7028 self.cortical_areas
7029 .values()
7030 .filter(|area| area.is_output_area())
7031 .map(|area| area.cortical_id)
7032 .collect()
7033 }
7034
7035 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
7042 self.cortical_areas
7043 .values()
7044 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
7045 (
7046 max_w.max(area.dimensions.width as usize),
7047 max_h.max(area.dimensions.height as usize),
7048 max_d.max(area.dimensions.depth as usize),
7049 )
7050 })
7051 }
7052
7053 pub fn get_cortical_area_properties(
7064 &self,
7065 cortical_id: &CorticalID,
7066 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7067 let area = self.get_cortical_area(cortical_id)?;
7068
7069 let mut properties = std::collections::HashMap::new();
7070 properties.insert(
7071 "cortical_id".to_string(),
7072 serde_json::json!(area.cortical_id),
7073 );
7074 properties.insert(
7075 "cortical_id_s".to_string(),
7076 serde_json::json!(area.cortical_id.to_string()),
7077 );
7078 properties.insert(
7079 "cortical_idx".to_string(),
7080 serde_json::json!(area.cortical_idx),
7081 );
7082 properties.insert("name".to_string(), serde_json::json!(area.name));
7083 use crate::models::CorticalAreaExt;
7084 properties.insert(
7085 "area_type".to_string(),
7086 serde_json::json!(area.get_cortical_group()),
7087 );
7088 properties.insert(
7089 "dimensions".to_string(),
7090 serde_json::json!({
7091 "width": area.dimensions.width,
7092 "height": area.dimensions.height,
7093 "depth": area.dimensions.depth,
7094 }),
7095 );
7096 properties.insert("position".to_string(), serde_json::json!(area.position));
7097
7098 for (key, value) in &area.properties {
7100 properties.insert(key.clone(), value.clone());
7101 }
7102
7103 properties.extend(area.properties.clone());
7105
7106 Some(properties)
7107 }
7108
7109 pub fn get_all_cortical_area_properties(
7116 &self,
7117 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
7118 self.cortical_areas
7119 .keys()
7120 .filter_map(|id| self.get_cortical_area_properties(id))
7121 .collect()
7122 }
7123
7124 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
7135 self.brain_regions
7136 .get_all_region_ids()
7137 .into_iter()
7138 .cloned()
7139 .collect()
7140 }
7141
7142 pub fn get_brain_region_names(&self) -> Vec<String> {
7149 self.brain_regions
7150 .get_all_region_ids()
7151 .iter()
7152 .filter_map(|id| {
7153 self.brain_regions
7154 .get_region(id)
7155 .map(|region| region.name.clone())
7156 })
7157 .collect()
7158 }
7159
7160 pub fn get_brain_region_properties(
7171 &self,
7172 region_id: &str,
7173 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
7174 let region = self.brain_regions.get_region(region_id)?;
7175
7176 let mut properties = std::collections::HashMap::new();
7177 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
7178 properties.insert("name".to_string(), serde_json::json!(region.name));
7179 properties.insert(
7180 "region_type".to_string(),
7181 serde_json::json!(format!("{:?}", region.region_type)),
7182 );
7183 properties.insert(
7184 "cortical_areas".to_string(),
7185 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
7186 );
7187
7188 properties.extend(region.properties.clone());
7190
7191 Some(properties)
7192 }
7193
7194 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
7205 self.cortical_areas.contains_key(cortical_id)
7206 }
7207
7208 pub fn brain_region_exists(&self, region_id: &str) -> bool {
7219 self.brain_regions.get_region(region_id).is_some()
7220 }
7221
7222 pub fn get_brain_region_count(&self) -> usize {
7229 self.brain_regions.region_count()
7230 }
7231
7232 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
7243 self.get_neurons_in_area(cortical_id)
7247 }
7248}
7249
7250impl std::fmt::Debug for ConnectomeManager {
7252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7253 f.debug_struct("ConnectomeManager")
7254 .field("cortical_areas", &self.cortical_areas.len())
7255 .field("next_cortical_idx", &self.next_cortical_idx)
7256 .field("brain_regions", &self.brain_regions)
7257 .field(
7258 "npu",
7259 &if self.npu.is_some() {
7260 "Connected"
7261 } else {
7262 "Not connected"
7263 },
7264 )
7265 .field("initialized", &self.initialized)
7266 .finish()
7267 }
7268}
7269
7270#[cfg(test)]
7271mod tests {
7272 use super::*;
7273 use feagi_structures::genomic::cortical_area::CoreCorticalType;
7274
7275 #[test]
7276 fn test_singleton_instance() {
7277 let instance1 = ConnectomeManager::instance();
7278 let instance2 = ConnectomeManager::instance();
7279
7280 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
7282 }
7283
7284 #[test]
7285 fn test_add_cortical_area() {
7286 ConnectomeManager::reset_for_testing();
7287
7288 let instance = ConnectomeManager::instance();
7289 let mut manager = instance.write();
7290
7291 use feagi_structures::genomic::cortical_area::{
7292 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7293 };
7294 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7296 let area = CorticalArea::new(
7297 cortical_id,
7298 0,
7299 "Visual Input".to_string(),
7300 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
7301 (0, 0, 0).into(),
7302 cortical_type,
7303 )
7304 .unwrap();
7305
7306 let initial_count = manager.get_cortical_area_count();
7307 let _cortical_idx = manager.add_cortical_area(area).unwrap();
7308
7309 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
7310 assert!(manager.has_cortical_area(&cortical_id));
7311 assert!(manager.is_initialized());
7312 }
7313
7314 #[test]
7315 fn test_cortical_area_lookups() {
7316 ConnectomeManager::reset_for_testing();
7317
7318 let instance = ConnectomeManager::instance();
7319 let mut manager = instance.write();
7320
7321 use feagi_structures::genomic::cortical_area::{
7322 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7323 };
7324 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7326 let area = CorticalArea::new(
7327 cortical_id,
7328 0,
7329 "Test Area".to_string(),
7330 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7331 (0, 0, 0).into(),
7332 cortical_type,
7333 )
7334 .unwrap();
7335
7336 let cortical_idx = manager.add_cortical_area(area).unwrap();
7337
7338 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
7340
7341 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
7343
7344 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
7346 assert_eq!(retrieved_area.name, "Test Area");
7347 }
7348
7349 #[test]
7350 fn test_remove_cortical_area() {
7351 ConnectomeManager::reset_for_testing();
7352
7353 let instance = ConnectomeManager::instance();
7354 let mut manager = instance.write();
7355
7356 use feagi_structures::genomic::cortical_area::{
7357 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7358 };
7359 let cortical_id = CoreCorticalType::Power.to_cortical_id();
7360
7361 if manager.has_cortical_area(&cortical_id) {
7363 manager.remove_cortical_area(&cortical_id).unwrap();
7364 }
7365
7366 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7367 let area = CorticalArea::new(
7368 cortical_id,
7369 0,
7370 "Test".to_string(),
7371 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7372 (0, 0, 0).into(),
7373 cortical_type,
7374 )
7375 .unwrap();
7376
7377 let initial_count = manager.get_cortical_area_count();
7378 manager.add_cortical_area(area).unwrap();
7379 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
7380
7381 manager.remove_cortical_area(&cortical_id).unwrap();
7382 assert_eq!(manager.get_cortical_area_count(), initial_count);
7383 assert!(!manager.has_cortical_area(&cortical_id));
7384 }
7385
7386 #[test]
7387 fn test_duplicate_area_error() {
7388 ConnectomeManager::reset_for_testing();
7389
7390 let instance = ConnectomeManager::instance();
7391 let mut manager = instance.write();
7392
7393 use feagi_structures::genomic::cortical_area::{
7394 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7395 };
7396 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
7399 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7400 let area1 = CorticalArea::new(
7401 cortical_id,
7402 0,
7403 "First".to_string(),
7404 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7405 (0, 0, 0).into(),
7406 cortical_type,
7407 )
7408 .unwrap();
7409
7410 let area2 = CorticalArea::new(
7411 cortical_id, 1,
7413 "Second".to_string(),
7414 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
7415 (0, 0, 0).into(),
7416 cortical_type,
7417 )
7418 .unwrap();
7419
7420 manager.add_cortical_area(area1).unwrap();
7421 let result = manager.add_cortical_area(area2);
7422
7423 assert!(result.is_err());
7424 }
7425
7426 #[test]
7427 fn test_brain_region_management() {
7428 ConnectomeManager::reset_for_testing();
7429
7430 let instance = ConnectomeManager::instance();
7431 let mut manager = instance.write();
7432
7433 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
7434 let region_id_str = region_id.to_string();
7435 let root = BrainRegion::new(
7436 region_id,
7437 "Root".to_string(),
7438 feagi_structures::genomic::brain_regions::RegionType::Undefined,
7439 )
7440 .unwrap();
7441
7442 let initial_count = manager.get_brain_region_ids().len();
7443 manager.add_brain_region(root, None).unwrap();
7444
7445 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
7446 assert!(manager.get_brain_region(®ion_id_str).is_some());
7447 }
7448
7449 #[test]
7450 fn test_synapse_operations() {
7451 use feagi_npu_burst_engine::npu::RustNPU;
7452 use feagi_npu_burst_engine::TracingMutex;
7453 use std::sync::Arc;
7454
7455 use feagi_npu_burst_engine::backend::CPUBackend;
7457 use feagi_npu_burst_engine::DynamicNPU;
7458 use feagi_npu_runtime::StdRuntime;
7459
7460 let runtime = StdRuntime;
7461 let backend = CPUBackend::new();
7462 let npu_result =
7463 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
7464 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
7465 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
7466
7467 use feagi_structures::genomic::cortical_area::{
7469 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7470 };
7471 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
7473 let area = CorticalArea::new(
7474 cortical_id,
7475 0, "Test Area".to_string(),
7477 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
7478 (0, 0, 0).into(), cortical_type,
7480 )
7481 .unwrap();
7482 let cortical_idx = manager.add_cortical_area(area).unwrap();
7483
7484 if let Some(npu_arc) = manager.get_npu() {
7486 if let Ok(mut npu_guard) = npu_arc.try_lock() {
7487 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
7488 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
7489 }
7490 }
7491 }
7492
7493 let neuron1_id = manager
7495 .add_neuron(
7496 &cortical_id,
7497 0,
7498 0,
7499 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
7511 .unwrap();
7512
7513 let neuron2_id = manager
7514 .add_neuron(
7515 &cortical_id,
7516 1,
7517 0,
7518 0, 100.0,
7520 f32::MAX, 0.1,
7522 -60.0,
7523 0,
7524 2,
7525 1.0,
7526 5,
7527 10,
7528 false,
7529 )
7530 .unwrap();
7531
7532 manager
7534 .create_synapse(
7535 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
7539 .unwrap();
7540
7541 println!("✅ Synapse creation test passed");
7544 }
7545
7546 #[test]
7547 fn test_apply_cortical_mapping_missing_rules_is_ok() {
7548 let mut manager = ConnectomeManager::new_for_testing();
7551
7552 use feagi_structures::genomic::cortical_area::{
7553 CorticalAreaType, IOCorticalAreaConfigurationFlag,
7554 };
7555
7556 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
7557 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
7558
7559 let src_area = CorticalArea::new(
7560 src_id,
7561 0,
7562 "src".to_string(),
7563 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7564 (0, 0, 0).into(),
7565 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7566 )
7567 .unwrap();
7568
7569 let dst_area = CorticalArea::new(
7570 dst_id,
7571 1,
7572 "dst".to_string(),
7573 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7574 (0, 0, 0).into(),
7575 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7576 )
7577 .unwrap();
7578
7579 manager.add_cortical_area(src_area).unwrap();
7580 manager.add_cortical_area(dst_area).unwrap();
7581
7582 let count = manager
7584 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7585 .unwrap();
7586 assert_eq!(count, 0);
7587
7588 manager
7590 .update_cortical_mapping(
7591 &src_id,
7592 &dst_id,
7593 vec![serde_json::json!({"morphology_id":"m1"})],
7594 )
7595 .unwrap();
7596 manager
7597 .update_cortical_mapping(&src_id, &dst_id, vec![])
7598 .unwrap();
7599
7600 let count2 = manager
7601 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
7602 .unwrap();
7603 assert_eq!(count2, 0);
7604 }
7605
7606 #[test]
7607 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
7608 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
7609 let mapping_dst = serde_json::json!({
7610 "csrc0002": [
7611 {"morphology_id": "m1"}
7612 ]
7613 });
7614 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
7615
7616 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
7617 .expect("legacy destination key should resolve");
7618 assert_eq!(rules.len(), 1);
7619 assert_eq!(
7620 rules[0].get("morphology_id").and_then(|v| v.as_str()),
7621 Some("m1")
7622 );
7623 }
7624
7625 #[test]
7626 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
7627 use feagi_npu_burst_engine::backend::CPUBackend;
7628 use feagi_npu_burst_engine::RustNPU;
7629 use feagi_npu_burst_engine::TracingMutex;
7630 use feagi_npu_runtime::StdRuntime;
7631 use feagi_structures::genomic::cortical_area::{
7632 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7633 };
7634 use std::sync::Arc;
7635
7636 let runtime = StdRuntime;
7637 let backend = CPUBackend::new();
7638 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7639 let dyn_npu = Arc::new(TracingMutex::new(
7640 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7641 "TestNPU",
7642 ));
7643 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7644
7645 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
7646 let area = CorticalArea::new(
7647 area_id,
7648 0,
7649 "n".to_string(),
7650 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7651 (0, 0, 0).into(),
7652 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7653 )
7654 .unwrap();
7655
7656 manager.add_cortical_area(area).unwrap();
7657 let nid = manager
7658 .add_neuron(
7659 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
7660 )
7661 .unwrap();
7662
7663 let props = manager
7664 .get_neuron_properties(nid)
7665 .expect("neuron properties");
7666 for key in [
7667 "consecutive_fire_count",
7668 "consecutive_fire_limit",
7669 "snooze_period",
7670 "membrane_potential",
7671 "threshold",
7672 "refractory_countdown",
7673 "mp_charge_accumulation",
7674 "neuron_type",
7675 "mp_driven_psp",
7676 "psp_uniform_distribution",
7677 "leak_coefficient",
7678 "resting_potential",
7679 "excitability",
7680 "threshold_limit",
7681 "refractory_period",
7682 ] {
7683 assert!(props.contains_key(key), "missing neuron state key: {key}");
7684 }
7685 }
7686
7687 #[test]
7688 fn test_mapping_deletion_prunes_synapses_between_areas() {
7689 use feagi_npu_burst_engine::backend::CPUBackend;
7690 use feagi_npu_burst_engine::RustNPU;
7691 use feagi_npu_burst_engine::TracingMutex;
7692 use feagi_npu_runtime::StdRuntime;
7693 use feagi_structures::genomic::cortical_area::{
7694 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7695 };
7696 use std::sync::Arc;
7697
7698 let runtime = StdRuntime;
7700 let backend = CPUBackend::new();
7701 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7702 let dyn_npu = Arc::new(TracingMutex::new(
7703 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7704 "TestNPU",
7705 ));
7706 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7707
7708 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
7710 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
7711
7712 let src_area = CorticalArea::new(
7713 src_id,
7714 0,
7715 "src".to_string(),
7716 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7717 (0, 0, 0).into(),
7718 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7719 )
7720 .unwrap();
7721 let dst_area = CorticalArea::new(
7722 dst_id,
7723 1,
7724 "dst".to_string(),
7725 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7726 (0, 0, 0).into(),
7727 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7728 )
7729 .unwrap();
7730
7731 manager.add_cortical_area(src_area).unwrap();
7732 manager.add_cortical_area(dst_area).unwrap();
7733
7734 let s0 = manager
7736 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7737 .unwrap();
7738 let s1 = manager
7739 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7740 .unwrap();
7741 let t0 = manager
7742 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7743 .unwrap();
7744 let t1 = manager
7745 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7746 .unwrap();
7747
7748 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7750 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7751
7752 {
7754 let mut npu = dyn_npu.lock().unwrap();
7755 npu.rebuild_synapse_index();
7756 assert_eq!(npu.get_synapse_count(), 2);
7757 }
7758
7759 manager
7761 .update_cortical_mapping(&src_id, &dst_id, vec![])
7762 .unwrap();
7763 let created = manager
7764 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7765 .unwrap();
7766 assert_eq!(created, 0);
7767
7768 {
7770 let mut npu = dyn_npu.lock().unwrap();
7771 npu.rebuild_synapse_index();
7773 assert_eq!(npu.get_synapse_count(), 0);
7774 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7775 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7776 }
7777 }
7778
7779 #[test]
7780 fn test_mapping_update_prunes_synapses_between_areas() {
7781 use feagi_npu_burst_engine::backend::CPUBackend;
7782 use feagi_npu_burst_engine::RustNPU;
7783 use feagi_npu_burst_engine::TracingMutex;
7784 use feagi_npu_runtime::StdRuntime;
7785 use feagi_structures::genomic::cortical_area::{
7786 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
7787 };
7788 use std::sync::Arc;
7789
7790 let runtime = StdRuntime;
7792 let backend = CPUBackend::new();
7793 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7794 let dyn_npu = Arc::new(TracingMutex::new(
7795 feagi_npu_burst_engine::DynamicNPU::F32(npu),
7796 "TestNPU",
7797 ));
7798 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7799
7800 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7802
7803 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
7806 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
7807
7808 let src_area = CorticalArea::new(
7809 src_id,
7810 0,
7811 "src".to_string(),
7812 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7813 (0, 0, 0).into(),
7814 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
7815 )
7816 .unwrap();
7817 let dst_area = CorticalArea::new(
7818 dst_id,
7819 0,
7820 "dst".to_string(),
7821 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7822 (0, 0, 0).into(),
7823 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
7824 )
7825 .unwrap();
7826
7827 manager.add_cortical_area(src_area).unwrap();
7828 manager.add_cortical_area(dst_area).unwrap();
7829
7830 let s0 = manager
7832 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7833 .unwrap();
7834 let s1 = manager
7835 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7836 .unwrap();
7837 let t0 = manager
7838 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7839 .unwrap();
7840 let t1 = manager
7841 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
7842 .unwrap();
7843
7844 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
7846 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
7847
7848 {
7850 let mut npu = dyn_npu.lock().unwrap();
7851 npu.rebuild_synapse_index();
7852 assert_eq!(npu.get_synapse_count(), 2);
7853 }
7854
7855 manager
7861 .update_cortical_mapping(
7862 &src_id,
7863 &dst_id,
7864 vec![serde_json::json!({
7865 "morphology_id": "episodic_memory",
7866 "morphology_scalar": [1],
7867 "postSynapticCurrent_multiplier": 1,
7868 "plasticity_flag": false,
7869 "plasticity_constant": 0,
7870 "ltp_multiplier": 0,
7871 "ltd_multiplier": 0,
7872 "plasticity_window": 0,
7873 })],
7874 )
7875 .unwrap();
7876 let created = manager
7877 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7878 .unwrap();
7879 assert_eq!(created, 0);
7880
7881 {
7883 let mut npu = dyn_npu.lock().unwrap();
7884 npu.rebuild_synapse_index();
7886 assert_eq!(npu.get_synapse_count(), 0);
7887 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
7888 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
7889 }
7890 }
7891
7892 #[test]
7893 fn test_upstream_area_tracking() {
7894 use crate::models::cortical_area::CorticalArea;
7896 use feagi_npu_burst_engine::backend::CPUBackend;
7897 use feagi_npu_burst_engine::TracingMutex;
7898 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
7899 use feagi_npu_runtime::StdRuntime;
7900 use feagi_structures::genomic::cortical_area::{
7901 CorticalAreaDimensions, CorticalAreaType, CorticalID,
7902 };
7903
7904 let runtime = StdRuntime;
7906 let backend = CPUBackend::new();
7907 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
7908 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
7909 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
7910
7911 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
7914
7915 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
7917 let src_area = CorticalArea::new(
7918 src_id,
7919 0,
7920 "Source Area".to_string(),
7921 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7922 (0, 0, 0).into(),
7923 CorticalAreaType::Custom(
7924 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7925 ),
7926 )
7927 .unwrap();
7928 let src_idx = manager.add_cortical_area(src_area).unwrap();
7929
7930 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
7932 let dst_area = CorticalArea::new(
7933 dst_id,
7934 0,
7935 "Dest Area".to_string(),
7936 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
7937 (0, 0, 0).into(),
7938 CorticalAreaType::Custom(
7939 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
7940 ),
7941 )
7942 .unwrap();
7943 manager.add_cortical_area(dst_area).unwrap();
7944
7945 {
7947 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
7948 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
7949 assert!(
7950 upstream.as_array().unwrap().is_empty(),
7951 "Upstream areas should be empty initially"
7952 );
7953 }
7954
7955 let mapping_data = vec![serde_json::json!({
7957 "morphology_id": "episodic_memory",
7958 "morphology_scalar": 1,
7959 "postSynapticCurrent_multiplier": 1.0,
7960 })];
7961 manager
7962 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
7963 .unwrap();
7964 manager
7965 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7966 .unwrap();
7967
7968 {
7970 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7971 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
7972 assert_eq!(
7973 upstream_areas[0], src_idx,
7974 "Upstream area should be src_idx"
7975 );
7976 }
7977
7978 manager
7980 .update_cortical_mapping(&src_id, &dst_id, vec![])
7981 .unwrap();
7982 manager
7983 .regenerate_synapses_for_mapping(&src_id, &dst_id)
7984 .unwrap();
7985
7986 {
7988 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
7989 assert_eq!(
7990 upstream_areas.len(),
7991 0,
7992 "Should have 0 upstream areas after deletion"
7993 );
7994 }
7995 }
7996
7997 #[test]
7998 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
7999 use crate::models::cortical_area::CorticalArea;
8000 use feagi_npu_burst_engine::backend::CPUBackend;
8001 use feagi_npu_burst_engine::TracingMutex;
8002 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8003 use feagi_npu_runtime::StdRuntime;
8004 use feagi_structures::genomic::cortical_area::{
8005 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8006 };
8007 use std::sync::Arc;
8008
8009 let runtime = StdRuntime;
8010 let backend = CPUBackend::new();
8011 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8012 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8013 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8014 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8015
8016 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8017 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
8018 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
8019 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
8020
8021 let a1_area = CorticalArea::new(
8022 a1_id,
8023 0,
8024 "A1".to_string(),
8025 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8026 (0, 0, 0).into(),
8027 CorticalAreaType::Custom(
8028 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8029 ),
8030 )
8031 .unwrap();
8032 let a2_area = CorticalArea::new(
8033 a2_id,
8034 0,
8035 "A2".to_string(),
8036 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8037 (0, 0, 0).into(),
8038 CorticalAreaType::Custom(
8039 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8040 ),
8041 )
8042 .unwrap();
8043
8044 let mut m1_area = CorticalArea::new(
8045 m1_id,
8046 0,
8047 "M1".to_string(),
8048 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8049 (0, 0, 0).into(),
8050 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8051 )
8052 .unwrap();
8053 m1_area
8054 .properties
8055 .insert("is_mem_type".to_string(), serde_json::json!(true));
8056 m1_area
8057 .properties
8058 .insert("temporal_depth".to_string(), serde_json::json!(1));
8059
8060 let mut m2_area = CorticalArea::new(
8061 m2_id,
8062 0,
8063 "M2".to_string(),
8064 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8065 (0, 0, 0).into(),
8066 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8067 )
8068 .unwrap();
8069 m2_area
8070 .properties
8071 .insert("is_mem_type".to_string(), serde_json::json!(true));
8072 m2_area
8073 .properties
8074 .insert("temporal_depth".to_string(), serde_json::json!(1));
8075
8076 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
8077 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
8078 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
8079 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
8080
8081 manager
8082 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8083 .unwrap();
8084 manager
8085 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8086 .unwrap();
8087
8088 let episodic_mapping = vec![serde_json::json!({
8089 "morphology_id": "episodic_memory",
8090 "morphology_scalar": 1,
8091 "postSynapticCurrent_multiplier": 1.0,
8092 })];
8093 manager
8094 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
8095 .unwrap();
8096 manager
8097 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
8098 .unwrap();
8099 manager
8100 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
8101 .unwrap();
8102 manager
8103 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
8104 .unwrap();
8105
8106 let assoc_mapping = vec![serde_json::json!({
8107 "morphology_id": "associative_memory",
8108 "morphology_scalar": 1,
8109 "postSynapticCurrent_multiplier": 1.0,
8110 "plasticity_flag": true,
8111 "plasticity_constant": 1,
8112 "ltp_multiplier": 1,
8113 "ltd_multiplier": 1,
8114 "plasticity_window": 5,
8115 })];
8116 manager
8117 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
8118 .unwrap();
8119 manager
8120 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8121 .unwrap();
8122 manager
8124 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
8125 .unwrap();
8126 manager
8127 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
8128 .unwrap();
8129
8130 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8131 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8132 assert_eq!(
8133 upstream_m1.len(),
8134 2,
8135 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
8136 );
8137 assert_eq!(
8138 upstream_m2.len(),
8139 2,
8140 "M2 should have A2 and M1 as upstreams"
8141 );
8142
8143 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
8144 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
8145
8146 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8147 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8148 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
8149 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
8150 assert!(upstream_m1.contains(&a1_idx));
8151 assert!(upstream_m1.contains(&m2_idx));
8152 assert!(upstream_m2.contains(&a2_idx));
8153 assert!(upstream_m2.contains(&m1_idx));
8154
8155 {
8157 let mut npu_lock = dyn_npu.lock().unwrap();
8158 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
8159 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
8160 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
8161 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
8162 npu_lock.process_burst().expect("Burst processing failed");
8163 }
8164
8165 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
8166 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
8167 assert_eq!(
8168 upstream_m1.len(),
8169 2,
8170 "M1 should keep 2 upstreams after firing"
8171 );
8172 assert_eq!(
8173 upstream_m2.len(),
8174 2,
8175 "M2 should keep 2 upstreams after firing"
8176 );
8177
8178 let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
8179 let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
8180 assert_eq!(
8181 episodic_upstream_m1,
8182 vec![a1_idx],
8183 "Episodic upstream list for M1 should exclude associative-only memory source M2"
8184 );
8185 assert_eq!(
8186 episodic_upstream_m2,
8187 vec![a2_idx],
8188 "Episodic upstream list for M2 should exclude associative-only memory source M1"
8189 );
8190 }
8191
8192 #[test]
8193 fn test_memory_twin_created_for_memory_mapping() {
8194 use crate::models::cortical_area::CorticalArea;
8195 use feagi_npu_burst_engine::backend::CPUBackend;
8196 use feagi_npu_burst_engine::TracingMutex;
8197 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8198 use feagi_npu_runtime::StdRuntime;
8199 use feagi_structures::genomic::cortical_area::{
8200 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8201 MemoryCorticalType,
8202 };
8203 use std::sync::Arc;
8204
8205 let runtime = StdRuntime;
8206 let backend = CPUBackend::new();
8207 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8208 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8209 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8210 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8211
8212 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
8213 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
8214
8215 let src_area = CorticalArea::new(
8216 src_id,
8217 0,
8218 "Source Area".to_string(),
8219 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8220 (0, 0, 0).into(),
8221 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8222 )
8223 .unwrap();
8224 let mut dst_area = CorticalArea::new(
8225 dst_id,
8226 0,
8227 "Memory Area".to_string(),
8228 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8229 (0, 0, 0).into(),
8230 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8231 )
8232 .unwrap();
8233 dst_area
8234 .properties
8235 .insert("is_mem_type".to_string(), serde_json::json!(true));
8236 dst_area
8237 .properties
8238 .insert("temporal_depth".to_string(), serde_json::json!(1));
8239
8240 manager.add_cortical_area(src_area).unwrap();
8241 manager.add_cortical_area(dst_area).unwrap();
8242
8243 let mapping_data = vec![serde_json::json!({
8244 "morphology_id": "episodic_memory",
8245 "morphology_scalar": 1,
8246 "postSynapticCurrent_multiplier": 1.0,
8247 })];
8248 manager
8249 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
8250 .unwrap();
8251 manager
8252 .regenerate_synapses_for_mapping(&src_id, &dst_id)
8253 .unwrap();
8254
8255 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
8256 let twin_map = memory_area
8257 .properties
8258 .get("memory_twin_areas")
8259 .and_then(|v| v.as_object())
8260 .expect("memory_twin_areas should be set");
8261 let twin_id_str = twin_map
8262 .get(&src_id.as_base_64())
8263 .and_then(|v| v.as_str())
8264 .expect("Missing twin entry for upstream area");
8265 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
8266 let mapping = memory_area
8267 .properties
8268 .get("cortical_mapping_dst")
8269 .and_then(|v| v.as_object())
8270 .and_then(|map| map.get(&twin_id.as_base_64()))
8271 .and_then(|v| v.as_array())
8272 .expect("Missing memory replay mapping for twin area");
8273 let uses_replay = mapping.iter().any(|rule| {
8274 rule.get("morphology_id")
8275 .and_then(|v| v.as_str())
8276 .is_some_and(|id| id == "memory_replay")
8277 });
8278 assert!(uses_replay, "Expected memory_replay mapping for twin area");
8279
8280 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8281 assert!(matches!(
8282 twin_area.cortical_type,
8283 CorticalAreaType::Custom(_)
8284 ));
8285 assert_eq!(
8286 twin_area
8287 .properties
8288 .get("memory_twin_of")
8289 .and_then(|v| v.as_str()),
8290 Some(src_id.as_base_64().as_str())
8291 );
8292 assert_eq!(
8293 twin_area
8294 .properties
8295 .get("memory_twin_for")
8296 .and_then(|v| v.as_str()),
8297 Some(dst_id.as_base_64().as_str())
8298 );
8299 }
8300
8301 #[test]
8302 fn test_associative_memory_between_memory_areas_creates_synapses() {
8303 use crate::models::cortical_area::CorticalArea;
8304 use feagi_npu_burst_engine::backend::CPUBackend;
8305 use feagi_npu_burst_engine::TracingMutex;
8306 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8307 use feagi_npu_runtime::StdRuntime;
8308 use feagi_structures::genomic::cortical_area::{
8309 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8310 };
8311 use std::sync::Arc;
8312
8313 let runtime = StdRuntime;
8314 let backend = CPUBackend::new();
8315 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8316 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8317 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8318 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8319
8320 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
8321 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
8322
8323 let mut m1_area = CorticalArea::new(
8324 m1_id,
8325 0,
8326 "Memory M1".to_string(),
8327 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8328 (0, 0, 0).into(),
8329 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8330 )
8331 .unwrap();
8332 m1_area
8333 .properties
8334 .insert("is_mem_type".to_string(), serde_json::json!(true));
8335 m1_area
8336 .properties
8337 .insert("temporal_depth".to_string(), serde_json::json!(1));
8338
8339 let mut m2_area = CorticalArea::new(
8340 m2_id,
8341 0,
8342 "Memory M2".to_string(),
8343 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8344 (0, 0, 0).into(),
8345 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8346 )
8347 .unwrap();
8348 m2_area
8349 .properties
8350 .insert("is_mem_type".to_string(), serde_json::json!(true));
8351 m2_area
8352 .properties
8353 .insert("temporal_depth".to_string(), serde_json::json!(1));
8354
8355 manager.add_cortical_area(m1_area).unwrap();
8356 manager.add_cortical_area(m2_area).unwrap();
8357
8358 manager
8359 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8360 .unwrap();
8361 manager
8362 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8363 .unwrap();
8364
8365 let mapping_data = vec![serde_json::json!({
8366 "morphology_id": "associative_memory",
8367 "morphology_scalar": 1,
8368 "postSynapticCurrent_multiplier": 1.0,
8369 "plasticity_flag": true,
8370 "plasticity_constant": 1,
8371 "ltp_multiplier": 1,
8372 "ltd_multiplier": 1,
8373 "plasticity_window": 5,
8374 })];
8375 manager
8376 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
8377 .unwrap();
8378 let created = manager
8379 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8380 .unwrap();
8381 assert!(
8382 created > 0,
8383 "Expected associative memory mapping between memory areas to create synapses"
8384 );
8385 let npu_guard = dyn_npu.lock().unwrap();
8386 let assoc_tagged =
8387 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
8388 assert!(
8389 assoc_tagged >= 1,
8390 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
8391 );
8392 }
8393
8394 #[test]
8395 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
8396 use crate::models::cortical_area::CorticalArea;
8397 use feagi_npu_burst_engine::backend::CPUBackend;
8398 use feagi_npu_burst_engine::TracingMutex;
8399 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8400 use feagi_npu_runtime::StdRuntime;
8401 use feagi_structures::genomic::cortical_area::{
8402 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8403 MemoryCorticalType,
8404 };
8405 use std::sync::Arc;
8406
8407 let runtime = StdRuntime;
8408 let backend = CPUBackend::new();
8409 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8410 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8411 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8412 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8413
8414 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
8415 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
8416
8417 let src_area = CorticalArea::new(
8418 src_id,
8419 0,
8420 "Source Area".to_string(),
8421 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8422 (0, 0, 0).into(),
8423 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8424 )
8425 .unwrap();
8426 let mut mem_area = CorticalArea::new(
8427 mem_id,
8428 0,
8429 "Memory Area".to_string(),
8430 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8431 (0, 0, 0).into(),
8432 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8433 )
8434 .unwrap();
8435 mem_area
8436 .properties
8437 .insert("is_mem_type".to_string(), serde_json::json!(true));
8438 mem_area
8439 .properties
8440 .insert("temporal_depth".to_string(), serde_json::json!(1));
8441
8442 manager.add_cortical_area(src_area).unwrap();
8443 manager.add_cortical_area(mem_area).unwrap();
8444
8445 let twin_id = manager
8446 .build_memory_twin_id(&mem_id, &src_id)
8447 .expect("Failed to build twin id");
8448 let twin_area = CorticalArea::new(
8449 twin_id,
8450 0,
8451 "Source Area_twin".to_string(),
8452 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8453 (0, 0, 0).into(),
8454 CorticalAreaType::Custom(
8455 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8456 ),
8457 )
8458 .unwrap();
8459 manager.add_cortical_area(twin_area).unwrap();
8460
8461 let repaired = manager
8462 .ensure_memory_twin_area(&mem_id, &src_id)
8463 .expect("Failed to repair twin");
8464 assert_eq!(repaired, twin_id);
8465
8466 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
8467 let twin_map = mem_area
8468 .properties
8469 .get("memory_twin_areas")
8470 .and_then(|v| v.as_object())
8471 .expect("memory_twin_areas should be set");
8472 let twin_id_str = twin_map
8473 .get(&src_id.as_base_64())
8474 .and_then(|v| v.as_str())
8475 .expect("Missing twin entry for upstream area");
8476 assert_eq!(twin_id_str, twin_id.as_base_64());
8477
8478 let replay_map = mem_area
8479 .properties
8480 .get("cortical_mapping_dst")
8481 .and_then(|v| v.as_object())
8482 .and_then(|map| map.get(&twin_id.as_base_64()))
8483 .and_then(|v| v.as_array())
8484 .expect("Missing memory replay mapping for twin area");
8485 let uses_replay = replay_map.iter().any(|rule| {
8486 rule.get("morphology_id")
8487 .and_then(|v| v.as_str())
8488 .is_some_and(|id| id == "memory_replay")
8489 });
8490 assert!(uses_replay, "Expected memory_replay mapping for twin area");
8491
8492 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8493 assert_eq!(
8494 twin_area
8495 .properties
8496 .get("memory_twin_of")
8497 .and_then(|v| v.as_str()),
8498 Some(src_id.as_base_64().as_str())
8499 );
8500 assert_eq!(
8501 twin_area
8502 .properties
8503 .get("memory_twin_for")
8504 .and_then(|v| v.as_str()),
8505 Some(mem_id.as_base_64().as_str())
8506 );
8507 }
8508
8509 #[test]
8510 fn test_memory_twin_inherits_upstream_parent_region() {
8511 use crate::models::cortical_area::CorticalArea;
8512 use crate::models::BrainRegion;
8513 use feagi_npu_burst_engine::backend::CPUBackend;
8514 use feagi_npu_burst_engine::TracingMutex;
8515 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8516 use feagi_npu_runtime::StdRuntime;
8517 use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
8518 use feagi_structures::genomic::cortical_area::{
8519 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8520 MemoryCorticalType,
8521 };
8522 use std::sync::Arc;
8523
8524 let runtime = StdRuntime;
8525 let backend = CPUBackend::new();
8526 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8527 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8528 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8529 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8530 let src_region_id = RegionID::new();
8531 let src_region_id_str = src_region_id.to_string();
8532 manager
8533 .add_brain_region(
8534 BrainRegion::new(
8535 src_region_id,
8536 "Src Region".to_string(),
8537 RegionType::Undefined,
8538 )
8539 .unwrap(),
8540 None,
8541 )
8542 .unwrap();
8543 let mem_region_id = RegionID::new();
8544 let mem_region_id_str = mem_region_id.to_string();
8545 manager
8546 .add_brain_region(
8547 BrainRegion::new(
8548 mem_region_id,
8549 "Mem Region".to_string(),
8550 RegionType::Undefined,
8551 )
8552 .unwrap(),
8553 None,
8554 )
8555 .unwrap();
8556
8557 let src_id = CorticalID::try_from_bytes(b"csrc1001").unwrap();
8558 let mem_id = CorticalID::try_from_bytes(b"mmem1001").unwrap();
8559
8560 let mut src_area = CorticalArea::new(
8561 src_id,
8562 0,
8563 "Origin".to_string(),
8564 CorticalAreaDimensions::new(4, 4, 1).unwrap(),
8565 (10, 20, 0).into(),
8566 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8567 )
8568 .unwrap();
8569 src_area.properties.insert(
8570 "parent_region_id".to_string(),
8571 serde_json::json!(src_region_id_str.clone()),
8572 );
8573
8574 let mut mem_area = CorticalArea::new(
8575 mem_id,
8576 0,
8577 "Memory".to_string(),
8578 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
8579 (200, 300, 0).into(),
8580 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8581 )
8582 .unwrap();
8583 mem_area
8584 .properties
8585 .insert("is_mem_type".to_string(), serde_json::json!(true));
8586 mem_area
8587 .properties
8588 .insert("temporal_depth".to_string(), serde_json::json!(1));
8589 mem_area.properties.insert(
8590 "parent_region_id".to_string(),
8591 serde_json::json!(mem_region_id_str.clone()),
8592 );
8593
8594 manager.add_cortical_area(src_area).unwrap();
8595 manager.add_cortical_area(mem_area).unwrap();
8596
8597 let mapping_data = vec![serde_json::json!({
8598 "morphology_id": "episodic_memory",
8599 "morphology_scalar": 1,
8600 "postSynapticCurrent_multiplier": 1.0,
8601 })];
8602 manager
8603 .update_cortical_mapping(&src_id, &mem_id, mapping_data)
8604 .unwrap();
8605 manager
8606 .regenerate_synapses_for_mapping(&src_id, &mem_id)
8607 .unwrap();
8608
8609 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
8610 let twin_map = memory_area
8611 .properties
8612 .get("memory_twin_areas")
8613 .and_then(|v| v.as_object())
8614 .expect("memory_twin_areas should be set");
8615 let twin_id = CorticalID::try_from_base_64(
8616 twin_map
8617 .get(&src_id.as_base_64())
8618 .and_then(|v| v.as_str())
8619 .expect("missing twin for source"),
8620 )
8621 .unwrap();
8622 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
8623
8624 assert_eq!(
8625 twin_area
8626 .properties
8627 .get("parent_region_id")
8628 .and_then(|v| v.as_str()),
8629 Some(src_region_id_str.as_str()),
8630 "Twin should inherit upstream/source parent region"
8631 );
8632 assert_ne!(
8633 twin_area
8634 .properties
8635 .get("parent_region_id")
8636 .and_then(|v| v.as_str()),
8637 Some(mem_region_id_str.as_str()),
8638 "Twin must not inherit memory area's parent region"
8639 );
8640 }
8641
8642 #[test]
8643 fn test_memory_twin_diagnostic_explains_memory_upstream_blocker() {
8644 use crate::models::cortical_area::CorticalArea;
8645 use feagi_npu_burst_engine::backend::CPUBackend;
8646 use feagi_npu_burst_engine::TracingMutex;
8647 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8648 use feagi_npu_runtime::StdRuntime;
8649 use feagi_structures::genomic::cortical_area::{
8650 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
8651 };
8652 use std::sync::Arc;
8653
8654 let runtime = StdRuntime;
8655 let backend = CPUBackend::new();
8656 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
8657 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8658 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
8659 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
8660
8661 let m1_id = CorticalID::try_from_bytes(b"mmem2001").unwrap();
8662 let m2_id = CorticalID::try_from_bytes(b"mmem2002").unwrap();
8663
8664 let mut m1_area = CorticalArea::new(
8665 m1_id,
8666 0,
8667 "Memory A".to_string(),
8668 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8669 (0, 0, 0).into(),
8670 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8671 )
8672 .unwrap();
8673 m1_area
8674 .properties
8675 .insert("is_mem_type".to_string(), serde_json::json!(true));
8676 m1_area
8677 .properties
8678 .insert("temporal_depth".to_string(), serde_json::json!(1));
8679
8680 let mut m2_area = CorticalArea::new(
8681 m2_id,
8682 0,
8683 "Memory B".to_string(),
8684 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8685 (0, 0, 0).into(),
8686 CorticalAreaType::Memory(MemoryCorticalType::Memory),
8687 )
8688 .unwrap();
8689 m2_area
8690 .properties
8691 .insert("is_mem_type".to_string(), serde_json::json!(true));
8692 m2_area
8693 .properties
8694 .insert("temporal_depth".to_string(), serde_json::json!(1));
8695
8696 manager.add_cortical_area(m1_area).unwrap();
8697 manager.add_cortical_area(m2_area).unwrap();
8698
8699 let mapping_data = vec![serde_json::json!({
8700 "morphology_id": "episodic_memory",
8701 "morphology_scalar": 1,
8702 "postSynapticCurrent_multiplier": 1.0,
8703 })];
8704 manager
8705 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
8706 .unwrap();
8707 manager
8708 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
8709 .unwrap();
8710
8711 let diagnostic = manager
8712 .diagnose_memory_twin_for_mapping(&m1_id, &m2_id)
8713 .expect("diagnostic should succeed");
8714 assert!(diagnostic.mapping_exists);
8715 assert_eq!(diagnostic.episodic_rule_count, 1);
8716 assert!(!diagnostic.twin_expected);
8717 assert!(!diagnostic.twin_present);
8718 assert_eq!(
8719 diagnostic.reason.as_deref(),
8720 Some("upstream_is_memory_area_twin_not_supported")
8721 );
8722 }
8723
8724 fn build_max_weight_test_manager() -> (
8729 ConnectomeManager,
8730 CorticalID,
8731 CorticalID,
8732 CorticalID,
8733 CorticalID,
8734 ) {
8735 use feagi_npu_burst_engine::backend::CPUBackend;
8736 use feagi_npu_burst_engine::TracingMutex;
8737 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
8738 use feagi_npu_runtime::StdRuntime;
8739 use feagi_structures::genomic::cortical_area::{
8740 CorticalAreaType, IOCorticalAreaConfigurationFlag,
8741 };
8742
8743 let runtime = StdRuntime;
8744 let backend = CPUBackend::new();
8745 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("npu");
8746 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
8747 let mut mgr = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
8748 feagi_evolutionary::templates::add_core_morphologies(&mut mgr.morphology_registry);
8752
8753 let src = CorticalID::try_from_bytes(b"cstmwsrc").unwrap();
8754 let dst = CorticalID::try_from_bytes(b"cstmwdst").unwrap();
8755 let reward = CorticalID::try_from_bytes(b"cstmwrwd").unwrap();
8756 let pain = CorticalID::try_from_bytes(b"cstmwpan").unwrap();
8757
8758 for (id, label, kind) in [
8759 (
8760 src,
8761 "src",
8762 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
8763 ),
8764 (
8765 dst,
8766 "dst",
8767 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
8768 ),
8769 (
8770 reward,
8771 "reward",
8772 CorticalAreaType::Custom(
8773 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8774 ),
8775 ),
8776 (
8777 pain,
8778 "pain",
8779 CorticalAreaType::Custom(
8780 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
8781 ),
8782 ),
8783 ] {
8784 mgr.add_cortical_area(
8785 CorticalArea::new(
8786 id,
8787 0,
8788 label.to_string(),
8789 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
8790 (0, 0, 0).into(),
8791 kind,
8792 )
8793 .unwrap(),
8794 )
8795 .unwrap();
8796 mgr.add_neuron(&id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
8797 .unwrap();
8798 }
8799 (mgr, src, dst, reward, pain)
8800 }
8801
8802 fn write_and_regenerate_mapping(
8807 mgr: &mut ConnectomeManager,
8808 src: &CorticalID,
8809 dst: &CorticalID,
8810 rule: serde_json::Value,
8811 ) -> BduResult<usize> {
8812 mgr.update_cortical_mapping(src, dst, vec![rule])?;
8813 mgr.regenerate_synapses_for_mapping(src, dst)
8814 }
8815
8816 #[test]
8820 fn test_max_weight_finite_positive_accepted_on_rstdp_rule() {
8821 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8822
8823 let result = write_and_regenerate_mapping(
8824 &mut mgr,
8825 &src,
8826 &dst,
8827 serde_json::json!({
8828 "morphology_id": "block_to_block",
8829 "morphology_scalar": [1, 1, 1],
8830 "postSynapticCurrent_multiplier": 1,
8831 "plasticity_flag": true,
8832 "plasticity_constant": 1,
8833 "ltp_multiplier": 1,
8834 "ltd_multiplier": 1,
8835 "plasticity_window": 10,
8836 "synaptic_delay_bursts": 1,
8837 "plasticity_mode": "rstdp",
8838 "eligibility_decay_bursts": 50,
8839 "reward_source_area": reward.as_base_64(),
8840 "punishment_source_area": pain.as_base_64(),
8841 "max_weight": 12.5,
8842 }),
8843 );
8844 assert!(
8845 result.is_ok(),
8846 "valid max_weight=12.5 must be accepted, got {:?}",
8847 result
8848 );
8849 }
8850
8851 #[test]
8856 fn test_max_weight_invalid_values_rejected() {
8857 for bad in &[
8858 serde_json::json!(0.0),
8859 serde_json::json!(-1.5),
8860 serde_json::json!("not_a_number"),
8861 ] {
8862 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8863 let result = write_and_regenerate_mapping(
8864 &mut mgr,
8865 &src,
8866 &dst,
8867 serde_json::json!({
8868 "morphology_id": "block_to_block",
8869 "morphology_scalar": [1, 1, 1],
8870 "postSynapticCurrent_multiplier": 1,
8871 "plasticity_flag": true,
8872 "plasticity_constant": 1,
8873 "ltp_multiplier": 1,
8874 "ltd_multiplier": 1,
8875 "plasticity_window": 10,
8876 "synaptic_delay_bursts": 1,
8877 "plasticity_mode": "rstdp",
8878 "eligibility_decay_bursts": 50,
8879 "reward_source_area": reward.as_base_64(),
8880 "punishment_source_area": pain.as_base_64(),
8881 "max_weight": bad,
8882 }),
8883 );
8884 assert!(
8885 result.is_err(),
8886 "max_weight={:?} should have been rejected, got {:?}",
8887 bad,
8888 result
8889 );
8890 }
8891 }
8892
8893 #[test]
8896 fn test_ltp_ltd_multiplier_out_of_i8_range_rejected() {
8897 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8898 let result = write_and_regenerate_mapping(
8899 &mut mgr,
8900 &src,
8901 &dst,
8902 serde_json::json!({
8903 "morphology_id": "block_to_block",
8904 "morphology_scalar": [1, 1, 1],
8905 "postSynapticCurrent_multiplier": 1,
8906 "plasticity_flag": true,
8907 "plasticity_constant": 1,
8908 "ltp_multiplier": 200,
8909 "ltd_multiplier": 1,
8910 "plasticity_window": 10,
8911 "synaptic_delay_bursts": 1,
8912 "plasticity_mode": "rstdp",
8913 "eligibility_decay_bursts": 50,
8914 "reward_source_area": reward.as_base_64(),
8915 "punishment_source_area": pain.as_base_64(),
8916 }),
8917 );
8918 assert!(
8919 result.is_err(),
8920 "ltp_multiplier=200 must be rejected (i8 range); got {:?}",
8921 result
8922 );
8923 }
8924
8925 #[test]
8928 fn test_max_weight_rejected_when_plasticity_off() {
8929 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
8930
8931 let result = write_and_regenerate_mapping(
8932 &mut mgr,
8933 &src,
8934 &dst,
8935 serde_json::json!({
8936 "morphology_id": "block_to_block",
8937 "morphology_scalar": [1, 1, 1],
8938 "postSynapticCurrent_multiplier": 1,
8939 "plasticity_flag": true,
8944 "plasticity_constant": 0,
8945 "ltp_multiplier": 0,
8946 "ltd_multiplier": 0,
8947 "plasticity_window": 0,
8948 "synaptic_delay_bursts": 1,
8949 "plasticity_mode": "off",
8950 "max_weight": 10.0,
8951 }),
8952 );
8953 assert!(
8954 result.is_err(),
8955 "max_weight on off-mode rule must be rejected; got {:?}",
8956 result
8957 );
8958 }
8959
8960 #[test]
8961 fn test_plasticity_eta_rejected_when_plasticity_off() {
8962 let (mut mgr, src, dst, _reward, _pain) = build_max_weight_test_manager();
8963
8964 let result = write_and_regenerate_mapping(
8965 &mut mgr,
8966 &src,
8967 &dst,
8968 serde_json::json!({
8969 "morphology_id": "block_to_block",
8970 "morphology_scalar": [1, 1, 1],
8971 "postSynapticCurrent_multiplier": 1,
8972 "plasticity_flag": true,
8973 "plasticity_constant": 0,
8974 "ltp_multiplier": 0,
8975 "ltd_multiplier": 0,
8976 "plasticity_window": 0,
8977 "synaptic_delay_bursts": 1,
8978 "plasticity_mode": "off",
8979 "plasticity_eta": 0.5,
8980 }),
8981 );
8982 assert!(
8983 result.is_err(),
8984 "plasticity_eta on off-mode rule must be rejected; got {:?}",
8985 result
8986 );
8987 }
8988
8989 #[test]
8990 fn test_plasticity_eta_non_positive_rejected() {
8991 let (mut mgr, src, dst, reward, pain) = build_max_weight_test_manager();
8992
8993 let result = write_and_regenerate_mapping(
8994 &mut mgr,
8995 &src,
8996 &dst,
8997 serde_json::json!({
8998 "morphology_id": "block_to_block",
8999 "morphology_scalar": [1, 1, 1],
9000 "postSynapticCurrent_multiplier": 1,
9001 "plasticity_flag": true,
9002 "plasticity_constant": 1,
9003 "ltp_multiplier": 1,
9004 "ltd_multiplier": 1,
9005 "plasticity_window": 10,
9006 "synaptic_delay_bursts": 1,
9007 "plasticity_mode": "rstdp",
9008 "eligibility_decay_bursts": 50,
9009 "reward_source_area": reward.as_base_64(),
9010 "punishment_source_area": pain.as_base_64(),
9011 "plasticity_eta": 0.0,
9012 }),
9013 );
9014 assert!(
9015 result.is_err(),
9016 "plasticity_eta=0 must be rejected; got {:?}",
9017 result
9018 );
9019 }
9020}