1use once_cell::sync::Lazy;
32use parking_lot::RwLock;
33use serde::{Deserialize, Serialize};
34use std::collections::{HashMap, HashSet};
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 classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
149
150 morphology_registry: feagi_evolutionary::MorphologyRegistry,
152
153 config: ConnectomeConfig,
155
156 npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
162
163 #[cfg(feature = "plasticity")]
165 plasticity_executor:
166 Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
167
168 cached_neuron_count: Arc<AtomicUsize>,
171
172 cached_synapse_count: Arc<AtomicUsize>,
175
176 cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
179
180 cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
183
184 initialized: bool,
186
187 last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
189}
190
191type NeuronData = (
193 u32,
194 u32,
195 u32,
196 f32,
197 f32,
198 f32,
199 f32,
200 i32,
201 u16,
202 f32,
203 u16,
204 u16,
205 bool,
206);
207
208impl ConnectomeManager {
209 fn get_mapping_rules_for_destination<'a>(
210 mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
211 dst_area_id: &CorticalID,
212 ) -> Option<&'a Vec<serde_json::Value>> {
213 if let Some(rules) = mapping_dst
214 .get(&dst_area_id.as_base_64())
215 .and_then(|value| value.as_array())
216 {
217 return Some(rules);
218 }
219
220 for (raw_dst_key, rules_value) in mapping_dst {
223 let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
224 .or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
225 if parsed_dst.as_ref().ok() != Some(dst_area_id) {
226 continue;
227 }
228 if let Some(rules) = rules_value.as_array() {
229 return Some(rules);
230 }
231 }
232
233 None
234 }
235
236 fn new() -> Self {
238 Self {
239 cortical_areas: HashMap::new(),
240 cortical_id_to_idx: HashMap::new(),
241 cortical_idx_to_id: HashMap::new(),
242 next_cortical_idx: 7, brain_regions: BrainRegionHierarchy::new(),
245 classifiers: HashMap::new(),
246 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
247 config: ConnectomeConfig::default(),
248 npu: None,
249 #[cfg(feature = "plasticity")]
250 plasticity_executor: None,
251 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
252 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
253 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
254 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
255 initialized: false,
256 last_fatigue_calculation: Arc::new(Mutex::new(
257 std::time::Instant::now() - std::time::Duration::from_secs(10),
258 )), }
260 }
261
262 pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
279 Arc::clone(&*INSTANCE)
282 }
283
284 pub fn new_for_testing() -> Self {
312 Self {
313 cortical_areas: HashMap::new(),
314 cortical_id_to_idx: HashMap::new(),
315 cortical_idx_to_id: HashMap::new(),
316 next_cortical_idx: 0,
317 brain_regions: BrainRegionHierarchy::new(),
318 classifiers: HashMap::new(),
319 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
320 config: ConnectomeConfig::default(),
321 npu: None,
322 #[cfg(feature = "plasticity")]
323 plasticity_executor: None,
324 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
325 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
326 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
327 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
328 initialized: false,
329 last_fatigue_calculation: Arc::new(Mutex::new(
330 std::time::Instant::now() - std::time::Duration::from_secs(10),
331 )),
332 }
333 }
334
335 pub fn new_for_testing_with_npu(
351 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
352 ) -> Self {
353 Self {
354 cortical_areas: HashMap::new(),
355 cortical_id_to_idx: HashMap::new(),
356 cortical_idx_to_id: HashMap::new(),
357 next_cortical_idx: 7,
358 brain_regions: BrainRegionHierarchy::new(),
359 classifiers: HashMap::new(),
360 morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
361 config: ConnectomeConfig::default(),
362 npu: Some(npu),
363 #[cfg(feature = "plasticity")]
364 plasticity_executor: None,
365 cached_neuron_count: Arc::new(AtomicUsize::new(0)),
366 cached_synapse_count: Arc::new(AtomicUsize::new(0)),
367 cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
368 cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
369 initialized: false,
370 last_fatigue_calculation: Arc::new(Mutex::new(
371 std::time::Instant::now() - std::time::Duration::from_secs(10),
372 )),
373 }
374 }
375
376 pub fn setup_core_morphologies_for_testing(&mut self) {
386 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
387 }
388
389 #[cfg(test)]
398 pub fn reset_for_testing() {
399 let mut instance = INSTANCE.write();
400 *instance = Self::new();
401 }
402
403 fn update_state_hashes(
409 &self,
410 brain_regions: Option<u64>,
411 cortical_areas: Option<u64>,
412 brain_geometry: Option<u64>,
413 morphologies: Option<u64>,
414 cortical_mappings: Option<u64>,
415 ) {
416 let state_manager = StateManager::instance();
417 let state_manager = state_manager.read();
418 if let Some(value) = brain_regions {
419 state_manager.set_brain_regions_hash(value);
420 }
421 if let Some(value) = cortical_areas {
422 state_manager.set_cortical_areas_hash(value);
423 }
424 if let Some(value) = brain_geometry {
425 state_manager.set_brain_geometry_hash(value);
426 }
427 if let Some(value) = morphologies {
428 state_manager.set_morphologies_hash(value);
429 }
430 if let Some(value) = cortical_mappings {
431 state_manager.set_cortical_mappings_hash(value);
432 }
433 }
434
435 fn refresh_brain_regions_hash(&self) {
437 let hash = self.compute_brain_regions_hash();
438 self.update_state_hashes(Some(hash), None, None, None, None);
439 }
440
441 #[allow(dead_code)]
442 fn refresh_cortical_areas_hash(&self) {
444 let hash = self.compute_cortical_areas_hash();
445 self.update_state_hashes(None, Some(hash), None, None, None);
446 }
447
448 #[allow(dead_code)]
449 fn refresh_brain_geometry_hash(&self) {
451 let hash = self.compute_brain_geometry_hash();
452 self.update_state_hashes(None, None, Some(hash), None, None);
453 }
454
455 fn refresh_morphologies_hash(&self) {
457 let hash = self.compute_morphologies_hash();
458 self.update_state_hashes(None, None, None, Some(hash), None);
459 }
460
461 pub fn refresh_cortical_mappings_hash(&self) {
463 let hash = self.compute_cortical_mappings_hash();
464 self.update_state_hashes(None, None, None, None, Some(hash));
465 }
466
467 pub fn refresh_classifiers_hash(&self) {
469 let hash = self.compute_classifiers_hash();
470 let state_manager = StateManager::instance();
471 let state_manager = state_manager.read();
472 state_manager.set_classifiers_hash(hash);
473 }
474
475 pub fn refresh_all_connectome_hashes(&self) {
480 self.update_state_hashes(
481 Some(self.compute_brain_regions_hash()),
482 Some(self.compute_cortical_areas_hash()),
483 Some(self.compute_brain_geometry_hash()),
484 Some(self.compute_morphologies_hash()),
485 Some(self.compute_cortical_mappings_hash()),
486 );
487 self.refresh_classifiers_hash();
488 }
489
490 pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
492 let cortical_hash = if properties_changed {
493 Some(self.compute_cortical_areas_hash())
494 } else {
495 None
496 };
497 let geometry_hash = if geometry_changed {
498 Some(self.compute_brain_geometry_hash())
499 } else {
500 None
501 };
502 self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
503 }
504
505 fn compute_brain_regions_hash(&self) -> u64 {
507 let mut hasher = Xxh64::new(DATA_HASH_SEED);
508 let mut region_ids: Vec<String> = self
509 .brain_regions
510 .get_all_region_ids()
511 .into_iter()
512 .cloned()
513 .collect();
514 region_ids.sort();
515
516 for region_id in region_ids {
517 let Some(region) = self.brain_regions.get_region(®ion_id) else {
518 continue;
519 };
520 Self::hash_str(&mut hasher, ®ion_id);
521 Self::hash_str(&mut hasher, ®ion.name);
522 Self::hash_str(&mut hasher, ®ion.region_type.to_string());
523 let parent_id = self.brain_regions.get_parent(®ion_id);
524 match parent_id {
525 Some(parent) => Self::hash_str(&mut hasher, parent),
526 None => Self::hash_str(&mut hasher, "null"),
527 }
528
529 let mut cortical_ids: Vec<String> = region
530 .cortical_areas
531 .iter()
532 .map(|id| id.as_base_64())
533 .collect();
534 cortical_ids.sort();
535 for cortical_id in cortical_ids {
536 Self::hash_str(&mut hasher, &cortical_id);
537 }
538
539 Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
540 }
541
542 hasher.finish() & HASH_SAFE_MASK
543 }
544
545 fn compute_cortical_areas_hash(&self) -> u64 {
547 let mut hasher = Xxh64::new(DATA_HASH_SEED);
548 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
549 areas.sort_by_key(|area| area.cortical_id.as_base_64());
550
551 for area in areas {
552 let cortical_id = area.cortical_id.as_base_64();
553 Self::hash_str(&mut hasher, &cortical_id);
554 hasher.write_u32(area.cortical_idx);
555 Self::hash_str(&mut hasher, &area.name);
556 Self::hash_str(&mut hasher, &area.cortical_type.to_string());
557
558 let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
559 Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
560 }
561
562 hasher.finish() & HASH_SAFE_MASK
563 }
564
565 fn compute_brain_geometry_hash(&self) -> u64 {
567 let mut hasher = Xxh64::new(DATA_HASH_SEED);
568 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
569 areas.sort_by_key(|area| area.cortical_id.as_base_64());
570
571 for area in areas {
572 let cortical_id = area.cortical_id.as_base_64();
573 Self::hash_str(&mut hasher, &cortical_id);
574
575 Self::hash_i32(&mut hasher, area.position.x);
576 Self::hash_i32(&mut hasher, area.position.y);
577 Self::hash_i32(&mut hasher, area.position.z);
578
579 Self::hash_u32(&mut hasher, area.dimensions.width);
580 Self::hash_u32(&mut hasher, area.dimensions.height);
581 Self::hash_u32(&mut hasher, area.dimensions.depth);
582
583 let coord_2d = area
584 .properties
585 .get("coordinate_2d")
586 .or_else(|| area.properties.get("coordinates_2d"));
587 match coord_2d {
588 Some(value) => Self::hash_json_value(&mut hasher, value),
589 None => Self::hash_str(&mut hasher, "null"),
590 }
591 }
592
593 hasher.finish() & HASH_SAFE_MASK
594 }
595
596 fn compute_morphologies_hash(&self) -> u64 {
598 let mut hasher = Xxh64::new(DATA_HASH_SEED);
599 let mut morphology_ids = self.morphology_registry.morphology_ids();
600 morphology_ids.sort();
601
602 for morphology_id in morphology_ids {
603 if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
604 Self::hash_str(&mut hasher, &morphology_id);
605 Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
606 Self::hash_str(&mut hasher, &morphology.class);
607 if let Ok(value) = serde_json::to_value(&morphology.parameters) {
608 Self::hash_json_value(&mut hasher, &value);
609 }
610 }
611 }
612
613 hasher.finish() & HASH_SAFE_MASK
614 }
615
616 fn compute_cortical_mappings_hash(&self) -> u64 {
618 let mut hasher = Xxh64::new(DATA_HASH_SEED);
619 let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
620 areas.sort_by_key(|area| area.cortical_id.as_base_64());
621
622 for area in areas {
623 let cortical_id = area.cortical_id.as_base_64();
624 Self::hash_str(&mut hasher, &cortical_id);
625 if let Some(serde_json::Value::Object(map)) =
626 area.properties.get("cortical_mapping_dst")
627 {
628 let mut dest_ids: Vec<&String> = map.keys().collect();
629 dest_ids.sort();
630 for dest_id in dest_ids {
631 Self::hash_str(&mut hasher, dest_id);
632 if let Some(value) = map.get(dest_id) {
633 Self::hash_json_value(&mut hasher, value);
634 }
635 }
636 } else {
637 Self::hash_str(&mut hasher, "null");
638 }
639 }
640
641 hasher.finish() & HASH_SAFE_MASK
642 }
643
644 fn compute_classifiers_hash(&self) -> u64 {
646 let mut hasher = Xxh64::new(DATA_HASH_SEED);
647 let mut classifier_ids: Vec<String> = self.classifiers.keys().cloned().collect();
648 classifier_ids.sort();
649 for classifier_id in classifier_ids {
650 let Some(classifier) = self.classifiers.get(&classifier_id) else {
651 continue;
652 };
653 Self::hash_str(&mut hasher, &classifier.classifier_id);
654 Self::hash_str(&mut hasher, &classifier.name);
655 Self::hash_str(&mut hasher, &classifier.parent_region_id);
656 Self::hash_i32(&mut hasher, classifier.coordinates_3d[0]);
657 Self::hash_i32(&mut hasher, classifier.coordinates_3d[1]);
658 Self::hash_i32(&mut hasher, classifier.coordinates_3d[2]);
659 Self::hash_str(&mut hasher, classifier.training_mode.as_str());
660 match &classifier.kernel_area_id {
661 Some(area_id) => Self::hash_str(&mut hasher, area_id),
662 None => Self::hash_str(&mut hasher, "null"),
663 }
664 match &classifier.class_area_id {
665 Some(area_id) => Self::hash_str(&mut hasher, area_id),
666 None => Self::hash_str(&mut hasher, "null"),
667 }
668 match &classifier.mask_area_id {
669 Some(area_id) => Self::hash_str(&mut hasher, area_id),
670 None => Self::hash_str(&mut hasher, "null"),
671 }
672 match classifier.kernel_size {
673 Some([x, y, z]) => {
674 Self::hash_u32(&mut hasher, x);
675 Self::hash_u32(&mut hasher, y);
676 Self::hash_u32(&mut hasher, z);
677 }
678 None => Self::hash_str(&mut hasher, "null"),
679 }
680 for field in &classifier.fields {
681 Self::hash_str(&mut hasher, &field.field_area_id);
682 Self::hash_str(&mut hasher, &field.scan_twin_id);
683 }
684 Self::hash_str(&mut hasher, &classifier.kernel_memory_id);
685 Self::hash_str(&mut hasher, &classifier.class_memory_id);
686 Self::hash_properties_filtered(&mut hasher, &classifier.properties, &[]);
687 }
688 hasher.finish() & HASH_SAFE_MASK
689 }
690
691 fn hash_str(hasher: &mut Xxh64, value: &str) {
693 hasher.write(value.as_bytes());
694 hasher.write_u8(0);
695 }
696
697 fn hash_i32(hasher: &mut Xxh64, value: i32) {
699 hasher.write(&value.to_le_bytes());
700 }
701
702 fn hash_u32(hasher: &mut Xxh64, value: u32) {
704 hasher.write(&value.to_le_bytes());
705 }
706
707 fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
709 match value {
710 serde_json::Value::Null => {
711 hasher.write_u8(0);
712 }
713 serde_json::Value::Bool(val) => {
714 hasher.write_u8(1);
715 hasher.write_u8(*val as u8);
716 }
717 serde_json::Value::Number(num) => {
718 hasher.write_u8(2);
719 Self::hash_str(hasher, &num.to_string());
720 }
721 serde_json::Value::String(val) => {
722 hasher.write_u8(3);
723 Self::hash_str(hasher, val);
724 }
725 serde_json::Value::Array(items) => {
726 hasher.write_u8(4);
727 for item in items {
728 Self::hash_json_value(hasher, item);
729 }
730 }
731 serde_json::Value::Object(map) => {
732 hasher.write_u8(5);
733 let mut keys: Vec<&String> = map.keys().collect();
734 keys.sort();
735 for key in keys {
736 Self::hash_str(hasher, key);
737 if let Some(val) = map.get(key) {
738 Self::hash_json_value(hasher, val);
739 }
740 }
741 }
742 }
743 }
744
745 fn hash_properties_filtered(
747 hasher: &mut Xxh64,
748 properties: &HashMap<String, serde_json::Value>,
749 excluded_keys: &[&str],
750 ) {
751 let mut keys: Vec<&String> = properties.keys().collect();
752 keys.sort();
753 for key in keys {
754 if excluded_keys.contains(&key.as_str()) {
755 continue;
756 }
757 Self::hash_str(hasher, key);
758 if let Some(value) = properties.get(key) {
759 Self::hash_json_value(hasher, value);
760 }
761 }
762 }
763
764 pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
785 if self.cortical_areas.contains_key(&area.cortical_id) {
787 return Err(BduError::InvalidArea(format!(
788 "Cortical area {} already exists",
789 area.cortical_id
790 )));
791 }
792
793 use feagi_structures::genomic::cortical_area::CoreCorticalType;
796
797 let death_id = CoreCorticalType::Death.to_cortical_id();
798 let power_id = CoreCorticalType::Power.to_cortical_id();
799 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
800 let pain_id = CoreCorticalType::Pain.to_cortical_id();
801 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
802 let fear_id = CoreCorticalType::Fear.to_cortical_id();
803 let hope_id = CoreCorticalType::Hope.to_cortical_id();
804
805 let is_death_area = area.cortical_id == death_id;
806 let is_power_area = area.cortical_id == power_id;
807 let is_fatigue_area = area.cortical_id == fatigue_id;
808 let is_pain_area = area.cortical_id == pain_id;
809 let is_pleasure_area = area.cortical_id == pleasure_id;
810 let is_fear_area = area.cortical_id == fear_id;
811 let is_hope_area = area.cortical_id == hope_id;
812
813 if is_death_area {
814 trace!(
815 target: "feagi-bdu",
816 "[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
817 area.cortical_id
818 );
819 area.cortical_idx = 0;
820 } else if is_power_area {
821 trace!(
822 target: "feagi-bdu",
823 "[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
824 area.cortical_id
825 );
826 area.cortical_idx = 1;
827 } else if is_fatigue_area {
828 trace!(
829 target: "feagi-bdu",
830 "[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
831 area.cortical_id
832 );
833 area.cortical_idx = 2;
834 } else if is_pain_area {
835 trace!(
836 target: "feagi-bdu",
837 "[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
838 area.cortical_id
839 );
840 area.cortical_idx = 3;
841 } else if is_pleasure_area {
842 trace!(
843 target: "feagi-bdu",
844 "[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
845 area.cortical_id
846 );
847 area.cortical_idx = 4;
848 } else if is_fear_area {
849 trace!(
850 target: "feagi-bdu",
851 "[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
852 area.cortical_id
853 );
854 area.cortical_idx = 5;
855 } else if is_hope_area {
856 trace!(
857 target: "feagi-bdu",
858 "[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
859 area.cortical_id
860 );
861 area.cortical_idx = 6;
862 } else {
863 if area.cortical_idx == 0 {
865 area.cortical_idx = self.next_cortical_idx;
866 self.next_cortical_idx += 1;
867 trace!(
868 target: "feagi-bdu",
869 "[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
870 area.cortical_idx,
871 area.cortical_id.as_base_64()
872 );
873 } else {
874 if area.cortical_idx <= 6 {
876 warn!(
877 "Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
878 area.cortical_id, area.cortical_idx);
879 area.cortical_idx = self.next_cortical_idx;
880 self.next_cortical_idx += 1;
881 info!(
882 " Reassigned '{}' to cortical_idx={}",
883 area.cortical_id, area.cortical_idx
884 );
885 } else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
886 return Err(BduError::InvalidArea(format!(
887 "Cortical index {} is already in use",
888 area.cortical_idx
889 )));
890 }
891
892 if area.cortical_idx >= self.next_cortical_idx {
894 self.next_cortical_idx = area.cortical_idx + 1;
895 }
896 }
897 }
898
899 let cortical_id = area.cortical_id;
900 let cortical_idx = area.cortical_idx;
901
902 self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
904 self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
905
906 area.properties
908 .insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
909
910 let parent_region_id = area
919 .properties
920 .get("parent_region_id")
921 .and_then(|v| v.as_str())
922 .map(|s| s.to_string());
923
924 self.cortical_areas.insert(cortical_id, area);
926
927 if let Some(region_id) = parent_region_id {
929 let region = self
930 .brain_regions
931 .get_region_mut(®ion_id)
932 .ok_or_else(|| {
933 BduError::InvalidArea(format!(
934 "Unknown parent_region_id '{}' for cortical area {}",
935 region_id,
936 cortical_id.as_base_64()
937 ))
938 })?;
939 region.add_area(cortical_id);
940 }
941
942 {
945 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
946 neuron_cache.insert(cortical_id, AtomicUsize::new(0));
947 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
948 synapse_cache.insert(cortical_id, AtomicUsize::new(0));
949 }
950 let state_manager = StateManager::instance();
952 let state_manager = state_manager.read();
953 state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
954
955 if let Some(ref npu) = self.npu {
959 trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
960 let mut npu_lock = npu.lock().map_err(|e| {
961 BduError::Internal(format!(
962 "Failed to lock NPU for cortical area registration: {}",
963 e
964 ))
965 })?;
966 npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
967 trace!(
968 target: "feagi-bdu",
969 "Registered cortical area idx={} -> '{}' in NPU",
970 cortical_idx,
971 cortical_id.as_base_64()
972 );
973 }
974
975 self.sync_cortical_area_flags_to_npu()?;
977
978 self.initialized = true;
979
980 self.refresh_cortical_area_hashes(true, true);
981 self.refresh_brain_regions_hash();
982
983 Ok(cortical_idx)
984 }
985
986 pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
1001 let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
1002 BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
1003 })?;
1004
1005 #[cfg(feature = "plasticity")]
1006 if let Some(executor) = self.plasticity_executor.as_ref() {
1007 let exec = match executor.lock() {
1008 Ok(exec) => exec,
1009 Err(_) => {
1010 self.cortical_areas.insert(*cortical_id, area);
1011 return Err(BduError::Internal(format!(
1012 "Failed to lock PlasticityExecutor while removing cortical area {}",
1013 cortical_id
1014 )));
1015 }
1016 };
1017 exec.unregister_memory_area(area.cortical_idx);
1018 }
1019
1020 self.cortical_id_to_idx.remove(cortical_id);
1022 self.cortical_idx_to_id.remove(&area.cortical_idx);
1023
1024 self.refresh_cortical_area_hashes(true, true);
1025 Ok(())
1026 }
1027
1028 pub fn rename_cortical_area_id(
1032 &mut self,
1033 old_id: &CorticalID,
1034 new_id: CorticalID,
1035 new_cortical_type: CorticalAreaType,
1036 ) -> BduResult<()> {
1037 self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
1038 }
1039
1040 pub fn rename_cortical_area_id_with_options(
1042 &mut self,
1043 old_id: &CorticalID,
1044 new_id: CorticalID,
1045 new_cortical_type: CorticalAreaType,
1046 update_npu_registry: bool,
1047 ) -> BduResult<()> {
1048 if !self.cortical_areas.contains_key(old_id) {
1049 return Err(BduError::InvalidArea(format!(
1050 "Cortical area {} does not exist",
1051 old_id
1052 )));
1053 }
1054 if self.cortical_areas.contains_key(&new_id) {
1055 return Err(BduError::InvalidArea(format!(
1056 "Cortical area {} already exists",
1057 new_id
1058 )));
1059 }
1060
1061 let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
1062 BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
1063 })?;
1064 let cortical_idx = area.cortical_idx;
1065 area.cortical_id = new_id;
1066 area.cortical_type = new_cortical_type;
1067
1068 self.cortical_areas.insert(new_id, area);
1069 self.cortical_id_to_idx.remove(old_id);
1070 self.cortical_id_to_idx.insert(new_id, cortical_idx);
1071 self.cortical_idx_to_id.insert(cortical_idx, new_id);
1072
1073 {
1075 let mut neuron_cache = self.cached_neuron_counts_per_area.write();
1076 if let Some(value) = neuron_cache.remove(old_id) {
1077 neuron_cache.insert(new_id, value);
1078 }
1079 let mut synapse_cache = self.cached_synapse_counts_per_area.write();
1080 if let Some(value) = synapse_cache.remove(old_id) {
1081 synapse_cache.insert(new_id, value);
1082 }
1083 }
1084
1085 self.brain_regions.rename_cortical_area_id(old_id, new_id);
1087
1088 let old_id_str = old_id.as_base_64();
1090 let new_id_str = new_id.as_base_64();
1091 for area in self.cortical_areas.values_mut() {
1092 if let Some(mapping) = area
1093 .properties
1094 .get_mut("cortical_mapping_dst")
1095 .and_then(|v| v.as_object_mut())
1096 {
1097 if let Some(value) = mapping.remove(&old_id_str) {
1098 mapping.insert(new_id_str.clone(), value);
1099 }
1100 }
1101 }
1102
1103 if update_npu_registry {
1105 if let Some(ref npu) = self.npu {
1106 if let Ok(mut npu_lock) = npu.lock() {
1107 npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
1108 }
1109 }
1110 }
1111
1112 self.refresh_cortical_area_hashes(true, true);
1113 self.refresh_brain_regions_hash();
1114 self.refresh_cortical_mappings_hash();
1115
1116 Ok(())
1117 }
1118
1119 pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
1121 self.cortical_areas.get(cortical_id)
1122 }
1123
1124 pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
1126 self.cortical_areas.get_mut(cortical_id)
1127 }
1128
1129 pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
1131 self.cortical_id_to_idx.get(cortical_id).copied()
1132 }
1133
1134 pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
1146 self.brain_regions.find_region_containing_area(cortical_id)
1147 }
1148
1149 pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
1152 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1153 return false;
1154 };
1155 let Some(src_area) = self.cortical_areas.get(area) else {
1156 return false;
1157 };
1158 let Some(dst_obj) = src_area
1159 .properties
1160 .get("cortical_mapping_dst")
1161 .and_then(|v| v.as_object())
1162 else {
1163 return false;
1164 };
1165 for dst_key in dst_obj.keys() {
1166 let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
1167 continue;
1168 };
1169 match self.brain_regions.find_region_containing_area(&dst_id) {
1170 None => return true,
1171 Some(rid) if rid != my_region => return true,
1172 _ => {}
1173 }
1174 }
1175 false
1176 }
1177
1178 pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
1180 let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
1181 return false;
1182 };
1183 let my_b64 = area.as_base_64();
1184 for (src_id, src_area) in &self.cortical_areas {
1185 if src_id == area {
1186 continue;
1187 }
1188 let Some(dst_map) = src_area
1189 .properties
1190 .get("cortical_mapping_dst")
1191 .and_then(|v| v.as_object())
1192 else {
1193 continue;
1194 };
1195 if !dst_map.contains_key(&my_b64) {
1196 continue;
1197 }
1198 match self.brain_regions.find_region_containing_area(src_id) {
1199 None => return true,
1200 Some(rid) if rid != my_region => return true,
1201 _ => {}
1202 }
1203 }
1204 false
1205 }
1206
1207 pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
1218 use std::collections::HashSet;
1219
1220 let region_ids: Vec<String> = self
1221 .brain_regions
1222 .get_all_region_ids()
1223 .into_iter()
1224 .cloned()
1225 .collect();
1226
1227 let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1228 let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
1229
1230 for rid in ®ion_ids {
1232 inputs_by_region.insert(rid.clone(), HashSet::new());
1233 outputs_by_region.insert(rid.clone(), HashSet::new());
1234 }
1235
1236 for (src_id, src_area) in &self.cortical_areas {
1237 let Some(dstmap) = src_area
1238 .properties
1239 .get("cortical_mapping_dst")
1240 .and_then(|v| v.as_object())
1241 else {
1242 continue;
1243 };
1244
1245 let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
1246 debug!(
1247 target: "feagi-bdu",
1248 "Skipping region IO for source area {} (not in any region)",
1249 src_id.as_base_64()
1250 );
1251 continue;
1252 };
1253
1254 for dst_id_str in dstmap.keys() {
1255 let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
1256 BduError::InvalidArea(format!(
1257 "Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
1258 dst_id_str,
1259 src_id.as_base_64(),
1260 e
1261 ))
1262 })?;
1263
1264 let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
1265 else {
1266 warn!(
1267 target: "feagi-bdu",
1268 "Skipping region IO for destination area {} (not in any region)",
1269 dst_id.as_base_64()
1270 );
1271 continue;
1272 };
1273
1274 if src_region_id == dst_region_id {
1275 continue;
1276 }
1277 if self.mapping_is_classifier_assembly_edge(src_id, &dst_id) {
1278 continue;
1279 }
1280
1281 outputs_by_region
1282 .entry(src_region_id.clone())
1283 .or_default()
1284 .insert(src_id.as_base_64());
1285 inputs_by_region
1286 .entry(dst_region_id.clone())
1287 .or_default()
1288 .insert(dst_id.as_base_64());
1289 }
1290 }
1291
1292 for rid in ®ion_ids {
1294 let Some(region) = self.brain_regions.get_region(rid) else {
1295 continue;
1296 };
1297 let in_ids = crate::region_io_designation::parse_designated_id_list(
1298 region
1299 .properties
1300 .get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
1301 )?;
1302 let out_ids = crate::region_io_designation::parse_designated_id_list(
1303 region
1304 .properties
1305 .get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
1306 )?;
1307 for id in in_ids {
1308 inputs_by_region
1309 .entry(rid.clone())
1310 .or_default()
1311 .insert(id.as_base_64());
1312 }
1313 for id in out_ids {
1314 outputs_by_region
1315 .entry(rid.clone())
1316 .or_default()
1317 .insert(id.as_base_64());
1318 }
1319 }
1320
1321 let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
1322 for rid in region_ids {
1323 let mut inputs: Vec<String> = inputs_by_region
1324 .remove(&rid)
1325 .unwrap_or_default()
1326 .into_iter()
1327 .collect();
1328 let mut outputs: Vec<String> = outputs_by_region
1329 .remove(&rid)
1330 .unwrap_or_default()
1331 .into_iter()
1332 .collect();
1333
1334 inputs.sort();
1335 outputs.sort();
1336
1337 let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
1338 BduError::InvalidArea(format!(
1339 "Unable to recompute region IO: region '{}' not found in hierarchy",
1340 rid
1341 ))
1342 })?;
1343
1344 if inputs.is_empty() {
1345 region.properties.remove("inputs");
1346 } else {
1347 region
1348 .properties
1349 .insert("inputs".to_string(), serde_json::json!(inputs.clone()));
1350 }
1351
1352 if outputs.is_empty() {
1353 region.properties.remove("outputs");
1354 } else {
1355 region
1356 .properties
1357 .insert("outputs".to_string(), serde_json::json!(outputs.clone()));
1358 }
1359
1360 computed.insert(rid, (inputs, outputs));
1361 }
1362
1363 self.refresh_brain_regions_hash();
1364
1365 Ok(computed)
1366 }
1367
1368 pub fn get_root_region_id(&self) -> Option<String> {
1374 self.brain_regions.get_root_region_id()
1375 }
1376
1377 pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
1379 self.cortical_idx_to_id.get(&cortical_idx)
1380 }
1381
1382 pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
1385 self.cortical_idx_to_id
1386 .iter()
1387 .map(|(idx, id)| (*idx, id.as_base_64()))
1388 .collect()
1389 }
1390
1391 pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
1396 let mut granularities = ahash::AHashMap::new();
1397 for (cortical_id, area) in &self.cortical_areas {
1398 let cortical_idx = self
1399 .cortical_id_to_idx
1400 .get(cortical_id)
1401 .copied()
1402 .unwrap_or(0);
1403
1404 if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
1407 if let Some(arr) = granularity_json.as_array() {
1408 if arr.len() == 3 {
1409 let x_opt = arr[0]
1410 .as_u64()
1411 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1412 let y_opt = arr[1]
1413 .as_u64()
1414 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1415 let z_opt = arr[2]
1416 .as_u64()
1417 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1418
1419 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1420 let granularity = (x as u32, y as u32, z as u32);
1421 if granularity != (1, 1, 1) {
1423 granularities.insert(cortical_idx, granularity);
1424 }
1425 }
1426 }
1427 }
1428 }
1429 }
1430 granularities
1431 }
1432
1433 pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
1435 self.cortical_areas.keys().collect()
1436 }
1437
1438 pub fn get_cortical_area_count(&self) -> usize {
1440 self.cortical_areas.len()
1441 }
1442
1443 pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
1457 if let Some(area) = self.cortical_areas.get(target_cortical_id) {
1458 if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
1459 if let Some(upstream_array) = upstream_prop.as_array() {
1460 return upstream_array
1461 .iter()
1462 .filter_map(|v| v.as_u64().map(|n| n as u32))
1463 .collect();
1464 }
1465 }
1466
1467 warn!(target: "feagi-bdu",
1469 "Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
1470 target_cortical_id.as_base_64()
1471 );
1472 }
1473
1474 Vec::new()
1475 }
1476
1477 pub fn get_episodic_memory_upstream_cortical_areas(
1483 &self,
1484 target_cortical_id: &CorticalID,
1485 ) -> Vec<u32> {
1486 let mut episodic_upstream = Vec::new();
1487 for (src_id, src_area) in &self.cortical_areas {
1488 if src_id == target_cortical_id {
1489 continue;
1490 }
1491 let Some(mapping_dst) = src_area
1492 .properties
1493 .get("cortical_mapping_dst")
1494 .and_then(|v| v.as_object())
1495 else {
1496 continue;
1497 };
1498 let Some(rules) =
1499 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1500 else {
1501 continue;
1502 };
1503 if !Self::mapping_has_morphology_rule(rules, "episodic_memory") {
1504 continue;
1505 }
1506 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1507 continue;
1508 };
1509 episodic_upstream.push(src_idx);
1510 }
1511
1512 episodic_upstream.sort_unstable();
1513 episodic_upstream.dedup();
1514 episodic_upstream
1515 }
1516
1517 fn mapping_has_morphology_rule(rules: &[serde_json::Value], morphology: &str) -> bool {
1518 rules.iter().any(|rule| {
1519 let morphology_id = rule
1520 .as_object()
1521 .and_then(|obj| obj.get("morphology_id"))
1522 .and_then(|v| v.as_str())
1523 .or_else(|| {
1524 rule.as_array()
1525 .and_then(|arr| arr.first())
1526 .and_then(|v| v.as_str())
1527 });
1528 morphology_id == Some(morphology)
1529 })
1530 }
1531
1532 fn area_belongs_to_classifier_assembly(area: &CorticalArea) -> bool {
1534 if area
1535 .properties
1536 .get("classifier_assembly")
1537 .and_then(|value| value.as_bool())
1538 == Some(true)
1539 {
1540 return true;
1541 }
1542 matches!(
1543 area.properties
1544 .get("classifier_role")
1545 .and_then(|value| value.as_str()),
1546 Some("kernel_memory") | Some("class_memory") | Some("scan_twin")
1547 )
1548 }
1549
1550 fn mapping_is_classifier_assembly_edge(
1552 &self,
1553 src_id: &CorticalID,
1554 dst_id: &CorticalID,
1555 ) -> bool {
1556 let src_is_classifier = self
1557 .cortical_areas
1558 .get(src_id)
1559 .is_some_and(Self::area_belongs_to_classifier_assembly);
1560 let dst_is_classifier = self
1561 .cortical_areas
1562 .get(dst_id)
1563 .is_some_and(Self::area_belongs_to_classifier_assembly);
1564 if src_is_classifier || dst_is_classifier {
1565 return true;
1566 }
1567 let src_key = src_id.as_base_64();
1568 let dst_key = dst_id.as_base_64();
1569 self.classifiers.values().any(|classifier| {
1570 classifier.owns_area(&src_key)
1571 || classifier.owns_area(&dst_key)
1572 || (classifier.references_input(&src_key) && classifier.owns_area(&dst_key))
1573 })
1574 }
1575
1576 pub fn get_episodic_scan_upstream_cortical_areas(
1578 &self,
1579 target_cortical_id: &CorticalID,
1580 ) -> Vec<u32> {
1581 let mut scan_upstream = Vec::new();
1582 for (src_id, src_area) in &self.cortical_areas {
1583 if src_id == target_cortical_id {
1584 continue;
1585 }
1586 let Some(mapping_dst) = src_area
1587 .properties
1588 .get("cortical_mapping_dst")
1589 .and_then(|v| v.as_object())
1590 else {
1591 continue;
1592 };
1593 let Some(rules) =
1594 Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
1595 else {
1596 continue;
1597 };
1598 if !Self::mapping_has_morphology_rule(rules, "episodic_scan") {
1599 continue;
1600 }
1601 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
1602 continue;
1603 };
1604 scan_upstream.push(src_idx);
1605 }
1606 scan_upstream.sort_unstable();
1607 scan_upstream.dedup();
1608 scan_upstream
1609 }
1610
1611 fn mapping_from_src_to_dst_has_scan(
1612 &self,
1613 src_area_id: &CorticalID,
1614 dst_area_id: &CorticalID,
1615 ) -> bool {
1616 let Some(src_area) = self.cortical_areas.get(src_area_id) else {
1617 return false;
1618 };
1619 let Some(mapping_dst) = src_area
1620 .properties
1621 .get("cortical_mapping_dst")
1622 .and_then(|v| v.as_object())
1623 else {
1624 return false;
1625 };
1626 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
1627 return false;
1628 };
1629 Self::mapping_has_morphology_rule(rules, "episodic_scan")
1630 }
1631
1632 pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
1634 upstream
1635 .iter()
1636 .filter_map(|idx| {
1637 let cortical_id = self.cortical_idx_to_id.get(idx)?;
1638 let area = self.cortical_areas.get(cortical_id)?;
1639 if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
1640 None
1641 } else {
1642 Some(*idx)
1643 }
1644 })
1645 .collect()
1646 }
1647
1648 pub fn refresh_upstream_cortical_areas_from_mappings(
1652 &mut self,
1653 target_cortical_id: &CorticalID,
1654 ) -> Vec<u32> {
1655 use std::collections::HashSet;
1656 let target_id_str = target_cortical_id.as_base_64();
1657 let mut upstream_idxs = HashSet::new();
1658 for (src_id, src_area) in &self.cortical_areas {
1659 if src_id == target_cortical_id {
1660 continue;
1661 }
1662 if let Some(mapping) = src_area
1663 .properties
1664 .get("cortical_mapping_dst")
1665 .and_then(|v| v.as_object())
1666 {
1667 if mapping.contains_key(&target_id_str) {
1668 if let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) {
1669 upstream_idxs.insert(src_idx);
1670 }
1671 }
1672 }
1673 }
1674
1675 let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
1676 upstream_list.sort_unstable();
1677
1678 if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
1679 target_area.properties.insert(
1680 "upstream_cortical_areas".to_string(),
1681 serde_json::json!(upstream_list),
1682 );
1683 }
1684
1685 self.get_upstream_cortical_areas(target_cortical_id)
1686 }
1687
1688 pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
1698 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
1699 let upstream_array = area
1700 .properties
1701 .entry("upstream_cortical_areas".to_string())
1702 .or_insert_with(|| serde_json::json!([]));
1703
1704 if let Some(arr) = upstream_array.as_array_mut() {
1705 let src_value = serde_json::json!(src_cortical_idx);
1706 if !arr.contains(&src_value) {
1707 arr.push(src_value);
1708 debug!(target: "feagi-bdu",
1709 "Added upstream area idx={} to cortical area '{}'",
1710 src_cortical_idx, target_cortical_id.as_base_64()
1711 );
1712 }
1713 }
1714 }
1715 }
1716
1717 pub fn get_memory_twin_for_upstream_idx(
1719 &self,
1720 memory_area_idx: u32,
1721 upstream_idx: u32,
1722 ) -> Option<CorticalID> {
1723 let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
1724 let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
1725 let area = self.cortical_areas.get(memory_id)?;
1726 let mapping = area
1727 .properties
1728 .get("memory_twin_areas")
1729 .and_then(|v| v.as_object())?;
1730 let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
1731 CorticalID::try_from_base_64(twin_b64).ok()
1732 }
1733
1734 pub fn diagnose_memory_twin_for_mapping(
1741 &self,
1742 src_area_id: &CorticalID,
1743 dst_area_id: &CorticalID,
1744 ) -> BduResult<MemoryTwinDiagnostic> {
1745 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
1746 BduError::InvalidArea(format!(
1747 "Source area not found: {}",
1748 src_area_id.as_base_64()
1749 ))
1750 })?;
1751 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
1752 BduError::InvalidArea(format!(
1753 "Destination area not found: {}",
1754 dst_area_id.as_base_64()
1755 ))
1756 })?;
1757
1758 let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
1759 let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
1760
1761 let rules_opt = src_area
1762 .properties
1763 .get("cortical_mapping_dst")
1764 .and_then(|v| v.as_object())
1765 .and_then(|mapping_dst| {
1766 Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
1767 });
1768 let mapping_exists = rules_opt.is_some();
1769 let episodic_rule_count = rules_opt
1770 .map(|rules| {
1771 rules
1772 .iter()
1773 .filter(|rule| {
1774 let morphology_id = rule
1775 .as_object()
1776 .and_then(|obj| obj.get("morphology_id"))
1777 .and_then(|v| v.as_str())
1778 .or_else(|| {
1779 rule.as_array()
1780 .and_then(|arr| arr.first())
1781 .and_then(|v| v.as_str())
1782 });
1783 morphology_id == Some("episodic_memory")
1784 })
1785 .count()
1786 })
1787 .unwrap_or(0);
1788
1789 let twin_expected =
1790 mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
1791 let twin_cortical_area = dst_area
1792 .properties
1793 .get("memory_twin_areas")
1794 .and_then(|v| v.as_object())
1795 .and_then(|map| map.get(&src_area_id.as_base_64()))
1796 .and_then(|v| v.as_str())
1797 .map(ToString::to_string);
1798 let twin_present = twin_cortical_area.is_some();
1799
1800 let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
1801 CorticalID::try_from_base_64(twin_b64)
1802 .ok()
1803 .and_then(|twin_id| {
1804 self.cortical_areas.get(&twin_id).and_then(|area| {
1805 area.properties
1806 .get("parent_region_id")
1807 .and_then(|v| v.as_str())
1808 .map(ToString::to_string)
1809 })
1810 })
1811 });
1812 let src_parent_region_id = src_area
1813 .properties
1814 .get("parent_region_id")
1815 .and_then(|v| v.as_str())
1816 .map(ToString::to_string);
1817 let dst_parent_region_id = dst_area
1818 .properties
1819 .get("parent_region_id")
1820 .and_then(|v| v.as_str())
1821 .map(ToString::to_string);
1822 let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
1823 (Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
1824 (None, None) if twin_present => Some(true),
1825 _ if twin_present => Some(false),
1826 _ => None,
1827 };
1828
1829 let reason = if !mapping_exists {
1830 Some("no_mapping_rule_between_src_and_dst".to_string())
1831 } else if episodic_rule_count == 0 {
1832 Some("mapping_exists_but_not_episodic_memory".to_string())
1833 } else if !dst_is_memory {
1834 Some("destination_is_not_memory_area".to_string())
1835 } else if src_is_memory {
1836 Some("upstream_is_memory_area_twin_not_supported".to_string())
1837 } else if !twin_present {
1838 Some("twin_expected_but_missing".to_string())
1839 } else if twin_parent_matches_src_parent == Some(false) {
1840 Some("twin_parent_region_mismatch_with_source".to_string())
1841 } else {
1842 None
1843 };
1844
1845 Ok(MemoryTwinDiagnostic {
1846 src_cortical_area: src_area_id.as_base_64(),
1847 dst_cortical_area: dst_area_id.as_base_64(),
1848 src_cortical_type: src_area.cortical_type.to_string(),
1849 dst_cortical_type: dst_area.cortical_type.to_string(),
1850 mapping_exists,
1851 episodic_rule_count,
1852 twin_expected,
1853 twin_present,
1854 twin_cortical_area,
1855 reason,
1856 src_parent_region_id,
1857 dst_parent_region_id,
1858 twin_parent_region_id,
1859 twin_parent_matches_src_parent,
1860 })
1861 }
1862
1863 pub fn ensure_memory_twin_area(
1865 &mut self,
1866 memory_area_id: &CorticalID,
1867 upstream_area_id: &CorticalID,
1868 ) -> BduResult<CorticalID> {
1869 use crate::models::CorticalAreaExt;
1870
1871 let register_replay_mapping = |manager: &mut ConnectomeManager,
1872 twin_id: &CorticalID|
1873 -> BduResult<()> {
1874 let Some(npu) = manager.npu.as_ref() else {
1875 return Ok(());
1876 };
1877 let memory_area_idx =
1878 *manager
1879 .cortical_id_to_idx
1880 .get(memory_area_id)
1881 .ok_or_else(|| {
1882 BduError::InvalidArea(format!(
1883 "Memory area idx missing for {}",
1884 memory_area_id.as_base_64()
1885 ))
1886 })?;
1887 let upstream_area_idx = *manager
1888 .cortical_id_to_idx
1889 .get(upstream_area_id)
1890 .ok_or_else(|| {
1891 BduError::InvalidArea(format!(
1892 "Upstream area idx missing for {}",
1893 upstream_area_id.as_base_64()
1894 ))
1895 })?;
1896 let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
1897 BduError::InvalidArea(format!(
1898 "Twin area idx missing for {}",
1899 twin_id.as_base_64()
1900 ))
1901 })?;
1902 let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
1903 BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
1904 })?;
1905 let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
1906 if let Ok(mut npu_lock) = npu.lock() {
1907 npu_lock.register_memory_twin_mapping(
1908 memory_area_idx,
1909 upstream_area_idx,
1910 twin_area_idx,
1911 potential,
1912 );
1913 }
1914 Ok(())
1915 };
1916
1917 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
1918 BduError::InvalidArea(format!(
1919 "Memory area {} not found",
1920 memory_area_id.as_base_64()
1921 ))
1922 })?;
1923 let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
1924 BduError::InvalidArea(format!(
1925 "Upstream area {} not found",
1926 upstream_area_id.as_base_64()
1927 ))
1928 })?;
1929
1930 if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
1931 return Err(BduError::InvalidArea(format!(
1932 "Upstream area {} is memory type; twin creation is only for non-memory areas",
1933 upstream_area_id.as_base_64()
1934 )));
1935 }
1936
1937 if let Some(existing) = memory_area
1938 .properties
1939 .get("memory_twin_areas")
1940 .and_then(|v| v.as_object())
1941 .and_then(|map| map.get(&upstream_area_id.as_base_64()))
1942 .and_then(|v| v.as_str())
1943 .and_then(|s| CorticalID::try_from_base_64(s).ok())
1944 {
1945 self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
1946 register_replay_mapping(self, &existing)?;
1947 self.refresh_cortical_mappings_hash();
1948 return Ok(existing);
1949 }
1950
1951 let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
1952 if self.cortical_areas.contains_key(&twin_id) {
1953 if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
1954 let expected_source = upstream_area_id.as_base_64();
1955 let expected_target = memory_area_id.as_base_64();
1956 let existing_source = existing
1957 .properties
1958 .get("memory_twin_of")
1959 .and_then(|v| v.as_str());
1960 let existing_target = existing
1961 .properties
1962 .get("memory_twin_for")
1963 .and_then(|v| v.as_str());
1964 if existing_source != Some(expected_source.as_str())
1965 || existing_target != Some(expected_target.as_str())
1966 {
1967 warn!(
1968 target: "feagi-bdu",
1969 "Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
1970 upstream_area_id.as_base_64(),
1971 memory_area_id.as_base_64()
1972 );
1973 existing.properties.insert(
1974 "memory_twin_of".to_string(),
1975 serde_json::json!(expected_source),
1976 );
1977 existing.properties.insert(
1978 "memory_twin_for".to_string(),
1979 serde_json::json!(expected_target),
1980 );
1981 }
1982 }
1983 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
1984 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
1985 register_replay_mapping(self, &twin_id)?;
1986 self.refresh_cortical_mappings_hash();
1987 return Ok(twin_id);
1988 }
1989
1990 let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
1991 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
1992 let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
1993 let mut twin_area = CorticalArea::new(
1994 twin_id,
1995 0,
1996 twin_name,
1997 upstream_area.dimensions,
1998 twin_position,
1999 twin_type,
2000 )?;
2001 twin_area.properties = self.build_memory_twin_properties(
2002 memory_area,
2003 upstream_area,
2004 memory_area_id,
2005 upstream_area_id,
2006 );
2007
2008 let _twin_idx = self.add_cortical_area(twin_area)?;
2009 let _ = self.create_neurons_for_area(&twin_id);
2010
2011 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
2012 self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
2013 register_replay_mapping(self, &twin_id)?;
2014 self.refresh_cortical_mappings_hash();
2015 Ok(twin_id)
2016 }
2017
2018 pub fn ensure_scan_twin_area(
2020 &mut self,
2021 memory_area_id: &CorticalID,
2022 field_area_id: &CorticalID,
2023 ) -> BduResult<CorticalID> {
2024 let class_channel_count = self.classifier_class_channel_count(memory_area_id)?;
2025 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
2026 BduError::InvalidArea(format!(
2027 "Memory area {} not found",
2028 memory_area_id.as_base_64()
2029 ))
2030 })?;
2031 let field_area = self.cortical_areas.get(field_area_id).ok_or_else(|| {
2032 BduError::InvalidArea(format!(
2033 "Scan field area {} not found",
2034 field_area_id.as_base_64()
2035 ))
2036 })?;
2037 if matches!(field_area.cortical_type, CorticalAreaType::Memory(_)) {
2038 return Err(BduError::InvalidArea(format!(
2039 "Scan field {} is memory type; scan twins are only for non-memory fields",
2040 field_area_id.as_base_64()
2041 )));
2042 }
2043
2044 if let Some(existing) = memory_area
2045 .properties
2046 .get("memory_twin_areas")
2047 .and_then(|v| v.as_object())
2048 .and_then(|map| map.get(&field_area_id.as_base_64()))
2049 .and_then(|v| v.as_str())
2050 .and_then(|s| CorticalID::try_from_base_64(s).ok())
2051 {
2052 self.refresh_cortical_mappings_hash();
2053 return Ok(existing);
2054 }
2055
2056 let twin_id = self.build_scan_twin_id(memory_area_id, field_area_id)?;
2057 if self.cortical_areas.contains_key(&twin_id) {
2058 self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
2059 self.refresh_cortical_mappings_hash();
2060 return Ok(twin_id);
2061 }
2062
2063 let twin_name = format!("{}_scan_twin", field_area.name.replace(' ', "_"));
2064 let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
2065 let twin_position = self.build_memory_twin_position(memory_area, field_area);
2066 let twin_dims = CorticalAreaDimensions::new(
2067 field_area.dimensions.width,
2068 field_area.dimensions.height,
2069 class_channel_count,
2070 )
2071 .map_err(|e| BduError::Internal(format!("Invalid scan twin dimensions: {}", e)))?;
2072 let mut twin_area =
2073 CorticalArea::new(twin_id, 0, twin_name, twin_dims, twin_position, twin_type)?;
2074 twin_area.properties = self.build_memory_twin_properties(
2075 memory_area,
2076 field_area,
2077 memory_area_id,
2078 field_area_id,
2079 );
2080 twin_area
2081 .properties
2082 .insert("scan_twin".to_string(), serde_json::json!(true));
2083
2084 let _twin_idx = self.add_cortical_area(twin_area)?;
2085 let _ = self.create_neurons_for_area(&twin_id);
2086 self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
2087 self.refresh_cortical_mappings_hash();
2088 Ok(twin_id)
2089 }
2090
2091 fn classifier_class_channel_count(&self, memory_area_id: &CorticalID) -> BduResult<u32> {
2092 let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
2093 BduError::InvalidArea(format!(
2094 "Memory area {} not found",
2095 memory_area_id.as_base_64()
2096 ))
2097 })?;
2098 let class_id = memory_area
2099 .properties
2100 .get("classifier_class_area_id")
2101 .and_then(|v| v.as_str())
2102 .ok_or_else(|| {
2103 BduError::InvalidArea(format!(
2104 "Memory area {} has no classifier_class_area_id; scan twin cannot be sized",
2105 memory_area_id.as_base_64()
2106 ))
2107 })?;
2108 let class_cortical_id = CorticalID::try_from_base_64(class_id).map_err(|e| {
2109 BduError::InvalidArea(format!(
2110 "Invalid classifier_class_area_id '{}': {}",
2111 class_id, e
2112 ))
2113 })?;
2114 let class_area = self.cortical_areas.get(&class_cortical_id).ok_or_else(|| {
2115 BduError::InvalidArea(format!(
2116 "Classifier class area {} not found",
2117 class_cortical_id.as_base_64()
2118 ))
2119 })?;
2120 let count = class_area
2121 .dimensions
2122 .width
2123 .saturating_mul(class_area.dimensions.height)
2124 .saturating_mul(class_area.dimensions.depth);
2125 if count == 0 {
2126 return Err(BduError::InvalidArea(format!(
2127 "Classifier class area {} has zero volume",
2128 class_cortical_id.as_base_64()
2129 )));
2130 }
2131 Ok(count)
2132 }
2133
2134 fn build_scan_twin_id(
2135 &self,
2136 memory_area_id: &CorticalID,
2137 field_area_id: &CorticalID,
2138 ) -> BduResult<CorticalID> {
2139 let mut hasher = Xxh64::new(DATA_HASH_SEED);
2140 hasher.write(memory_area_id.as_base_64().as_bytes());
2141 hasher.write(field_area_id.as_base_64().as_bytes());
2142 hasher.write(b"scan_twin");
2143 let hash = hasher.finish();
2144 let mut bytes = hash.to_be_bytes();
2145 bytes[0] = b'c';
2146 CorticalID::try_from_bytes(&bytes).map_err(|e| {
2147 BduError::Internal(format!("Failed to build scan twin cortical ID: {}", e))
2148 })
2149 }
2150
2151 fn build_memory_twin_position(
2152 &self,
2153 _memory_area: &CorticalArea,
2154 upstream_area: &CorticalArea,
2155 ) -> GenomeCoordinate3D {
2156 let width = upstream_area.dimensions.width as f32;
2157 let margin = (width * 0.25).ceil() as i32;
2158 let offset = upstream_area.dimensions.width as i32 + margin;
2159 GenomeCoordinate3D::new(
2160 upstream_area.position.x + offset,
2161 upstream_area.position.y,
2162 upstream_area.position.z,
2163 )
2164 }
2165
2166 fn build_memory_twin_id(
2167 &self,
2168 memory_area_id: &CorticalID,
2169 upstream_area_id: &CorticalID,
2170 ) -> BduResult<CorticalID> {
2171 let mut hasher = Xxh64::new(DATA_HASH_SEED);
2172 hasher.write(memory_area_id.as_base_64().as_bytes());
2173 hasher.write(upstream_area_id.as_base_64().as_bytes());
2174 hasher.write(b"memory_twin");
2175 let hash = hasher.finish();
2176 let mut bytes = hash.to_be_bytes();
2177 bytes[0] = b'c';
2178 CorticalID::try_from_bytes(&bytes)
2179 .map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
2180 }
2181
2182 fn build_memory_twin_properties(
2183 &self,
2184 _memory_area: &CorticalArea,
2185 upstream_area: &CorticalArea,
2186 memory_area_id: &CorticalID,
2187 upstream_area_id: &CorticalID,
2188 ) -> HashMap<String, serde_json::Value> {
2189 let mut props = upstream_area.properties.clone();
2190 props.remove("cortical_mapping_dst");
2191 props.remove("upstream_cortical_areas");
2192 props.remove("parent_region_id");
2193 props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
2194 props.insert("is_mem_type".to_string(), serde_json::json!(false));
2195 props.insert(
2196 "memory_twin_of".to_string(),
2197 serde_json::json!(upstream_area_id.as_base_64()),
2198 );
2199 props.insert(
2200 "memory_twin_for".to_string(),
2201 serde_json::json!(memory_area_id.as_base_64()),
2202 );
2203 if let Some(parent_region_id) = upstream_area
2204 .properties
2205 .get("parent_region_id")
2206 .and_then(|v| v.as_str())
2207 {
2208 props.insert(
2209 "parent_region_id".to_string(),
2210 serde_json::json!(parent_region_id),
2211 );
2212 }
2213 props
2214 }
2215
2216 pub fn remove_memory_twin_mapping(
2217 &mut self,
2218 memory_area_id: &str,
2219 field_area_id: &str,
2220 ) -> BduResult<()> {
2221 let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
2222 BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
2223 })?;
2224 if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
2225 if let Some(twins) = memory_area
2226 .properties
2227 .get_mut("memory_twin_areas")
2228 .and_then(|value| value.as_object_mut())
2229 {
2230 twins.remove(field_area_id);
2231 }
2232 }
2233 Ok(())
2234 }
2235
2236 fn set_memory_twin_mapping(
2237 &mut self,
2238 memory_area_id: &CorticalID,
2239 upstream_area_id: &CorticalID,
2240 twin_id: &CorticalID,
2241 ) {
2242 if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
2243 let mapping = memory_area
2244 .properties
2245 .entry("memory_twin_areas".to_string())
2246 .or_insert_with(|| serde_json::json!({}));
2247 if let Some(map) = mapping.as_object_mut() {
2248 map.insert(
2249 upstream_area_id.as_base_64(),
2250 serde_json::json!(twin_id.as_base_64()),
2251 );
2252 }
2253 }
2254 }
2255
2256 fn twin_is_classifier_stamp(&self, twin_id: &CorticalID) -> bool {
2258 let twin_b64 = twin_id.as_base_64();
2259 if self
2260 .classifiers
2261 .values()
2262 .any(|classifier| classifier.field_for_twin(&twin_b64).is_some())
2263 {
2264 return true;
2265 }
2266 self.cortical_areas
2267 .get(twin_id)
2268 .is_some_and(Self::area_belongs_to_classifier_assembly)
2269 }
2270
2271 pub fn rekey_memory_twin_source(
2274 &mut self,
2275 memory_area_id: &str,
2276 old_src_area_id: &str,
2277 new_src_area_id: &str,
2278 ) -> BduResult<()> {
2279 if old_src_area_id == new_src_area_id {
2280 return Ok(());
2281 }
2282 let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
2283 BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
2284 })?;
2285 let twin_entries: Vec<(String, serde_json::Value)> = {
2286 let memory_area = self.cortical_areas.get(&memory_id).ok_or_else(|| {
2287 BduError::InvalidArea(format!("Memory area {} not found", memory_area_id))
2288 })?;
2289 memory_area
2290 .properties
2291 .get("memory_twin_areas")
2292 .and_then(|value| value.as_object())
2293 .map(|twins| {
2294 twins
2295 .iter()
2296 .map(|(key, value)| (key.clone(), value.clone()))
2297 .collect()
2298 })
2299 .unwrap_or_default()
2300 };
2301 if twin_entries.is_empty() {
2302 return Ok(());
2303 }
2304 let mut used_key: Option<String> = None;
2305 let mut twin_id: Option<serde_json::Value> = None;
2306 if let Some((_, value)) = twin_entries.iter().find(|(key, _)| key == old_src_area_id) {
2307 used_key = Some(old_src_area_id.to_string());
2308 twin_id = Some(value.clone());
2309 } else {
2310 for (key, value) in &twin_entries {
2311 let Some(twin_b64) = value.as_str() else {
2312 continue;
2313 };
2314 let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) else {
2315 continue;
2316 };
2317 let owned_by_old_field = self
2318 .cortical_areas
2319 .get(&twin_cortical_id)
2320 .and_then(|twin| twin.properties.get("memory_twin_of"))
2321 .and_then(|property| property.as_str())
2322 == Some(old_src_area_id);
2323 if owned_by_old_field {
2324 used_key = Some(key.clone());
2325 twin_id = Some(value.clone());
2326 break;
2327 }
2328 }
2329 }
2330 let Some(used_key) = used_key else {
2331 return Ok(());
2332 };
2333 let Some(twin_id) = twin_id else {
2334 return Ok(());
2335 };
2336 if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
2337 if let Some(twins) = memory_area
2338 .properties
2339 .get_mut("memory_twin_areas")
2340 .and_then(|value| value.as_object_mut())
2341 {
2342 twins.remove(&used_key);
2343 twins.insert(new_src_area_id.to_string(), twin_id.clone());
2344 }
2345 }
2346 if let Some(twin_b64) = twin_id.as_str() {
2347 if let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) {
2348 if let Some(twin) = self.cortical_areas.get_mut(&twin_cortical_id) {
2349 twin.properties.insert(
2350 "memory_twin_of".to_string(),
2351 serde_json::json!(new_src_area_id),
2352 );
2353 }
2354 }
2355 }
2356 Ok(())
2357 }
2358
2359 fn ensure_memory_replay_mapping(
2360 &mut self,
2361 memory_area_id: &CorticalID,
2362 twin_id: &CorticalID,
2363 ) -> BduResult<()> {
2364 if !self.morphology_registry.contains("memory_replay") {
2365 feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
2366 }
2367 self.refresh_morphologies_hash();
2368 let mapping_data = vec![serde_json::json!({
2369 "morphology_id": "memory_replay",
2370 "morphology_scalar": [1, 1, 1],
2371 "postSynapticCurrent_multiplier": 1,
2372 "plasticity_flag": false,
2373 "plasticity_constant": 0,
2374 "ltp_multiplier": 0,
2375 "ltd_multiplier": 0,
2376 "plasticity_window": 0,
2377 })];
2378 self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
2379 let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
2380 self.refresh_cortical_area_hashes(true, false);
2382 Ok(())
2383 }
2384
2385 pub fn teardown_owned_memory_twin_for_mapping(
2390 &mut self,
2391 memory_area_id: &CorticalID,
2392 upstream_area_id: &CorticalID,
2393 ) -> BduResult<Option<CorticalID>> {
2394 let upstream_id = upstream_area_id.as_base_64();
2395 let twin_id = self
2396 .cortical_areas
2397 .get(memory_area_id)
2398 .and_then(|memory_area| memory_area.properties.get("memory_twin_areas"))
2399 .and_then(|value| value.as_object())
2400 .and_then(|twins| twins.get(&upstream_id))
2401 .and_then(|value| value.as_str())
2402 .map(CorticalID::try_from_base_64)
2403 .transpose()
2404 .map_err(|error| {
2405 BduError::InvalidArea(format!(
2406 "Invalid owned memory twin ID for {} -> {}: {}",
2407 upstream_area_id.as_base_64(),
2408 memory_area_id.as_base_64(),
2409 error
2410 ))
2411 })?;
2412 let Some(twin_id) = twin_id else {
2413 return Ok(None);
2414 };
2415 if self.twin_is_classifier_stamp(&twin_id) {
2417 return Ok(None);
2418 }
2419
2420 let twin = self.cortical_areas.get(&twin_id).ok_or_else(|| {
2421 BduError::InvalidArea(format!(
2422 "Owned memory twin {} for {} -> {} does not exist",
2423 twin_id.as_base_64(),
2424 upstream_area_id.as_base_64(),
2425 memory_area_id.as_base_64()
2426 ))
2427 })?;
2428 let owned_by_source = twin
2429 .properties
2430 .get("memory_twin_of")
2431 .and_then(|value| value.as_str())
2432 == Some(upstream_id.as_str());
2433 let owned_by_memory = twin
2434 .properties
2435 .get("memory_twin_for")
2436 .and_then(|value| value.as_str())
2437 == Some(memory_area_id.as_base_64().as_str());
2438 if !owned_by_source || !owned_by_memory {
2439 return Err(BduError::InvalidArea(format!(
2440 "Cortical area {} is not the generated twin owned by {} -> {}",
2441 twin_id.as_base_64(),
2442 upstream_area_id.as_base_64(),
2443 memory_area_id.as_base_64()
2444 )));
2445 }
2446
2447 let memory_idx = *self.cortical_id_to_idx.get(memory_area_id).ok_or_else(|| {
2448 BduError::InvalidArea(format!(
2449 "Memory area idx missing for {}",
2450 memory_area_id.as_base_64()
2451 ))
2452 })?;
2453 let upstream_idx = *self
2454 .cortical_id_to_idx
2455 .get(upstream_area_id)
2456 .ok_or_else(|| {
2457 BduError::InvalidArea(format!(
2458 "Upstream area idx missing for {}",
2459 upstream_area_id.as_base_64()
2460 ))
2461 })?;
2462
2463 self.update_cortical_mapping(memory_area_id, &twin_id, Vec::new())?;
2464 let _ = self.regenerate_synapses_for_mapping(memory_area_id, &twin_id)?;
2465 if let Some(npu) = &self.npu {
2466 npu.lock()
2467 .unwrap()
2468 .unregister_memory_twin_mapping(memory_idx, upstream_idx);
2469 }
2470
2471 let memory_area = self.cortical_areas.get_mut(memory_area_id).ok_or_else(|| {
2472 BduError::InvalidArea(format!(
2473 "Memory area {} not found",
2474 memory_area_id.as_base_64()
2475 ))
2476 })?;
2477 let twins = memory_area
2478 .properties
2479 .get_mut("memory_twin_areas")
2480 .and_then(|value| value.as_object_mut())
2481 .ok_or_else(|| {
2482 BduError::InvalidArea(format!(
2483 "Memory area {} has invalid memory_twin_areas metadata",
2484 memory_area_id.as_base_64()
2485 ))
2486 })?;
2487 twins.remove(&upstream_id);
2488 if twins.is_empty() {
2489 memory_area.properties.remove("memory_twin_areas");
2490 }
2491
2492 for region_id in self
2493 .get_brain_region_ids()
2494 .into_iter()
2495 .cloned()
2496 .collect::<Vec<_>>()
2497 {
2498 if let Some(region) = self.get_brain_region_mut(®ion_id) {
2499 region.remove_area(&twin_id);
2500 }
2501 }
2502 self.remove_cortical_area(&twin_id)?;
2503 self.refresh_cortical_mappings_hash();
2504 Ok(Some(twin_id))
2505 }
2506
2507 pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
2517 if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
2518 if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
2519 if let Some(arr) = upstream_prop.as_array_mut() {
2520 let src_value = serde_json::json!(src_cortical_idx);
2521 if let Some(pos) = arr.iter().position(|v| v == &src_value) {
2522 arr.remove(pos);
2523 debug!(target: "feagi-bdu",
2524 "Removed upstream area idx={} from cortical area '{}'",
2525 src_cortical_idx, target_cortical_id.as_base_64()
2526 );
2527 }
2528 }
2529 }
2530 }
2531 }
2532
2533 pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
2535 self.cortical_areas.contains_key(cortical_id)
2536 }
2537
2538 pub fn is_initialized(&self) -> bool {
2540 self.initialized && !self.cortical_areas.is_empty()
2541 }
2542
2543 pub fn add_brain_region(
2549 &mut self,
2550 region: BrainRegion,
2551 parent_id: Option<String>,
2552 ) -> BduResult<()> {
2553 self.brain_regions.add_region(region, parent_id)?;
2554 self.refresh_brain_regions_hash();
2555 Ok(())
2556 }
2557
2558 pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
2560 self.brain_regions.remove_region(region_id)?;
2561 self.refresh_brain_regions_hash();
2562 Ok(())
2563 }
2564
2565 pub fn clear_brain_regions(&mut self) {
2570 self.brain_regions.clear();
2571 self.refresh_brain_regions_hash();
2572 }
2573
2574 pub fn replace_classifiers(
2576 &mut self,
2577 classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
2578 ) {
2579 self.classifiers = classifiers;
2580 self.refresh_classifiers_hash();
2581 }
2582
2583 pub fn apply_loaded_classifier_assemblies(&mut self) {
2590 let classifiers: Vec<_> = self.classifiers.values().cloned().collect();
2591 for classifier in classifiers {
2592 self.apply_loaded_classifier_assembly(&classifier);
2593 }
2594 }
2595
2596 fn apply_loaded_classifier_assembly(
2597 &mut self,
2598 classifier: &feagi_structures::genomic::classifiers::Classifier,
2599 ) {
2600 let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) else {
2601 return;
2602 };
2603 let Ok(class_mem_id) = CorticalID::try_from_base_64(&classifier.class_memory_id) else {
2604 return;
2605 };
2606
2607 let mut twin_map = serde_json::Map::new();
2608 for field in &classifier.fields {
2609 if field.field_area_id.is_empty() || field.scan_twin_id.is_empty() {
2610 continue;
2611 }
2612 let Ok(twin_id) = CorticalID::try_from_base_64(&field.scan_twin_id) else {
2613 continue;
2614 };
2615 if !self.cortical_areas.contains_key(&twin_id) {
2616 continue;
2617 }
2618 twin_map.insert(
2619 field.field_area_id.clone(),
2620 serde_json::json!(field.scan_twin_id),
2621 );
2622 if let Ok(field_id) = CorticalID::try_from_base_64(&field.field_area_id) {
2623 if let Some(field_area) = self.cortical_areas.get_mut(&field_id) {
2624 Self::ensure_assembly_burst(field_area);
2625 }
2626 }
2627 if let Some(twin_area) = self.cortical_areas.get_mut(&twin_id) {
2628 twin_area
2629 .properties
2630 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2631 twin_area.properties.insert(
2632 "classifier_role".to_string(),
2633 serde_json::json!("scan_twin"),
2634 );
2635 twin_area
2636 .properties
2637 .insert("scan_twin".to_string(), serde_json::json!(true));
2638 twin_area.properties.insert(
2639 "memory_twin_of".to_string(),
2640 serde_json::json!(field.field_area_id),
2641 );
2642 twin_area.properties.insert(
2643 "memory_twin_for".to_string(),
2644 serde_json::json!(classifier.kernel_memory_id),
2645 );
2646 Self::ensure_assembly_burst(twin_area);
2647 }
2648 }
2649
2650 if let Some(mask_area_id) = &classifier.mask_area_id {
2651 if let Ok(mask_id) = CorticalID::try_from_base_64(mask_area_id) {
2652 if let Some(mask_area) = self.cortical_areas.get_mut(&mask_id) {
2653 Self::ensure_assembly_burst(mask_area);
2654 }
2655 }
2656 }
2657
2658 if let Some(kernel_mem) = self.cortical_areas.get_mut(&kernel_mem_id) {
2659 kernel_mem
2660 .properties
2661 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2662 kernel_mem.properties.insert(
2663 "classifier_role".to_string(),
2664 serde_json::json!("kernel_memory"),
2665 );
2666 Self::write_classifier_training_properties(kernel_mem, classifier);
2667 kernel_mem.properties.insert(
2668 "classifier_class_memory_id".to_string(),
2669 serde_json::json!(classifier.class_memory_id),
2670 );
2671 kernel_mem.properties.insert(
2672 "memory_twin_areas".to_string(),
2673 serde_json::Value::Object(twin_map),
2674 );
2675 Self::ensure_assembly_burst(kernel_mem);
2676 }
2677 if let Some(class_mem) = self.cortical_areas.get_mut(&class_mem_id) {
2678 class_mem
2679 .properties
2680 .insert("classifier_assembly".to_string(), serde_json::json!(true));
2681 class_mem.properties.insert(
2682 "classifier_role".to_string(),
2683 serde_json::json!("class_memory"),
2684 );
2685 class_mem.properties.insert(
2686 "classifier_kernel_memory_id".to_string(),
2687 serde_json::json!(classifier.kernel_memory_id),
2688 );
2689 if let Some(class_area_id) = &classifier.class_area_id {
2690 class_mem.properties.insert(
2691 "classifier_class_area_id".to_string(),
2692 serde_json::json!(class_area_id),
2693 );
2694 } else {
2695 class_mem.properties.remove("classifier_class_area_id");
2696 }
2697 Self::ensure_assembly_burst(class_mem);
2698 }
2699 }
2700
2701 fn write_classifier_training_properties(
2704 kernel_mem: &mut CorticalArea,
2705 classifier: &feagi_structures::genomic::classifiers::Classifier,
2706 ) {
2707 use feagi_structures::genomic::classifiers::ClassifierTrainingMode;
2708 kernel_mem.properties.insert(
2709 "classifier_training_mode".to_string(),
2710 serde_json::json!(classifier.training_mode),
2711 );
2712 match classifier.training_mode {
2713 ClassifierTrainingMode::Kernel => {
2714 kernel_mem.properties.remove("classifier_mask_area_id");
2715 kernel_mem.properties.remove("classifier_kernel_size");
2716 if let Some(kernel_area_id) = &classifier.kernel_area_id {
2717 kernel_mem.properties.insert(
2718 "classifier_kernel_area_id".to_string(),
2719 serde_json::json!(kernel_area_id),
2720 );
2721 } else {
2722 kernel_mem.properties.remove("classifier_kernel_area_id");
2723 }
2724 if let Some(class_area_id) = &classifier.class_area_id {
2725 kernel_mem.properties.insert(
2726 "classifier_class_area_id".to_string(),
2727 serde_json::json!(class_area_id),
2728 );
2729 } else {
2730 kernel_mem.properties.remove("classifier_class_area_id");
2731 }
2732 }
2733 ClassifierTrainingMode::Scanner => {
2734 kernel_mem.properties.remove("classifier_kernel_area_id");
2735 kernel_mem.properties.remove("classifier_class_area_id");
2736 if let Some(mask_area_id) = &classifier.mask_area_id {
2737 kernel_mem.properties.insert(
2738 "classifier_mask_area_id".to_string(),
2739 serde_json::json!(mask_area_id),
2740 );
2741 } else {
2742 kernel_mem.properties.remove("classifier_mask_area_id");
2743 }
2744 if let Some(kernel_size) = classifier.kernel_size {
2745 kernel_mem.properties.insert(
2746 "classifier_kernel_size".to_string(),
2747 serde_json::json!(kernel_size),
2748 );
2749 } else {
2750 kernel_mem.properties.remove("classifier_kernel_size");
2751 }
2752 }
2753 }
2754 }
2755
2756 fn ensure_assembly_burst(area: &mut CorticalArea) {
2759 if !area.properties.contains_key("burst_engine_active") {
2760 area.properties
2761 .insert("burst_engine_active".to_string(), serde_json::json!(true));
2762 }
2763 }
2764
2765 pub fn upsert_classifier(
2766 &mut self,
2767 classifier: feagi_structures::genomic::classifiers::Classifier,
2768 ) {
2769 self.classifiers
2770 .insert(classifier.classifier_id.clone(), classifier.clone());
2771 if let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) {
2772 if self.cortical_areas.contains_key(&kernel_mem_id) {
2773 self.apply_loaded_classifier_assembly(&classifier);
2774 }
2775 }
2776 self.refresh_classifiers_hash();
2777 }
2778
2779 pub fn reconfigure_classifier_scan(&self, kernel_memory_id: &str) {
2781 #[cfg(feature = "plasticity")]
2782 {
2783 let Ok(memory_id) = CorticalID::try_from_base_64(kernel_memory_id) else {
2784 return;
2785 };
2786 let Some(executor) = &self.plasticity_executor else {
2787 return;
2788 };
2789 let Ok(exec) = executor.lock() else {
2790 return;
2791 };
2792 self.configure_memory_scan_on_executor(&*exec, &memory_id);
2793 }
2794 #[cfg(not(feature = "plasticity"))]
2795 {
2796 let _ = kernel_memory_id;
2797 }
2798 }
2799
2800 pub fn get_classifier(
2801 &self,
2802 classifier_id: &str,
2803 ) -> Option<&feagi_structures::genomic::classifiers::Classifier> {
2804 self.classifiers.get(classifier_id)
2805 }
2806
2807 pub fn list_classifiers(
2808 &self,
2809 ) -> HashMap<String, feagi_structures::genomic::classifiers::Classifier> {
2810 self.classifiers.clone()
2811 }
2812
2813 pub fn remove_classifier(
2814 &mut self,
2815 classifier_id: &str,
2816 ) -> Option<feagi_structures::genomic::classifiers::Classifier> {
2817 let removed = self.classifiers.remove(classifier_id);
2818 if removed.is_some() {
2819 self.refresh_classifiers_hash();
2820 }
2821 removed
2822 }
2823
2824 pub fn classifiers_owning_area(
2826 &self,
2827 area_id: &str,
2828 ) -> Vec<feagi_structures::genomic::classifiers::Classifier> {
2829 self.classifiers
2830 .values()
2831 .filter(|classifier| classifier.owns_area(area_id))
2832 .cloned()
2833 .collect()
2834 }
2835
2836 pub fn clear_classifier_inputs_for_area(&mut self, area_id: &str) -> usize {
2838 let mut updated = Vec::new();
2839 for classifier in self.classifiers.values_mut() {
2840 if classifier.references_input(area_id) {
2841 classifier.clear_input(area_id);
2842 updated.push(classifier.clone());
2843 }
2844 }
2845 let count = updated.len();
2846 for classifier in &updated {
2847 if let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) {
2848 if self.cortical_areas.contains_key(&kernel_mem_id) {
2849 self.apply_loaded_classifier_assembly(classifier);
2850 }
2851 }
2852 }
2853 if count > 0 {
2854 self.refresh_classifiers_hash();
2855 }
2856 count
2857 }
2858
2859 pub fn apply_classifier_mapping_change(
2861 &mut self,
2862 src_area_id: &str,
2863 dst_area_id: &str,
2864 morphology_id: &str,
2865 removed: bool,
2866 ) -> usize {
2867 let mut updated = 0usize;
2868 for classifier in self.classifiers.values_mut() {
2869 if classifier.apply_mapping_change(src_area_id, dst_area_id, morphology_id, removed) {
2870 updated += 1;
2871 }
2872 }
2873 if updated > 0 {
2874 self.refresh_classifiers_hash();
2875 }
2876 updated
2877 }
2878
2879 pub fn change_brain_region_parent(
2881 &mut self,
2882 region_id: &str,
2883 new_parent_id: &str,
2884 ) -> BduResult<()> {
2885 self.brain_regions.change_parent(region_id, new_parent_id)?;
2886 self.refresh_brain_regions_hash();
2887 Ok(())
2888 }
2889
2890 pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
2892 self.brain_regions.get_region(region_id)
2893 }
2894
2895 pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
2897 self.brain_regions.get_region_mut(region_id)
2898 }
2899
2900 pub fn get_brain_region_ids(&self) -> Vec<&String> {
2902 self.brain_regions.get_all_region_ids()
2903 }
2904
2905 pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
2907 &self.brain_regions
2908 }
2909
2910 pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
2916 &self.morphology_registry
2917 }
2918
2919 pub fn get_morphology_count(&self) -> usize {
2921 self.morphology_registry.count()
2922 }
2923
2924 pub fn upsert_morphology(
2929 &mut self,
2930 morphology_id: String,
2931 morphology: feagi_evolutionary::Morphology,
2932 ) {
2933 self.morphology_registry
2934 .add_morphology(morphology_id, morphology);
2935 self.refresh_morphologies_hash();
2936 }
2937
2938 pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
2944 let removed = self.morphology_registry.remove_morphology(morphology_id);
2945 if removed {
2946 self.refresh_morphologies_hash();
2947 }
2948 removed
2949 }
2950
2951 pub fn update_cortical_mapping(
2969 &mut self,
2970 src_area_id: &CorticalID,
2971 dst_area_id: &CorticalID,
2972 mapping_data: Vec<serde_json::Value>,
2973 ) -> BduResult<()> {
2974 use tracing::info;
2975
2976 self.validate_memory_outbound_mapping_contract(src_area_id, dst_area_id, &mapping_data)?;
2977 crate::region_io_designation::validate_cross_region_mapping_proposal(
2978 self,
2979 src_area_id,
2980 dst_area_id,
2981 &mapping_data,
2982 )?;
2983
2984 debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
2985
2986 {
2987 let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
2989 crate::types::BduError::InvalidArea(format!(
2990 "Source area not found: {}",
2991 src_area_id
2992 ))
2993 })?;
2994
2995 let cortical_mapping_dst =
2997 if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
2998 existing.as_object_mut().ok_or_else(|| {
2999 crate::types::BduError::InvalidMorphology(
3000 "cortical_mapping_dst is not an object".to_string(),
3001 )
3002 })?
3003 } else {
3004 src_area
3006 .properties
3007 .insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
3008 src_area
3009 .properties
3010 .get_mut("cortical_mapping_dst")
3011 .unwrap()
3012 .as_object_mut()
3013 .unwrap()
3014 };
3015
3016 if mapping_data.is_empty() {
3018 cortical_mapping_dst.remove(&dst_area_id.as_base_64());
3020 info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
3021 } else {
3022 cortical_mapping_dst.insert(
3023 dst_area_id.as_base_64(),
3024 serde_json::Value::Array(mapping_data.clone()),
3025 );
3026 debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
3027 src_area_id, dst_area_id, mapping_data.len());
3028 }
3029 }
3030
3031 self.refresh_cortical_mappings_hash();
3032
3033 Ok(())
3034 }
3035
3036 pub fn validate_memory_outbound_mapping_contract(
3043 &self,
3044 src_area_id: &CorticalID,
3045 dst_area_id: &CorticalID,
3046 mapping_data: &[serde_json::Value],
3047 ) -> BduResult<()> {
3048 if mapping_data.is_empty() {
3049 return Ok(());
3050 }
3051 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3052 BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3053 })?;
3054 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
3055 BduError::InvalidArea(format!("Destination area not found: {}", dst_area_id))
3056 })?;
3057 if !matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
3058 || matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3059 {
3060 return Ok(());
3061 }
3062
3063 for rule in mapping_data {
3064 let morphology_id = rule
3065 .as_object()
3066 .and_then(|rule_obj| rule_obj.get("morphology_id"))
3067 .and_then(|value| value.as_str())
3068 .or_else(|| {
3069 rule.as_array()
3070 .and_then(|rule_array| rule_array.first())
3071 .and_then(|value| value.as_str())
3072 });
3073 let is_replay_edge_to_own_twin = morphology_id == Some("memory_replay")
3074 && dst_area
3075 .properties
3076 .get("memory_twin_for")
3077 .and_then(|value| value.as_str())
3078 == Some(src_area_id.as_base_64().as_str());
3079 if morphology_id != Some("associative_memory") && !is_replay_edge_to_own_twin {
3080 return Err(BduError::InvalidMorphology(format!(
3081 "Memory-to-non-memory mapping {} -> {} only supports associative_memory \
3082 (or memory_replay to its own twin)",
3083 src_area_id, dst_area_id
3084 )));
3085 }
3086 }
3087 Ok(())
3088 }
3089
3090 pub fn regenerate_synapses_for_mapping(
3103 &mut self,
3104 src_area_id: &CorticalID,
3105 dst_area_id: &CorticalID,
3106 ) -> BduResult<usize> {
3107 use tracing::info;
3108
3109 debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
3110
3111 let mapping_rules_len = self
3112 .cortical_areas
3113 .get(src_area_id)
3114 .and_then(|area| area.properties.get("cortical_mapping_dst"))
3115 .and_then(|v| v.as_object())
3116 .and_then(|map| map.get(&dst_area_id.as_base_64()))
3117 .and_then(|v| v.as_array())
3118 .map(|arr| arr.len())
3119 .unwrap_or(0);
3120 tracing::debug!(
3121 target: "feagi-bdu",
3122 "Mapping rules for {} -> {}: {}",
3123 src_area_id,
3124 dst_area_id,
3125 mapping_rules_len
3126 );
3127
3128 let Some(npu_arc) = self.npu.clone() else {
3130 info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
3131 return Ok(0);
3132 };
3133
3134 let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3144 BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
3145 })?;
3146 let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3147 BduError::InvalidArea(format!(
3148 "No cortical idx for destination area {}",
3149 dst_area_id
3150 ))
3151 })?;
3152
3153 let mut pruned_synapse_count: usize = 0;
3156 use std::time::Instant;
3157 let start = Instant::now();
3158
3159 let (sources, targets) = {
3165 let lock_start = std::time::Instant::now();
3166 let npu = npu_arc.lock().unwrap();
3167 let lock_wait = lock_start.elapsed();
3168 tracing::debug!(
3169 target: "feagi-bdu",
3170 "[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
3171 lock_wait.as_secs_f64() * 1000.0,
3172 src_area_id,
3173 dst_area_id
3174 );
3175 let sources: Vec<NeuronId> = npu
3176 .get_neurons_in_cortical_area(src_idx)
3177 .into_iter()
3178 .map(NeuronId)
3179 .collect();
3180 let targets: Vec<NeuronId> = npu
3181 .get_neurons_in_cortical_area(dst_idx)
3182 .into_iter()
3183 .map(NeuronId)
3184 .collect();
3185 (sources, targets)
3186 };
3187
3188 tracing::debug!(
3189 target: "feagi-bdu",
3190 "Prune synapses: {} sources, {} targets",
3191 sources.len(),
3192 targets.len()
3193 );
3194
3195 if !sources.is_empty() && !targets.is_empty() {
3196 let remove_start = Instant::now();
3197 pruned_synapse_count = {
3198 let lock_start = std::time::Instant::now();
3199 let mut npu = npu_arc.lock().unwrap();
3200 let lock_wait = lock_start.elapsed();
3201 tracing::debug!(
3202 target: "feagi-bdu",
3203 "[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
3204 lock_wait.as_secs_f64() * 1000.0,
3205 src_area_id,
3206 dst_area_id
3207 );
3208 npu.remove_synapses_between(sources, targets)
3212 };
3213 let remove_time = remove_start.elapsed();
3214 let total_time = start.elapsed();
3215
3216 info!(
3217 target: "feagi-bdu",
3218 "Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
3219 pruned_synapse_count,
3220 src_area_id,
3221 dst_area_id,
3222 total_time.as_millis(),
3223 remove_time.as_millis()
3224 );
3225
3226 if pruned_synapse_count > 0 {
3228 let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
3229 BduError::Internal(format!(
3230 "Pruned synapse count overflow (usize -> u32): {}",
3231 pruned_synapse_count
3232 ))
3233 })?;
3234 let state_manager = StateManager::instance();
3235 let state_manager = state_manager.read();
3236 let core_state = state_manager.get_core_state();
3237 core_state.subtract_synapse_count(pruned_u32);
3238 state_manager.subtract_cortical_area_outgoing_synapses(
3239 &src_area_id.as_base_64(),
3240 pruned_synapse_count,
3241 );
3242 state_manager.subtract_cortical_area_incoming_synapses(
3243 &dst_area_id.as_base_64(),
3244 pruned_synapse_count,
3245 );
3246
3247 {
3251 let mut cache = self.cached_synapse_counts_per_area.write();
3252 let entry = cache
3253 .entry(*src_area_id)
3254 .or_insert_with(|| AtomicUsize::new(0));
3255 let mut current = entry.load(Ordering::Relaxed);
3256 loop {
3257 let next = current.saturating_sub(pruned_synapse_count);
3258 match entry.compare_exchange(
3259 current,
3260 next,
3261 Ordering::Relaxed,
3262 Ordering::Relaxed,
3263 ) {
3264 Ok(_) => break,
3265 Err(v) => current = v,
3266 }
3267 }
3268 }
3269 }
3270 }
3271
3272 let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
3279 tracing::debug!(
3280 target: "feagi-bdu",
3281 "Synaptogenesis created {} synapses for {} -> {}",
3282 synapse_count,
3283 src_area_id,
3284 dst_area_id
3285 );
3286
3287 if synapse_count > 0 {
3290 let created_u32 = u32::try_from(synapse_count).map_err(|_| {
3291 BduError::Internal(format!(
3292 "Created synapse count overflow (usize -> u32): {}",
3293 synapse_count
3294 ))
3295 })?;
3296
3297 {
3299 let mut cache = self.cached_synapse_counts_per_area.write();
3300 cache
3301 .entry(*src_area_id)
3302 .or_insert_with(|| AtomicUsize::new(0))
3303 .fetch_add(synapse_count, Ordering::Relaxed);
3304 }
3305
3306 let state_manager = StateManager::instance();
3308 let state_manager = state_manager.read();
3309 let core_state = state_manager.get_core_state();
3310 core_state.add_synapse_count(created_u32);
3311 state_manager
3312 .add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
3313 state_manager
3314 .add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
3315 }
3316
3317 let src_idx_for_upstream = src_idx;
3320
3321 let has_mapping = self
3323 .cortical_areas
3324 .get(src_area_id)
3325 .and_then(|area| area.properties.get("cortical_mapping_dst"))
3326 .and_then(|v| v.as_object())
3327 .and_then(|map| map.get(&dst_area_id.as_base_64()))
3328 .is_some();
3329
3330 debug!(target: "feagi-bdu",
3331 "Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
3332 synapse_count,
3333 src_area_id.as_base_64(),
3334 dst_area_id.as_base_64(),
3335 has_mapping,
3336 if has_mapping { "" } else { "NOT " }
3337 );
3338
3339 if has_mapping {
3340 self.add_upstream_area(dst_area_id, src_idx_for_upstream);
3342
3343 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
3344 if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
3345 && !Self::area_belongs_to_classifier_assembly(dst_area)
3346 {
3347 let is_scan = self.mapping_from_src_to_dst_has_scan(src_area_id, dst_area_id);
3348 let twin_result = if is_scan {
3349 self.ensure_scan_twin_area(dst_area_id, src_area_id)
3350 } else {
3351 self.ensure_memory_twin_area(dst_area_id, src_area_id)
3352 };
3353 if let Err(e) = twin_result {
3354 warn!(
3355 target: "feagi-bdu",
3356 "Failed to ensure {} twin for {} -> {}: {}",
3357 if is_scan { "scan" } else { "memory" },
3358 src_area_id.as_base_64(),
3359 dst_area_id.as_base_64(),
3360 e
3361 );
3362 }
3363 }
3364 }
3365
3366 #[cfg(feature = "plasticity")]
3368 if let Some(ref executor) = self.plasticity_executor {
3369 use feagi_evolutionary::extract_memory_properties;
3370
3371 if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
3372 if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
3373 let upstream_areas =
3374 self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
3375 debug!(
3376 target: "feagi-bdu",
3377 "Registering memory area idx={} id={} upstream={} depth={}",
3378 dst_area.cortical_idx,
3379 dst_area_id.as_base_64(),
3380 upstream_areas.len(),
3381 mem_props.temporal_depth
3382 );
3383
3384 if let Some(ref npu_arc) = self.npu {
3387 if let Ok(mut npu) = npu_arc.lock() {
3388 let existing_configs = npu.get_all_fire_ledger_configs();
3389 for &upstream_idx in &upstream_areas {
3390 let existing = existing_configs
3391 .iter()
3392 .find(|(idx, _)| *idx == upstream_idx)
3393 .map(|(_, w)| *w)
3394 .unwrap_or(0);
3395
3396 let desired = mem_props.temporal_depth as usize;
3397 let resolved = existing.max(desired);
3398 if resolved != existing {
3399 if let Err(e) =
3400 npu.configure_fire_ledger_window(upstream_idx, resolved)
3401 {
3402 warn!(
3403 target: "feagi-bdu",
3404 "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
3405 upstream_idx,
3406 resolved,
3407 e
3408 );
3409 }
3410 }
3411 }
3412 } else {
3413 warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
3414 }
3415 }
3416
3417 if let Ok(exec) = executor.lock() {
3418 use feagi_npu_plasticity::{
3419 MemoryNeuronLifecycleConfig, PlasticityExecutor,
3420 };
3421
3422 let lifecycle_config = MemoryNeuronLifecycleConfig {
3423 initial_lifespan: mem_props.init_lifespan,
3424 lifespan_growth_rate: mem_props.lifespan_growth_rate,
3425 longterm_threshold: mem_props.longterm_threshold,
3426 max_reactivations: 1000,
3427 };
3428
3429 exec.register_memory_area(
3430 dst_area.cortical_idx,
3431 dst_area_id.as_base_64(),
3432 mem_props.temporal_depth,
3433 upstream_areas.clone(),
3434 Some(lifecycle_config),
3435 mem_props.mp_learning_enabled,
3436 );
3437 self.configure_memory_scan_on_executor(&*exec, dst_area_id);
3438 } else {
3439 warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
3440 }
3441 } else {
3442 debug!(
3443 target: "feagi-bdu",
3444 "Skipping plasticity registration: no memory properties for area {}",
3445 dst_area_id.as_base_64()
3446 );
3447 }
3448 } else {
3449 warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
3450 }
3451 } else {
3452 warn!(
3453 target: "feagi-bdu",
3454 "PlasticityExecutor not available; memory area {} not registered",
3455 dst_area_id.as_base_64()
3456 );
3457 }
3458
3459 #[cfg(not(feature = "plasticity"))]
3460 {
3461 info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
3462 }
3463 } else {
3464 self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
3466
3467 let destination_is_memory = self
3468 .cortical_areas
3469 .get(dst_area_id)
3470 .is_some_and(|area| matches!(area.cortical_type, CorticalAreaType::Memory(_)));
3471 if destination_is_memory {
3472 self.teardown_owned_memory_twin_for_mapping(dst_area_id, src_area_id)?;
3473 }
3474
3475 let mut npu = npu_arc.lock().unwrap();
3477 let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
3478 }
3479
3480 debug!(
3481 target: "feagi-bdu",
3482 "Created {} new synapses: {} -> {}",
3483 synapse_count,
3484 src_area_id,
3485 dst_area_id
3486 );
3487
3488 if pruned_synapse_count > 0 || synapse_count == 0 {
3491 let mut npu = npu_arc.lock().unwrap();
3492 npu.rebuild_synapse_index();
3493 info!(
3494 target: "feagi-bdu",
3495 "Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
3496 src_area_id,
3497 dst_area_id,
3498 pruned_synapse_count,
3499 synapse_count
3500 );
3501 } else {
3502 debug!(
3503 target: "feagi-bdu",
3504 "Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
3505 src_area_id,
3506 dst_area_id,
3507 synapse_count
3508 );
3509 }
3510
3511 {
3513 let npu = npu_arc.lock().unwrap();
3514 let fresh_count = npu.get_synapse_count();
3515 self.cached_synapse_count
3516 .store(fresh_count, Ordering::Relaxed);
3517 }
3518
3519 Ok(synapse_count)
3520 }
3521
3522 fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
3524 v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
3525 }
3526
3527 fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
3528 v.as_u64()
3529 .map(|n| n as usize)
3530 .or_else(|| v.as_f64().map(|f| f as usize))
3531 }
3532
3533 #[allow(clippy::too_many_arguments)]
3535 fn register_stdp_mapping_for_rule(
3536 npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
3537 src_area_id: &CorticalID,
3538 dst_area_id: &CorticalID,
3539 src_cortical_idx: u32,
3540 dst_cortical_idx: u32,
3541 rule_obj: &serde_json::Map<String, serde_json::Value>,
3542 bidirectional_stdp: bool,
3543 synapse_psp: f32,
3544 synapse_type: feagi_npu_neural::SynapseType,
3545 ) -> BduResult<()> {
3546 let plasticity_window = rule_obj
3547 .get("plasticity_window")
3548 .and_then(Self::json_number_as_usize_for_stdp)
3549 .ok_or_else(|| {
3550 BduError::Internal(format!(
3551 "Missing plasticity_window in plastic mapping rule {} -> {}",
3552 src_area_id, dst_area_id
3553 ))
3554 })?;
3555 let plasticity_constant = rule_obj
3556 .get("plasticity_constant")
3557 .and_then(Self::json_number_as_i64_for_stdp)
3558 .ok_or_else(|| {
3559 BduError::Internal(format!(
3560 "Missing plasticity_constant in plastic mapping rule {} -> {}",
3561 src_area_id, dst_area_id
3562 ))
3563 })?;
3564 let ltp_i64 = rule_obj
3565 .get("ltp_multiplier")
3566 .and_then(Self::json_number_as_i64_for_stdp)
3567 .ok_or_else(|| {
3568 BduError::Internal(format!(
3569 "Missing ltp_multiplier in plastic mapping rule {} -> {}",
3570 src_area_id, dst_area_id
3571 ))
3572 })?;
3573 let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
3574 BduError::Internal(format!(
3575 "ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
3576 i8::MIN,
3577 i8::MAX,
3578 ltp_i64,
3579 src_area_id,
3580 dst_area_id
3581 ))
3582 })?;
3583 let ltd_i64 = rule_obj
3584 .get("ltd_multiplier")
3585 .and_then(Self::json_number_as_i64_for_stdp)
3586 .ok_or_else(|| {
3587 BduError::Internal(format!(
3588 "Missing ltd_multiplier in plastic mapping rule {} -> {}",
3589 src_area_id, dst_area_id
3590 ))
3591 })?;
3592 let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
3593 BduError::Internal(format!(
3594 "ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
3595 i8::MIN,
3596 i8::MAX,
3597 ltd_i64,
3598 src_area_id,
3599 dst_area_id
3600 ))
3601 })?;
3602
3603 let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
3607 Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
3608 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
3609 }
3610 Some(s) if s.eq_ignore_ascii_case("stdp") => {
3611 feagi_npu_burst_engine::npu::PlasticityMode::Stdp
3612 }
3613 Some(s) if s.eq_ignore_ascii_case("off") => {
3614 feagi_npu_burst_engine::npu::PlasticityMode::Off
3615 }
3616 Some(other) => {
3617 return Err(BduError::Internal(format!(
3618 "Unknown plasticity_mode '{}' in mapping rule {} -> {}",
3619 other, src_area_id, dst_area_id
3620 )));
3621 }
3622 None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
3623 };
3624
3625 let eligibility_decay_bursts = rule_obj
3627 .get("eligibility_decay_bursts")
3628 .and_then(|v| v.as_u64())
3629 .map(|n| n as u32)
3630 .unwrap_or(0);
3631 let reward_source_area_id = rule_obj
3632 .get("reward_source_area")
3633 .and_then(|v| v.as_str())
3634 .map(str::to_string);
3635 let punishment_source_area_id = rule_obj
3636 .get("punishment_source_area")
3637 .and_then(|v| v.as_str())
3638 .map(str::to_string);
3639
3640 let max_weight_provided = rule_obj.get("max_weight").is_some()
3645 && !rule_obj
3646 .get("max_weight")
3647 .map(|v| v.is_null())
3648 .unwrap_or(true);
3649 let max_weight: f32 = if max_weight_provided {
3650 let raw = rule_obj
3651 .get("max_weight")
3652 .and_then(|v| v.as_f64())
3653 .ok_or_else(|| {
3654 BduError::Internal(format!(
3655 "max_weight must be a number on mapping {} -> {}",
3656 src_area_id, dst_area_id
3657 ))
3658 })?;
3659 if raw.is_nan() || raw <= 0.0 {
3660 return Err(BduError::Internal(format!(
3661 "max_weight must be strictly positive (got {}) on mapping {} -> {}",
3662 raw, src_area_id, dst_area_id
3663 )));
3664 }
3665 raw as f32
3666 } else {
3667 f32::INFINITY
3668 };
3669
3670 let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
3673 && !rule_obj
3674 .get("plasticity_eta")
3675 .map(|v| v.is_null())
3676 .unwrap_or(true);
3677 let plasticity_eta: f32 = if plasticity_eta_provided {
3678 let raw = rule_obj
3679 .get("plasticity_eta")
3680 .and_then(|v| v.as_f64())
3681 .ok_or_else(|| {
3682 BduError::Internal(format!(
3683 "plasticity_eta must be a number on mapping {} -> {}",
3684 src_area_id, dst_area_id
3685 ))
3686 })?;
3687 if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
3688 return Err(BduError::Internal(format!(
3689 "plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
3690 raw, src_area_id, dst_area_id
3691 )));
3692 }
3693 raw as f32
3694 } else {
3695 1.0
3696 };
3697
3698 if !matches!(
3700 plasticity_mode,
3701 feagi_npu_burst_engine::npu::PlasticityMode::RStdp
3702 ) && (reward_source_area_id.is_some()
3703 || punishment_source_area_id.is_some()
3704 || eligibility_decay_bursts != 0)
3705 {
3706 return Err(BduError::Internal(format!(
3707 "R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
3708 only valid when plasticity_mode='rstdp' on mapping {} -> {}",
3709 src_area_id, dst_area_id
3710 )));
3711 }
3712
3713 if matches!(
3717 plasticity_mode,
3718 feagi_npu_burst_engine::npu::PlasticityMode::Off
3719 ) && max_weight_provided
3720 {
3721 return Err(BduError::Internal(format!(
3722 "max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
3723 src_area_id, dst_area_id
3724 )));
3725 }
3726
3727 if matches!(
3728 plasticity_mode,
3729 feagi_npu_burst_engine::npu::PlasticityMode::Off
3730 ) && plasticity_eta_provided
3731 {
3732 return Err(BduError::Internal(format!(
3733 "plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
3734 src_area_id, dst_area_id
3735 )));
3736 }
3737
3738 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
3739 let mut npu_lock = npu
3740 .lock()
3741 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
3742 trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
3743
3744 let resolve_optional_area =
3749 |label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
3750 let Some(name) = name_opt else {
3751 return Ok(None);
3752 };
3753 match npu_lock.get_cortical_area_id(name.as_str()) {
3754 Some(idx) => Ok(Some(idx)),
3755 None => Err(BduError::Internal(format!(
3756 "Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
3757 label, name, src_area_id, dst_area_id
3758 ))),
3759 }
3760 };
3761 let reward_source_area =
3762 resolve_optional_area("reward_source_area", &reward_source_area_id)?;
3763 let punishment_source_area =
3764 resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
3765
3766 if matches!(
3768 plasticity_mode,
3769 feagi_npu_burst_engine::npu::PlasticityMode::Off
3770 ) {
3771 return Ok(());
3772 }
3773
3774 let params = feagi_npu_burst_engine::npu::StdpMappingParams {
3775 plasticity_window,
3776 plasticity_constant,
3777 ltp_multiplier,
3778 ltd_multiplier,
3779 bidirectional_stdp,
3780 associative_memory_mapping: rule_obj
3781 .get("morphology_id")
3782 .and_then(|value| value.as_str())
3783 == Some("associative_memory"),
3784 synapse_psp,
3785 synapse_type,
3786 plasticity_mode,
3787 eligibility_decay_bursts,
3788 reward_source_area,
3789 punishment_source_area,
3790 max_weight,
3791 plasticity_eta,
3792 };
3793
3794 npu_lock
3795 .register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
3796 .map_err(|e| {
3797 BduError::Internal(format!(
3798 "Failed to register STDP mapping {} -> {}: {}",
3799 src_area_id, dst_area_id, e
3800 ))
3801 })?;
3802
3803 let mut areas_to_track: Vec<(u32, usize)> = vec![
3807 (src_cortical_idx, plasticity_window),
3808 (dst_cortical_idx, plasticity_window),
3809 ];
3810 if let Some(area) = reward_source_area {
3811 areas_to_track.push((area, 1));
3812 }
3813 if let Some(area) = punishment_source_area {
3814 areas_to_track.push((area, 1));
3815 }
3816
3817 let existing_configs = npu_lock.get_all_fire_ledger_configs();
3818 for (area_idx, required_depth) in areas_to_track {
3819 let existing = existing_configs
3820 .iter()
3821 .find(|(idx, _)| *idx == area_idx)
3822 .map(|(_, w)| *w)
3823 .unwrap_or(0);
3824 let resolved = existing.max(required_depth);
3825 if resolved != existing {
3826 npu_lock
3827 .configure_fire_ledger_window(area_idx, resolved)
3828 .map_err(|e| {
3829 BduError::Internal(format!(
3830 "Failed to configure FireLedger window for area idx={} (requested={}): {}",
3831 area_idx, resolved, e
3832 ))
3833 })?;
3834 }
3835 }
3836
3837 Ok(())
3838 }
3839
3840 fn resolve_synapse_params_for_rule(
3842 &self,
3843 src_area_id: &CorticalID,
3844 rule: &serde_json::Value,
3845 ) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
3846 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3848 crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
3849 })?;
3850
3851 let (weight, synapse_type) = {
3853 let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
3854 if let Some(i) = v.as_i64() {
3855 return Some(i as f64);
3856 }
3857 v.as_f64()
3858 };
3859
3860 let mult: f64 = if let Some(obj) = rule.as_object() {
3861 obj.get("postSynapticCurrent_multiplier")
3862 .and_then(parse_f64)
3863 .unwrap_or(1.0)
3864 } else if let Some(arr) = rule.as_array() {
3865 arr.get(2).and_then(parse_f64).unwrap_or(1.0)
3866 } else {
3867 128.0
3868 };
3869
3870 if mult < 0.0 {
3871 (mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
3872 } else {
3873 (mult as f32, feagi_npu_neural::SynapseType::Excitatory)
3874 }
3875 };
3876
3877 use crate::models::cortical_area::CorticalAreaExt;
3879 let psp_f32 = src_area.postsynaptic_current();
3880
3881 let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
3882 obj.get("synaptic_delay_bursts")
3883 .and_then(|v| v.as_u64())
3884 .map(|d| u8::try_from(d).unwrap_or(1))
3885 .unwrap_or(1)
3886 } else if let Some(arr) = rule.as_array() {
3887 arr.get(8)
3888 .and_then(|v| v.as_u64())
3889 .map(|d| u8::try_from(d).unwrap_or(1))
3890 .unwrap_or(1)
3891 } else {
3892 1
3893 };
3894 if delay_bursts < 1 {
3895 return Err(crate::types::BduError::Internal(format!(
3896 "synaptic_delay_bursts must be >= 1 (src area {})",
3897 src_area_id.as_base_64()
3898 )));
3899 }
3900
3901 tracing::debug!(
3902 target: "feagi-bdu",
3903 "Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
3904 src_area_id.as_base_64(),
3905 weight,
3906 psp_f32,
3907 synapse_type,
3908 delay_bursts
3909 );
3910
3911 Ok((weight, psp_f32, synapse_type, delay_bursts))
3912 }
3913
3914 fn apply_cortical_mapping_for_pair(
3916 &mut self,
3917 src_area_id: &CorticalID,
3918 dst_area_id: &CorticalID,
3919 ) -> BduResult<usize> {
3920 let rules = {
3926 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
3927 crate::types::BduError::InvalidArea(format!(
3928 "Source area not found: {}",
3929 src_area_id
3930 ))
3931 })?;
3932
3933 let Some(mapping_dst) = src_area
3934 .properties
3935 .get("cortical_mapping_dst")
3936 .and_then(|v| v.as_object())
3937 else {
3938 return Ok(0);
3939 };
3940
3941 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
3942 else {
3943 return Ok(0);
3944 };
3945
3946 rules.clone()
3947 }; if rules.is_empty() {
3950 return Ok(0);
3951 }
3952
3953 let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
3955 crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
3956 })?;
3957 let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
3958 crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
3959 })?;
3960
3961 let npu_arc = self
3963 .npu
3964 .as_ref()
3965 .ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
3966 .clone();
3967
3968 tracing::debug!(
3969 target: "feagi-bdu",
3970 "Applying {} mapping rule(s) for {} -> {}",
3971 rules.len(),
3972 src_area_id,
3973 dst_area_id
3974 );
3975 let mut total_synapses = 0;
3977 for rule in &rules {
3978 let morphology_id = if let Some(rule_obj) = rule.as_object() {
3979 rule_obj
3980 .get("morphology_id")
3981 .and_then(|v| v.as_str())
3982 .unwrap_or("unknown")
3983 .to_string()
3984 } else if let Some(rule_arr) = rule.as_array() {
3985 rule_arr
3986 .first()
3987 .and_then(|v| v.as_str())
3988 .unwrap_or("unknown")
3989 .to_string()
3990 } else {
3991 "unknown".to_string()
3992 };
3993
3994 let rule_keys: Vec<String> = rule
3995 .as_object()
3996 .map(|obj| obj.keys().cloned().collect())
3997 .unwrap_or_default();
3998
3999 let mut plasticity_flag = rule
4001 .as_object()
4002 .and_then(|obj| obj.get("plasticity_flag"))
4003 .and_then(|v| v.as_bool())
4004 .unwrap_or(false);
4005 if morphology_id == "associative_memory" {
4006 plasticity_flag = true;
4007 }
4008 if plasticity_flag {
4009 let Some(rule_obj) = rule.as_object() else {
4010 return Err(crate::types::BduError::InvalidMorphology(
4011 "Plasticity mapping rule must be an object format".to_string(),
4012 ));
4013 };
4014 let (_weight, psp, synapse_type, _delay_bursts) =
4015 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
4016 let bidirectional_stdp = false;
4019 if let Err(e) = Self::register_stdp_mapping_for_rule(
4020 &npu_arc,
4021 src_area_id,
4022 dst_area_id,
4023 src_cortical_idx,
4024 dst_cortical_idx,
4025 rule_obj,
4026 bidirectional_stdp,
4027 psp,
4028 synapse_type,
4029 ) {
4030 tracing::error!(
4031 target: "feagi-bdu",
4032 "STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
4033 src_area_id,
4034 dst_area_id,
4035 morphology_id,
4036 rule_keys,
4037 e
4038 );
4039 return Err(e);
4040 }
4041 }
4042
4043 if let Some(gate_area_str) = rule
4047 .as_object()
4048 .and_then(|obj| obj.get("gate_source_area"))
4049 .and_then(|v| v.as_str())
4050 {
4051 let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
4052 crate::types::BduError::Internal(format!(
4053 "Invalid gate_source_area '{}' on mapping {} -> {}",
4054 gate_area_str, src_area_id, dst_area_id
4055 ))
4056 })?;
4057 let gate_cortical_idx =
4058 self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
4059 crate::types::BduError::Internal(format!(
4060 "Unknown gate_source_area '{}' on mapping {} -> {}",
4061 gate_area_str, src_area_id, dst_area_id
4062 ))
4063 })?;
4064
4065 let mut npu_lock = npu_arc.lock().map_err(|_| {
4066 crate::types::BduError::Internal(
4067 "Failed to acquire NPU lock for gate registration".to_string(),
4068 )
4069 })?;
4070 if let Err(e) = npu_lock.register_gate_mapping(
4071 src_cortical_idx,
4072 dst_cortical_idx,
4073 *gate_cortical_idx,
4074 ) {
4075 tracing::error!(
4076 target: "feagi-bdu",
4077 "Gate mapping registration failed for {} -> {} (gate={}): {}",
4078 src_area_id,
4079 dst_area_id,
4080 gate_area_str,
4081 e
4082 );
4083 return Err(crate::types::BduError::Internal(format!(
4084 "Gate registration failed: {}",
4085 e
4086 )));
4087 }
4088 }
4089
4090 let synapse_count = match self.apply_single_morphology_rule(
4092 src_area_id,
4093 dst_area_id,
4094 rule,
4095 ) {
4096 Ok(count) => count,
4097 Err(e) => {
4098 tracing::error!(
4099 target: "feagi-bdu",
4100 "Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
4101 src_area_id,
4102 dst_area_id,
4103 morphology_id,
4104 rule_keys,
4105 e
4106 );
4107 return Err(e);
4108 }
4109 };
4110 total_synapses += synapse_count;
4111 tracing::debug!(
4112 target: "feagi-bdu",
4113 "Rule {} created {} synapses for {} -> {}",
4114 morphology_id,
4115 synapse_count,
4116 src_area_id,
4117 dst_area_id
4118 );
4119 }
4120
4121 Ok(total_synapses)
4122 }
4123
4124 #[allow(clippy::too_many_arguments)]
4138 fn apply_function_morphology(
4139 &self,
4140 morphology_id: &str,
4141 rule: &serde_json::Value,
4142 npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
4143 npu: &mut feagi_npu_burst_engine::DynamicNPU,
4144 src_area_id: &CorticalID,
4145 dst_area_id: &CorticalID,
4146 src_idx: u32,
4147 dst_idx: u32,
4148 weight: f32,
4149 psp: f32,
4150 synapse_attractivity: u8,
4151 synapse_type: feagi_npu_neural::SynapseType,
4152 delay_bursts: u8,
4153 ) -> BduResult<usize> {
4154 match morphology_id {
4155 "projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
4156 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4158 crate::types::BduError::InvalidArea(format!(
4159 "Source area not found: {}",
4160 src_area_id
4161 ))
4162 })?;
4163 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4164 crate::types::BduError::InvalidArea(format!(
4165 "Destination area not found: {}",
4166 dst_area_id
4167 ))
4168 })?;
4169
4170 let src_dimensions = (
4171 src_area.dimensions.width as usize,
4172 src_area.dimensions.height as usize,
4173 src_area.dimensions.depth as usize,
4174 );
4175 let dst_dimensions = (
4176 dst_area.dimensions.width as usize,
4177 dst_area.dimensions.height as usize,
4178 dst_area.dimensions.depth as usize,
4179 );
4180
4181 let transpose = match morphology_id {
4184 "transpose_xy" => Some((1, 0, 2)),
4185 "transpose_yz" => Some((0, 2, 1)),
4186 "transpose_xz" => Some((2, 1, 0)),
4187 _ => None,
4188 };
4189
4190 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
4191 let count = apply_projector_morphology_with_dimensions(
4192 npu,
4193 src_idx,
4194 dst_idx,
4195 src_dimensions,
4196 dst_dimensions,
4197 transpose,
4198 None, weight,
4200 psp,
4201 synapse_attractivity,
4202 synapse_type,
4203 0,
4204 delay_bursts,
4205 )?;
4206 npu.rebuild_synapse_index();
4208 Ok(count as usize)
4209 }
4210 "centered_projector" => {
4211 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4212 crate::types::BduError::InvalidArea(format!(
4213 "Source area not found: {}",
4214 src_area_id
4215 ))
4216 })?;
4217 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4218 crate::types::BduError::InvalidArea(format!(
4219 "Destination area not found: {}",
4220 dst_area_id
4221 ))
4222 })?;
4223
4224 let src_dimensions = (
4225 src_area.dimensions.width as usize,
4226 src_area.dimensions.height as usize,
4227 src_area.dimensions.depth as usize,
4228 );
4229 let dst_dimensions = (
4230 dst_area.dimensions.width as usize,
4231 dst_area.dimensions.height as usize,
4232 dst_area.dimensions.depth as usize,
4233 );
4234
4235 let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
4236 npu,
4237 src_idx,
4238 dst_idx,
4239 src_dimensions,
4240 dst_dimensions,
4241 weight,
4242 psp,
4243 synapse_attractivity,
4244 synapse_type,
4245 delay_bursts,
4246 )?;
4247 if count > 0 {
4248 npu.rebuild_synapse_index();
4249 }
4250 Ok(count as usize)
4251 }
4252 "episodic_memory" => {
4253 use tracing::trace;
4256 trace!(
4257 target: "feagi-bdu",
4258 "Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
4259 src_idx, dst_idx
4260 );
4261 Ok(0)
4262 }
4263 "episodic_scan" => {
4264 use tracing::trace;
4265 trace!(
4266 target: "feagi-bdu",
4267 "Episodic scan morphology: {} -> {} (no physical synapses, plasticity-driven)",
4268 src_idx, dst_idx
4269 );
4270 Ok(0)
4271 }
4272 "memory_replay" => {
4273 use tracing::trace;
4275 trace!(
4276 target: "feagi-bdu",
4277 "Memory replay morphology: {} -> {} (no physical synapses)",
4278 src_idx, dst_idx
4279 );
4280 Ok(0)
4281 }
4282 "associative_memory" => {
4283 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4287 crate::types::BduError::InvalidArea(format!(
4288 "Source area not found: {}",
4289 src_area_id
4290 ))
4291 })?;
4292 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4293 crate::types::BduError::InvalidArea(format!(
4294 "Destination area not found: {}",
4295 dst_area_id
4296 ))
4297 })?;
4298
4299 if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
4300 && matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
4301 {
4302 let src_dimensions = (
4303 src_area.dimensions.width as usize,
4304 src_area.dimensions.height as usize,
4305 src_area.dimensions.depth as usize,
4306 );
4307 let dst_dimensions = (
4308 dst_area.dimensions.width as usize,
4309 dst_area.dimensions.height as usize,
4310 dst_area.dimensions.depth as usize,
4311 );
4312 use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
4313 let count = apply_projector_morphology_with_dimensions(
4314 npu,
4315 src_idx,
4316 dst_idx,
4317 src_dimensions,
4318 dst_dimensions,
4319 None,
4320 None,
4321 weight,
4322 psp,
4323 synapse_attractivity,
4324 synapse_type,
4325 SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
4326 delay_bursts,
4327 )?;
4328 npu.rebuild_synapse_index();
4329 Ok(count as usize)
4330 } else {
4331 Ok(0)
4332 }
4333 }
4334 "block_to_block" => {
4335 tracing::warn!(
4336 target: "feagi-bdu",
4337 "🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
4338 src_idx, dst_idx
4339 );
4340 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4342 crate::types::BduError::InvalidArea(format!(
4343 "Source area not found: {}",
4344 src_area_id
4345 ))
4346 })?;
4347 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4348 crate::types::BduError::InvalidArea(format!(
4349 "Destination area not found: {}",
4350 dst_area_id
4351 ))
4352 })?;
4353
4354 let src_dimensions = (
4355 src_area.dimensions.width as usize,
4356 src_area.dimensions.height as usize,
4357 src_area.dimensions.depth as usize,
4358 );
4359 let dst_dimensions = (
4360 dst_area.dimensions.width as usize,
4361 dst_area.dimensions.height as usize,
4362 dst_area.dimensions.depth as usize,
4363 );
4364
4365 let scalar = if let Some(obj) = rule.as_object() {
4367 if let Some(scalar_arr) =
4369 obj.get("morphology_scalar").and_then(|v| v.as_array())
4370 {
4371 scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
4373 } else {
4374 1 }
4376 } else if let Some(arr) = rule.as_array() {
4377 arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
4379 } else {
4380 1 };
4382
4383 let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
4386 let count = if estimated_neurons > 100_000 {
4387 let _ = npu;
4389
4390 crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
4391 npu_arc,
4392 src_idx,
4393 dst_idx,
4394 src_dimensions,
4395 dst_dimensions,
4396 scalar, weight,
4398 psp,
4399 synapse_attractivity,
4400 synapse_type,
4401 delay_bursts,
4402 )? as usize
4403 } else {
4404 tracing::warn!(
4406 target: "feagi-bdu",
4407 "🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
4408 src_idx, dst_idx, src_dimensions, dst_dimensions
4409 );
4410 let count =
4411 crate::connectivity::synaptogenesis::apply_block_connection_morphology(
4412 npu,
4413 src_idx,
4414 dst_idx,
4415 src_dimensions,
4416 dst_dimensions,
4417 scalar, weight,
4419 psp,
4420 synapse_attractivity,
4421 synapse_type,
4422 delay_bursts,
4423 )? as usize;
4424 tracing::warn!(
4425 target: "feagi-bdu",
4426 "🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
4427 count
4428 );
4429 if count > 0 {
4431 npu.rebuild_synapse_index();
4432 }
4433 count
4434 };
4435
4436 if count > 0 && estimated_neurons > 100_000 {
4438 let mut npu_lock = npu_arc.lock().unwrap();
4439 npu_lock.rebuild_synapse_index();
4440 }
4441
4442 Ok(count)
4443 }
4444 "bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
4445 | "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
4446 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4447 crate::types::BduError::InvalidArea(format!(
4448 "Source area not found: {}",
4449 src_area_id
4450 ))
4451 })?;
4452 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4453 crate::types::BduError::InvalidArea(format!(
4454 "Destination area not found: {}",
4455 dst_area_id
4456 ))
4457 })?;
4458
4459 let src_dimensions = (
4460 src_area.dimensions.width as usize,
4461 src_area.dimensions.height as usize,
4462 src_area.dimensions.depth as usize,
4463 );
4464 let dst_dimensions = (
4465 dst_area.dimensions.width as usize,
4466 dst_area.dimensions.height as usize,
4467 dst_area.dimensions.depth as usize,
4468 );
4469
4470 let (axis, mode) = match morphology_id {
4471 "bitmask_encoder_x" => (
4472 crate::connectivity::core_morphologies::BitmaskAxis::X,
4473 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4474 ),
4475 "bitmask_encoder_y" => (
4476 crate::connectivity::core_morphologies::BitmaskAxis::Y,
4477 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4478 ),
4479 "bitmask_encoder_z" => (
4480 crate::connectivity::core_morphologies::BitmaskAxis::Z,
4481 crate::connectivity::core_morphologies::BitmaskMode::Encoder,
4482 ),
4483 "bitmask_decoder_x" => (
4484 crate::connectivity::core_morphologies::BitmaskAxis::X,
4485 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4486 ),
4487 "bitmask_decoder_y" => (
4488 crate::connectivity::core_morphologies::BitmaskAxis::Y,
4489 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4490 ),
4491 "bitmask_decoder_z" => (
4492 crate::connectivity::core_morphologies::BitmaskAxis::Z,
4493 crate::connectivity::core_morphologies::BitmaskMode::Decoder,
4494 ),
4495 _ => unreachable!("matched bitmask morphology above"),
4496 };
4497
4498 let count =
4499 crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
4500 npu,
4501 src_idx,
4502 dst_idx,
4503 src_dimensions,
4504 dst_dimensions,
4505 axis,
4506 mode,
4507 weight,
4508 psp,
4509 synapse_attractivity,
4510 synapse_type,
4511 delay_bursts,
4512 )?;
4513 if count > 0 {
4514 npu.rebuild_synapse_index();
4515 }
4516 Ok(count as usize)
4517 }
4518 "sweeper" => {
4519 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4520 crate::types::BduError::InvalidArea(format!(
4521 "Destination area not found: {}",
4522 dst_area_id
4523 ))
4524 })?;
4525 let dst_dimensions = (
4526 dst_area.dimensions.width as usize,
4527 dst_area.dimensions.height as usize,
4528 dst_area.dimensions.depth as usize,
4529 );
4530
4531 let count =
4532 crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
4533 npu,
4534 src_idx,
4535 dst_idx,
4536 dst_dimensions,
4537 weight,
4538 psp,
4539 synapse_attractivity,
4540 synapse_type,
4541 delay_bursts,
4542 )?;
4543 if count > 0 {
4544 npu.rebuild_synapse_index();
4545 }
4546 Ok(count as usize)
4547 }
4548 "last_to_first" => {
4549 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4550 crate::types::BduError::InvalidArea(format!(
4551 "Source area not found: {}",
4552 src_area_id
4553 ))
4554 })?;
4555 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4556 crate::types::BduError::InvalidArea(format!(
4557 "Destination area not found: {}",
4558 dst_area_id
4559 ))
4560 })?;
4561 let src_dimensions = (
4562 src_area.dimensions.width as usize,
4563 src_area.dimensions.height as usize,
4564 src_area.dimensions.depth as usize,
4565 );
4566 let dst_dimensions = (
4567 dst_area.dimensions.width as usize,
4568 dst_area.dimensions.height as usize,
4569 dst_area.dimensions.depth as usize,
4570 );
4571
4572 let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
4573 npu,
4574 src_idx,
4575 dst_idx,
4576 src_dimensions,
4577 dst_dimensions,
4578 weight,
4579 psp,
4580 synapse_attractivity,
4581 synapse_type,
4582 delay_bursts,
4583 )?;
4584 if count > 0 {
4585 npu.rebuild_synapse_index();
4586 }
4587 Ok(count as usize)
4588 }
4589 "first_to_last" => {
4590 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4591 crate::types::BduError::InvalidArea(format!(
4592 "Source area not found: {}",
4593 src_area_id
4594 ))
4595 })?;
4596 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4597 crate::types::BduError::InvalidArea(format!(
4598 "Destination area not found: {}",
4599 dst_area_id
4600 ))
4601 })?;
4602 let src_dimensions = (
4603 src_area.dimensions.width as usize,
4604 src_area.dimensions.height as usize,
4605 src_area.dimensions.depth as usize,
4606 );
4607 let dst_dimensions = (
4608 dst_area.dimensions.width as usize,
4609 dst_area.dimensions.height as usize,
4610 dst_area.dimensions.depth as usize,
4611 );
4612
4613 let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
4614 npu,
4615 src_idx,
4616 dst_idx,
4617 src_dimensions,
4618 dst_dimensions,
4619 weight,
4620 psp,
4621 synapse_attractivity,
4622 synapse_type,
4623 delay_bursts,
4624 )?;
4625 if count > 0 {
4626 npu.rebuild_synapse_index();
4627 }
4628 Ok(count as usize)
4629 }
4630 "rotator_z" => {
4631 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4632 crate::types::BduError::InvalidArea(format!(
4633 "Source area not found: {}",
4634 src_area_id
4635 ))
4636 })?;
4637 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4638 crate::types::BduError::InvalidArea(format!(
4639 "Destination area not found: {}",
4640 dst_area_id
4641 ))
4642 })?;
4643 let src_dimensions = (
4644 src_area.dimensions.width as usize,
4645 src_area.dimensions.height as usize,
4646 src_area.dimensions.depth as usize,
4647 );
4648 let dst_dimensions = (
4649 dst_area.dimensions.width as usize,
4650 dst_area.dimensions.height as usize,
4651 dst_area.dimensions.depth as usize,
4652 );
4653
4654 let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
4655 npu,
4656 src_idx,
4657 dst_idx,
4658 src_dimensions,
4659 dst_dimensions,
4660 weight,
4661 psp,
4662 synapse_attractivity,
4663 synapse_type,
4664 delay_bursts,
4665 )?;
4666 if count > 0 {
4667 npu.rebuild_synapse_index();
4668 }
4669 Ok(count as usize)
4670 }
4671 _ => {
4672 use tracing::debug;
4675 debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
4676 Ok(0)
4677 }
4678 }
4679 }
4680
4681 fn apply_single_morphology_rule(
4683 &mut self,
4684 src_area_id: &CorticalID,
4685 dst_area_id: &CorticalID,
4686 rule: &serde_json::Value,
4687 ) -> BduResult<usize> {
4688 let morphology_id = if let Some(arr) = rule.as_array() {
4690 arr.first().and_then(|v| v.as_str()).unwrap_or("")
4691 } else if let Some(obj) = rule.as_object() {
4692 obj.get("morphology_id")
4693 .and_then(|v| v.as_str())
4694 .unwrap_or("")
4695 } else {
4696 return Ok(0);
4697 };
4698
4699 if morphology_id.is_empty() {
4700 return Ok(0);
4701 }
4702
4703 let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
4705 crate::types::BduError::InvalidMorphology(format!(
4706 "Morphology not found: {}",
4707 morphology_id
4708 ))
4709 })?;
4710
4711 let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
4713 crate::types::BduError::InvalidArea(format!(
4714 "Source area ID not found: {}",
4715 src_area_id
4716 ))
4717 })?;
4718 let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
4719 crate::types::BduError::InvalidArea(format!(
4720 "Destination area ID not found: {}",
4721 dst_area_id
4722 ))
4723 })?;
4724
4725 if let Some(ref npu_arc) = self.npu {
4727 let lock_start = std::time::Instant::now();
4728 let mut npu = npu_arc.lock().unwrap();
4729 let lock_wait = lock_start.elapsed();
4730 tracing::debug!(
4731 target: "feagi-bdu",
4732 "[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
4733 lock_wait.as_secs_f64() * 1000.0,
4734 src_area_id,
4735 dst_area_id,
4736 morphology_id
4737 );
4738
4739 let (weight, psp, synapse_type, delay_bursts) =
4740 self.resolve_synapse_params_for_rule(src_area_id, rule)?;
4741
4742 let synapse_attractivity = if let Some(obj) = rule.as_object() {
4744 obj.get("synapse_attractivity")
4745 .and_then(|v| v.as_u64())
4746 .unwrap_or(100) as u8
4747 } else {
4748 100 };
4750
4751 match morphology.morphology_type {
4752 feagi_evolutionary::MorphologyType::Functions => {
4753 tracing::debug!(
4754 target: "feagi-bdu",
4755 "apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
4756 morphology_id
4757 );
4758 self.apply_function_morphology(
4761 morphology_id,
4762 rule,
4763 npu_arc,
4764 &mut npu,
4765 src_area_id,
4766 dst_area_id,
4767 *src_idx,
4768 *dst_idx,
4769 weight,
4770 psp,
4771 synapse_attractivity,
4772 synapse_type,
4773 delay_bursts,
4774 )
4775 }
4776 feagi_evolutionary::MorphologyType::Vectors => {
4777 use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
4778
4779 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4781 crate::types::BduError::InvalidArea(format!(
4782 "Destination area not found: {}",
4783 dst_area_id
4784 ))
4785 })?;
4786
4787 let dst_dimensions = (
4788 dst_area.dimensions.width as usize,
4789 dst_area.dimensions.height as usize,
4790 dst_area.dimensions.depth as usize,
4791 );
4792
4793 if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
4794 morphology.parameters
4795 {
4796 let vectors_tuples: Vec<(i32, i32, i32)> =
4798 vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
4799
4800 let count = apply_vectors_morphology_with_dimensions(
4801 &mut npu,
4802 *src_idx,
4803 *dst_idx,
4804 vectors_tuples,
4805 dst_dimensions,
4806 weight, psp, synapse_attractivity, synapse_type,
4810 delay_bursts,
4811 )?;
4812 npu.rebuild_synapse_index();
4815 Ok(count as usize)
4816 } else {
4817 Ok(0)
4818 }
4819 }
4820 feagi_evolutionary::MorphologyType::Patterns => {
4821 use crate::connectivity::core_morphologies::apply_patterns_morphology;
4822 use crate::connectivity::rules::patterns::{
4823 Pattern3D, PatternElement as RulePatternElement,
4824 };
4825 use feagi_evolutionary::PatternElement as EvoPatternElement;
4826
4827 let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
4828 morphology.parameters
4829 else {
4830 return Ok(0);
4831 };
4832
4833 let convert_element =
4834 |element: &EvoPatternElement|
4835 -> crate::types::BduResult<RulePatternElement> {
4836 match element {
4837 EvoPatternElement::Value(value) => {
4838 if *value < 0 {
4839 return Err(crate::types::BduError::InvalidMorphology(
4840 format!(
4841 "Pattern morphology {} contains negative voxel coordinate {}",
4842 morphology_id, value
4843 ),
4844 ));
4845 }
4846 Ok(RulePatternElement::Exact(*value))
4847 }
4848 EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
4849 EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
4850 EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
4851 EvoPatternElement::DirectionPositive => {
4852 Ok(RulePatternElement::DirectionExclusive(
4853 crate::connectivity::rules::patterns::Direction::Positive,
4854 ))
4855 }
4856 EvoPatternElement::DirectionNegative => {
4857 Ok(RulePatternElement::DirectionExclusive(
4858 crate::connectivity::rules::patterns::Direction::Negative,
4859 ))
4860 }
4861 EvoPatternElement::DirectionPositiveInclusive => {
4862 Ok(RulePatternElement::DirectionInclusive(
4863 crate::connectivity::rules::patterns::Direction::Positive,
4864 ))
4865 }
4866 EvoPatternElement::DirectionNegativeInclusive => {
4867 Ok(RulePatternElement::DirectionInclusive(
4868 crate::connectivity::rules::patterns::Direction::Negative,
4869 ))
4870 }
4871 EvoPatternElement::Offset(off) => {
4872 Ok(RulePatternElement::Offset(*off))
4873 }
4874 EvoPatternElement::Range(lo, hi) => {
4875 Ok(RulePatternElement::Range(*lo, *hi))
4876 }
4877 EvoPatternElement::AbsoluteRange(lo, hi) => {
4878 Ok(RulePatternElement::AbsoluteRange(*lo, *hi))
4879 }
4880 }
4881 };
4882
4883 let mut converted_patterns = Vec::with_capacity(patterns.len());
4884 for pattern_pair in patterns {
4885 if pattern_pair.len() != 2 {
4886 return Err(crate::types::BduError::InvalidMorphology(format!(
4887 "Pattern morphology {} must contain [src, dst] pairs",
4888 morphology_id
4889 )));
4890 }
4891
4892 let src_pattern = &pattern_pair[0];
4893 let dst_pattern = &pattern_pair[1];
4894
4895 if src_pattern.len() != 3 || dst_pattern.len() != 3 {
4896 return Err(crate::types::BduError::InvalidMorphology(format!(
4897 "Pattern morphology {} requires 3-axis patterns",
4898 morphology_id
4899 )));
4900 }
4901
4902 let src: Pattern3D = (
4903 convert_element(&src_pattern[0])?,
4904 convert_element(&src_pattern[1])?,
4905 convert_element(&src_pattern[2])?,
4906 );
4907 let dst: Pattern3D = (
4908 convert_element(&dst_pattern[0])?,
4909 convert_element(&dst_pattern[1])?,
4910 convert_element(&dst_pattern[2])?,
4911 );
4912
4913 converted_patterns.push((src, dst));
4914 }
4915
4916 let count = apply_patterns_morphology(
4917 &mut npu,
4918 *src_idx,
4919 *dst_idx,
4920 converted_patterns,
4921 weight,
4922 psp,
4923 synapse_attractivity,
4924 synapse_type,
4925 delay_bursts,
4926 )?;
4927 if count > 0 {
4928 npu.rebuild_synapse_index();
4929 }
4930 Ok(count as usize)
4931 }
4932 feagi_evolutionary::MorphologyType::Composite => {
4933 let feagi_evolutionary::MorphologyParameters::Composite { .. } =
4934 morphology.parameters
4935 else {
4936 return Ok(0);
4937 };
4938
4939 if morphology_id != "tile" {
4940 use tracing::debug;
4941 debug!(
4942 target: "feagi-bdu",
4943 "Composite morphology {} not yet implemented",
4944 morphology_id
4945 );
4946 return Ok(0);
4947 }
4948
4949 let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
4950 crate::types::BduError::InvalidArea(format!(
4951 "Source area not found: {}",
4952 src_area_id
4953 ))
4954 })?;
4955 let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
4956 crate::types::BduError::InvalidArea(format!(
4957 "Destination area not found: {}",
4958 dst_area_id
4959 ))
4960 })?;
4961 let src_dimensions = (
4962 src_area.dimensions.width as usize,
4963 src_area.dimensions.height as usize,
4964 src_area.dimensions.depth as usize,
4965 );
4966 let dst_dimensions = (
4967 dst_area.dimensions.width as usize,
4968 dst_area.dimensions.height as usize,
4969 dst_area.dimensions.depth as usize,
4970 );
4971
4972 let count =
4973 crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
4974 &mut npu,
4975 *src_idx,
4976 *dst_idx,
4977 src_dimensions,
4978 dst_dimensions,
4979 weight,
4980 psp,
4981 synapse_attractivity,
4982 synapse_type,
4983 delay_bursts,
4984 )?;
4985 if count > 0 {
4986 npu.rebuild_synapse_index();
4987 }
4988 Ok(count as usize)
4989 }
4990 }
4991 } else {
4992 Ok(0) }
4994 }
4995
4996 pub fn set_npu(
5009 &mut self,
5010 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
5011 ) {
5012 self.npu = Some(Arc::clone(&npu));
5013 info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
5014
5015 #[cfg(not(feature = "wasm"))]
5018 {
5019 use feagi_state_manager::StateManager;
5020 let state_manager = StateManager::instance();
5021 let state_manager = state_manager.read();
5022 let core_state = state_manager.get_core_state();
5023 core_state.set_neuron_capacity(self.config.max_neurons as u32);
5025 core_state.set_synapse_capacity(self.config.max_synapses as u32);
5026 info!(
5027 target: "feagi-bdu",
5028 "📊 Updated State Manager with capacity: {} neurons, {} synapses",
5029 self.config.max_neurons, self.config.max_synapses
5030 );
5031 }
5032
5033 match self.sync_cortical_ids_to_npu() {
5040 Ok(existing_area_count) => {
5041 if existing_area_count > 0 {
5042 info!(
5043 target: "feagi-bdu",
5044 "Backfilled {} cortical area registrations into NPU",
5045 existing_area_count
5046 );
5047 }
5048 }
5049 Err(e) => {
5050 warn!(
5051 target: "feagi-bdu",
5052 "Failed to lock NPU for cortical area backfill registration: {}",
5053 e
5054 );
5055 }
5056 }
5057
5058 self.update_all_cached_stats();
5060 info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
5061 self.get_neuron_count(), self.get_synapse_count());
5062 }
5063
5064 pub fn sync_cortical_ids_to_npu(&self) -> BduResult<usize> {
5069 let npu = self
5070 .npu
5071 .as_ref()
5072 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
5073 let mut npu_lock = npu.lock().map_err(|e| {
5074 BduError::Internal(format!("Failed to lock NPU for cortical ID sync: {}", e))
5075 })?;
5076 let count = self.cortical_id_to_idx.len();
5077 for (cortical_id, cortical_idx) in &self.cortical_id_to_idx {
5078 npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
5079 }
5080 Ok(count)
5081 }
5082
5083 pub fn has_npu(&self) -> bool {
5085 self.npu.is_some()
5086 }
5087
5088 pub fn get_npu(
5095 &self,
5096 ) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
5097 {
5098 self.npu.as_ref()
5099 }
5100
5101 pub fn rebind_npu_runtime_after_connectome_apply(&mut self) -> BduResult<()> {
5107 #[cfg(feature = "plasticity")]
5108 if let Some(executor) = self.plasticity_executor.as_ref() {
5109 executor
5110 .lock()
5111 .map_err(|_| {
5112 BduError::Internal(
5113 "Failed to lock PlasticityExecutor for registration reset".to_string(),
5114 )
5115 })?
5116 .clear_memory_area_registrations()
5117 .map_err(BduError::Internal)?;
5118 }
5119 self.refresh_all_upstream_cortical_areas_from_mappings();
5120 self.rebuild_memory_twin_mappings()?;
5121 self.rebind_memory_twin_mappings_to_npu()?;
5122 self.rebind_stdp_mappings_to_npu()?;
5123 #[cfg(feature = "plasticity")]
5124 self.reregister_memory_areas_with_plasticity()?;
5125 Ok(())
5126 }
5127
5128 pub fn refresh_all_upstream_cortical_areas_from_mappings(&mut self) {
5134 let area_ids: Vec<CorticalID> = self.cortical_areas.keys().copied().collect();
5135 for area_id in area_ids {
5136 self.refresh_upstream_cortical_areas_from_mappings(&area_id);
5137 }
5138 }
5139
5140 pub fn rebuild_memory_twin_mappings(&mut self) -> BduResult<usize> {
5150 let jobs = self.collect_memory_twin_rebuild_jobs()?;
5151 let mut restored = 0usize;
5152 for (memory_id, upstream_id, known_twin) in jobs {
5153 match known_twin {
5154 Some(twin_id) => {
5155 self.restore_memory_twin_index(&memory_id, &upstream_id, &twin_id)?;
5156 restored += 1;
5157 }
5158 None => {
5159 self.ensure_memory_twin_area(&memory_id, &upstream_id)?;
5160 restored += 1;
5161 }
5162 }
5163 }
5164 if restored > 0 {
5165 info!(
5166 target: "feagi-bdu",
5167 "Rebuilt {} memory twin mapping(s) after connectome apply",
5168 restored
5169 );
5170 self.refresh_cortical_mappings_hash();
5171 }
5172 self.rebuild_scan_twin_mappings()?;
5173 Ok(restored)
5174 }
5175
5176 fn rebuild_scan_twin_mappings(&mut self) -> BduResult<usize> {
5177 let mut pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
5178 for (src_id, src_area) in &self.cortical_areas {
5179 if Self::area_is_memory(src_area) {
5180 continue;
5181 }
5182 let Some(dstmap) = src_area
5183 .properties
5184 .get("cortical_mapping_dst")
5185 .and_then(|value| value.as_object())
5186 else {
5187 continue;
5188 };
5189 for (dst_b64, rules) in dstmap {
5190 if !Self::mapping_rules_use_morphology(rules, "episodic_scan") {
5191 continue;
5192 }
5193 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
5194 continue;
5195 };
5196 pairs.push((memory_id, *src_id));
5197 }
5198 }
5199 let mut restored = 0usize;
5200 for (memory_id, field_id) in pairs {
5201 if self.ensure_scan_twin_area(&memory_id, &field_id).is_ok() {
5202 restored += 1;
5203 }
5204 }
5205 Ok(restored)
5206 }
5207
5208 fn collect_memory_twin_rebuild_jobs(
5209 &self,
5210 ) -> BduResult<Vec<(CorticalID, CorticalID, Option<CorticalID>)>> {
5211 let mut jobs: Vec<(CorticalID, CorticalID, Option<CorticalID>)> = Vec::new();
5212 let mut seen: HashSet<(CorticalID, CorticalID)> = HashSet::new();
5213
5214 for (memory_id, memory_area) in &self.cortical_areas {
5215 if !Self::area_is_memory(memory_area) {
5216 continue;
5217 }
5218 let Some(dstmap) = memory_area
5219 .properties
5220 .get("cortical_mapping_dst")
5221 .and_then(|value| value.as_object())
5222 else {
5223 continue;
5224 };
5225 for (dst_b64, rules) in dstmap {
5226 if !Self::mapping_rules_use_morphology(rules, "memory_replay") {
5227 continue;
5228 }
5229 let twin_id = match CorticalID::try_from_base_64(dst_b64) {
5230 Ok(id) => id,
5231 Err(_) => continue,
5232 };
5233 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
5234 continue;
5235 };
5236 let Some(upstream_b64) = twin_area
5237 .properties
5238 .get("memory_twin_of")
5239 .and_then(|value| value.as_str())
5240 else {
5241 continue;
5242 };
5243 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5244 BduError::InvalidArea(format!(
5245 "Invalid memory_twin_of '{}' on {}: {}",
5246 upstream_b64,
5247 twin_id.as_base_64(),
5248 error
5249 ))
5250 })?;
5251 if seen.insert((*memory_id, upstream_id)) {
5252 jobs.push((*memory_id, upstream_id, Some(twin_id)));
5253 }
5254 }
5255 }
5256
5257 for (twin_id, twin_area) in &self.cortical_areas {
5258 let Some(upstream_b64) = twin_area
5259 .properties
5260 .get("memory_twin_of")
5261 .and_then(|value| value.as_str())
5262 else {
5263 continue;
5264 };
5265 let Some(memory_b64) = twin_area
5266 .properties
5267 .get("memory_twin_for")
5268 .and_then(|value| value.as_str())
5269 else {
5270 continue;
5271 };
5272 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5273 BduError::InvalidArea(format!(
5274 "Invalid memory_twin_of '{}' on {}: {}",
5275 upstream_b64,
5276 twin_id.as_base_64(),
5277 error
5278 ))
5279 })?;
5280 let memory_id = CorticalID::try_from_base_64(memory_b64).map_err(|error| {
5281 BduError::InvalidArea(format!(
5282 "Invalid memory_twin_for '{}' on {}: {}",
5283 memory_b64,
5284 twin_id.as_base_64(),
5285 error
5286 ))
5287 })?;
5288 if !self.cortical_areas.contains_key(&memory_id)
5289 || !self.cortical_areas.contains_key(&upstream_id)
5290 {
5291 continue;
5292 }
5293 if seen.insert((memory_id, upstream_id)) {
5294 jobs.push((memory_id, upstream_id, Some(*twin_id)));
5295 }
5296 }
5297
5298 let mut episodic_pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
5299 for (src_id, src_area) in &self.cortical_areas {
5300 if Self::area_is_memory(src_area) {
5301 continue;
5302 }
5303 let Some(dstmap) = src_area
5304 .properties
5305 .get("cortical_mapping_dst")
5306 .and_then(|value| value.as_object())
5307 else {
5308 continue;
5309 };
5310 for (dst_b64, rules) in dstmap {
5311 if !Self::mapping_rules_use_morphology(rules, "episodic_memory") {
5312 continue;
5313 }
5314 let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
5315 continue;
5316 };
5317 let Some(memory_area) = self.cortical_areas.get(&memory_id) else {
5318 continue;
5319 };
5320 if !Self::area_is_memory(memory_area) {
5321 continue;
5322 }
5323 episodic_pairs.push((memory_id, *src_id));
5324 }
5325 }
5326
5327 for (memory_id, upstream_id) in episodic_pairs {
5328 if !seen.insert((memory_id, upstream_id)) {
5329 continue;
5330 }
5331 let indexed_twin = self
5332 .cortical_areas
5333 .get(&memory_id)
5334 .and_then(|area| area.properties.get("memory_twin_areas"))
5335 .and_then(|value| value.as_object())
5336 .and_then(|map| map.get(&upstream_id.as_base_64()))
5337 .and_then(|value| value.as_str())
5338 .and_then(|twin_b64| CorticalID::try_from_base_64(twin_b64).ok());
5339 if let Some(twin_id) = indexed_twin {
5340 jobs.push((memory_id, upstream_id, Some(twin_id)));
5341 continue;
5342 }
5343 if let Ok(hash_twin_id) = self.build_memory_twin_id(&memory_id, &upstream_id) {
5344 if self.cortical_areas.contains_key(&hash_twin_id) {
5345 jobs.push((memory_id, upstream_id, Some(hash_twin_id)));
5346 continue;
5347 }
5348 }
5349 jobs.push((memory_id, upstream_id, None));
5350 }
5351
5352 Ok(jobs)
5353 }
5354
5355 fn restore_memory_twin_index(
5356 &mut self,
5357 memory_area_id: &CorticalID,
5358 upstream_area_id: &CorticalID,
5359 twin_id: &CorticalID,
5360 ) -> BduResult<()> {
5361 let expected_source = upstream_area_id.as_base_64();
5362 let expected_target = memory_area_id.as_base_64();
5363 let Some(existing) = self.cortical_areas.get_mut(twin_id) else {
5364 return Err(BduError::InvalidArea(format!(
5365 "Twin area {} not found while restoring memory twin index",
5366 twin_id.as_base_64()
5367 )));
5368 };
5369 let existing_source = existing
5370 .properties
5371 .get("memory_twin_of")
5372 .and_then(|value| value.as_str())
5373 .map(ToString::to_string);
5374 let existing_target = existing
5375 .properties
5376 .get("memory_twin_for")
5377 .and_then(|value| value.as_str())
5378 .map(ToString::to_string);
5379 if existing_source.as_deref() != Some(expected_source.as_str())
5380 || existing_target.as_deref() != Some(expected_target.as_str())
5381 {
5382 existing.properties.insert(
5383 "memory_twin_of".to_string(),
5384 serde_json::json!(expected_source),
5385 );
5386 existing.properties.insert(
5387 "memory_twin_for".to_string(),
5388 serde_json::json!(expected_target),
5389 );
5390 }
5391 self.set_memory_twin_mapping(memory_area_id, upstream_area_id, twin_id);
5392
5393 let has_replay = self
5394 .cortical_areas
5395 .get(memory_area_id)
5396 .and_then(|area| area.properties.get("cortical_mapping_dst"))
5397 .and_then(|value| value.as_object())
5398 .and_then(|map| map.get(&twin_id.as_base_64()))
5399 .is_some_and(|rules| Self::mapping_rules_use_morphology(rules, "memory_replay"));
5400 if !has_replay {
5401 self.ensure_memory_replay_mapping(memory_area_id, twin_id)?;
5402 }
5403 Ok(())
5404 }
5405
5406 fn area_is_memory(area: &CorticalArea) -> bool {
5407 matches!(area.cortical_type, CorticalAreaType::Memory(_))
5408 || area
5409 .properties
5410 .get("is_mem_type")
5411 .and_then(|value| value.as_bool())
5412 == Some(true)
5413 }
5414
5415 fn mapping_rules_use_morphology(rules: &serde_json::Value, morphology_id: &str) -> bool {
5416 rules.as_array().is_some_and(|arr| {
5417 arr.iter().any(|rule| {
5418 rule.as_object()
5419 .and_then(|obj| obj.get("morphology_id"))
5420 .and_then(|value| value.as_str())
5421 == Some(morphology_id)
5422 })
5423 })
5424 }
5425
5426 fn rebind_memory_twin_mappings_to_npu(&mut self) -> BduResult<()> {
5427 use crate::models::CorticalAreaExt;
5428
5429 let Some(npu_arc) = self.npu.clone() else {
5430 return Ok(());
5431 };
5432 let mut bindings: Vec<(u32, u32, u32, f32)> = Vec::new();
5433 for (memory_id, area) in &self.cortical_areas {
5434 if !matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
5435 continue;
5436 }
5437 let Some(twins) = area
5438 .properties
5439 .get("memory_twin_areas")
5440 .and_then(|value| value.as_object())
5441 else {
5442 continue;
5443 };
5444 let Some(&memory_idx) = self.cortical_id_to_idx.get(memory_id) else {
5445 continue;
5446 };
5447 for (upstream_b64, twin_val) in twins {
5448 let Some(twin_b64) = twin_val.as_str() else {
5449 return Err(BduError::InvalidArea(format!(
5450 "memory_twin_areas entry for {} is not a string",
5451 upstream_b64
5452 )));
5453 };
5454 let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
5455 BduError::InvalidArea(format!(
5456 "Invalid upstream cortical ID {}: {}",
5457 upstream_b64, error
5458 ))
5459 })?;
5460 let twin_id = CorticalID::try_from_base_64(twin_b64).map_err(|error| {
5461 BduError::InvalidArea(format!(
5462 "Invalid twin cortical ID {}: {}",
5463 twin_b64, error
5464 ))
5465 })?;
5466 let Some(&upstream_idx) = self.cortical_id_to_idx.get(&upstream_id) else {
5467 continue;
5468 };
5469 let Some(&twin_idx) = self.cortical_id_to_idx.get(&twin_id) else {
5470 continue;
5471 };
5472 let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
5473 continue;
5474 };
5475 let potential =
5476 twin_area.firing_threshold() + twin_area.firing_threshold_increment();
5477 bindings.push((memory_idx, upstream_idx, twin_idx, potential));
5478 }
5479 }
5480 let mut npu = npu_arc.lock().unwrap();
5481 for (memory_idx, upstream_idx, twin_idx, potential) in bindings {
5482 npu.register_memory_twin_mapping(memory_idx, upstream_idx, twin_idx, potential);
5483 }
5484 Ok(())
5485 }
5486
5487 fn rebind_stdp_mappings_to_npu(&mut self) -> BduResult<()> {
5488 let Some(npu_arc) = self.npu.clone() else {
5489 return Ok(());
5490 };
5491 let mut jobs: Vec<(CorticalID, CorticalID, u32, u32, Vec<serde_json::Value>)> = Vec::new();
5492 for (src_id, src_area) in &self.cortical_areas {
5493 let Some(dst_map) = src_area
5494 .properties
5495 .get("cortical_mapping_dst")
5496 .and_then(|value| value.as_object())
5497 else {
5498 continue;
5499 };
5500 let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
5501 continue;
5502 };
5503 for (dst_b64, rules) in dst_map {
5504 let dst_id = CorticalID::try_from_base_64(dst_b64).map_err(|error| {
5505 BduError::InvalidArea(format!(
5506 "Invalid destination cortical ID {}: {}",
5507 dst_b64, error
5508 ))
5509 })?;
5510 let Some(&dst_idx) = self.cortical_id_to_idx.get(&dst_id) else {
5511 continue;
5512 };
5513 let Some(rules_arr) = rules.as_array() else {
5514 continue;
5515 };
5516 jobs.push((*src_id, dst_id, src_idx, dst_idx, rules_arr.clone()));
5517 }
5518 }
5519
5520 for (src_id, dst_id, src_idx, dst_idx, rules) in jobs {
5521 for rule in &rules {
5522 let morphology_id = rule
5523 .as_object()
5524 .and_then(|obj| obj.get("morphology_id"))
5525 .and_then(|value| value.as_str())
5526 .unwrap_or("");
5527 let mut plasticity_flag = rule
5528 .as_object()
5529 .and_then(|obj| obj.get("plasticity_flag"))
5530 .and_then(|value| value.as_bool())
5531 .unwrap_or(false);
5532 if morphology_id == "associative_memory" {
5533 plasticity_flag = true;
5534 }
5535 if !plasticity_flag {
5536 continue;
5537 }
5538 let Some(rule_obj) = rule.as_object() else {
5539 return Err(BduError::InvalidMorphology(
5540 "Plasticity mapping rule must be an object format".to_string(),
5541 ));
5542 };
5543 let (_weight, psp, synapse_type, _delay) =
5544 self.resolve_synapse_params_for_rule(&src_id, rule)?;
5545 Self::register_stdp_mapping_for_rule(
5546 &npu_arc,
5547 &src_id,
5548 &dst_id,
5549 src_idx,
5550 dst_idx,
5551 rule_obj,
5552 false,
5553 psp,
5554 synapse_type,
5555 )?;
5556 }
5557 }
5558 Ok(())
5559 }
5560
5561 #[cfg(feature = "plasticity")]
5562 fn reregister_memory_areas_with_plasticity(&mut self) -> BduResult<()> {
5563 use feagi_evolutionary::extract_memory_properties;
5564 use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
5565
5566 let Some(executor) = self.plasticity_executor.clone() else {
5567 return Ok(());
5568 };
5569 let memory_ids: Vec<CorticalID> = self
5570 .cortical_areas
5571 .iter()
5572 .filter(|(_, area)| matches!(area.cortical_type, CorticalAreaType::Memory(_)))
5573 .map(|(id, _)| *id)
5574 .collect();
5575
5576 for memory_id in memory_ids {
5577 let Some(area) = self.cortical_areas.get(&memory_id) else {
5578 continue;
5579 };
5580 let Some(mem_props) = extract_memory_properties(&area.properties) else {
5581 continue;
5582 };
5583 let Some(&area_idx) = self.cortical_id_to_idx.get(&memory_id) else {
5584 continue;
5585 };
5586 let area_name = memory_id.as_base_64();
5587 let upstream_areas = self.get_episodic_memory_upstream_cortical_areas(&memory_id);
5588 let lifecycle_config = MemoryNeuronLifecycleConfig {
5589 initial_lifespan: mem_props.init_lifespan,
5590 lifespan_growth_rate: mem_props.lifespan_growth_rate,
5591 longterm_threshold: mem_props.longterm_threshold,
5592 max_reactivations: 1000,
5593 };
5594 let exec = executor.lock().map_err(|_| {
5595 BduError::Internal(
5596 "Failed to lock PlasticityExecutor for memory-area rebind".to_string(),
5597 )
5598 })?;
5599 exec.register_memory_area(
5600 area_idx,
5601 area_name,
5602 mem_props.temporal_depth,
5603 upstream_areas,
5604 Some(lifecycle_config),
5605 mem_props.mp_learning_enabled,
5606 );
5607 self.configure_memory_scan_on_executor(&*exec, &memory_id);
5608 }
5609 Ok(())
5610 }
5611
5612 #[cfg(feature = "plasticity")]
5613 pub(crate) fn configure_memory_scan_on_executor(
5614 &self,
5615 exec: &dyn feagi_npu_plasticity::PlasticityExecutor,
5616 memory_id: &CorticalID,
5617 ) {
5618 let Some(scan) = self.build_memory_scan_config(memory_id) else {
5619 if let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) {
5620 exec.configure_memory_scan(area_idx, None);
5621 }
5622 return;
5623 };
5624 let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) else {
5625 return;
5626 };
5627 exec.configure_memory_scan(area_idx, Some(scan));
5628 }
5629
5630 #[cfg(feature = "plasticity")]
5631 fn build_memory_scan_config(
5632 &self,
5633 memory_id: &CorticalID,
5634 ) -> Option<feagi_npu_plasticity::MemoryScanConfig> {
5635 use feagi_evolutionary::extract_memory_properties;
5636 use feagi_npu_plasticity::{MemoryScanConfig, MemoryScanSource, ScanKernel};
5637
5638 let memory_area = self.cortical_areas.get(memory_id)?;
5639 let mem_props = extract_memory_properties(&memory_area.properties)?;
5640 let training_mode = memory_area
5641 .properties
5642 .get("classifier_training_mode")
5643 .and_then(|value| value.as_str())
5644 .unwrap_or("kernel");
5645 if training_mode == "scanner" {
5646 return self.build_scanner_memory_scan_config(memory_id, memory_area, &mem_props);
5647 }
5648 if training_mode != "kernel" {
5649 return None;
5650 }
5651 let kernel_b64 = memory_area
5652 .properties
5653 .get("classifier_kernel_area_id")
5654 .and_then(|v| v.as_str())?;
5655 let class_b64 = memory_area
5656 .properties
5657 .get("classifier_class_area_id")
5658 .and_then(|v| v.as_str())?;
5659 let class_mem_b64 = memory_area
5660 .properties
5661 .get("classifier_class_memory_id")
5662 .and_then(|v| v.as_str())?;
5663 let kernel_id = CorticalID::try_from_base_64(kernel_b64).ok()?;
5664 let class_id = CorticalID::try_from_base_64(class_b64).ok()?;
5665 let class_mem_id = CorticalID::try_from_base_64(class_mem_b64).ok()?;
5666 if !self.mapping_from_src_to_dst_has_associative(memory_id, &class_mem_id) {
5667 return None;
5668 }
5669 let kernel_area = self.cortical_areas.get(&kernel_id)?;
5670 let class_area = self.cortical_areas.get(&class_id)?;
5671 let class_memory_area_idx = *self.cortical_id_to_idx.get(&class_mem_id)?;
5672 let class_channel_count = class_area
5673 .dimensions
5674 .width
5675 .saturating_mul(class_area.dimensions.height)
5676 .saturating_mul(class_area.dimensions.depth);
5677 if class_channel_count == 0 {
5678 return None;
5679 }
5680 let scan_fields = self.get_episodic_scan_upstream_cortical_areas(memory_id);
5681 if scan_fields.is_empty() {
5682 return None;
5683 }
5684 let twin_map = memory_area
5685 .properties
5686 .get("memory_twin_areas")
5687 .and_then(|v| v.as_object())?;
5688 let mut sources = Vec::new();
5689 for field_idx in scan_fields {
5690 let field_id = *self.cortical_idx_to_id.get(&field_idx)?;
5691 let field_area = self.cortical_areas.get(&field_id)?;
5692 let twin_b64 = twin_map.get(&field_id.as_base_64())?.as_str()?;
5693 let twin_id = CorticalID::try_from_base_64(twin_b64).ok()?;
5694 let twin_idx = *self.cortical_id_to_idx.get(&twin_id)?;
5695 sources.push(MemoryScanSource {
5696 field_area_idx: field_idx,
5697 twin_area_idx: twin_idx,
5698 field_width: field_area.dimensions.width,
5699 field_height: field_area.dimensions.height,
5700 field_depth: field_area.dimensions.depth,
5701 });
5702 }
5703 if sources.is_empty() {
5704 return None;
5705 }
5706 Some(MemoryScanConfig {
5707 kernel: ScanKernel {
5708 width: kernel_area.dimensions.width,
5709 height: kernel_area.dimensions.height,
5710 depth: kernel_area.dimensions.depth,
5711 },
5712 min_window_activity: mem_props.min_window_activity,
5713 scan_skip_density: mem_props.scan_skip_density,
5714 class_channel_count,
5715 class_area_width: class_area.dimensions.width,
5716 class_area_height: class_area.dimensions.height,
5717 class_memory_area_idx,
5718 sources,
5719 scanner_mask: None,
5720 })
5721 }
5722
5723 #[cfg(feature = "plasticity")]
5724 fn build_scanner_memory_scan_config(
5725 &self,
5726 memory_id: &CorticalID,
5727 memory_area: &CorticalArea,
5728 mem_props: &feagi_evolutionary::MemoryAreaProperties,
5729 ) -> Option<feagi_npu_plasticity::MemoryScanConfig> {
5730 use feagi_npu_plasticity::{
5731 MemoryScanConfig, MemoryScanSource, ScanKernel, ScannerMaskSource,
5732 };
5733
5734 let kernel_size = memory_area
5735 .properties
5736 .get("classifier_kernel_size")
5737 .and_then(|value| value.as_array())
5738 .filter(|values| values.len() == 3)?;
5739 let kernel = ScanKernel {
5740 width: kernel_size[0].as_u64().filter(|axis| *axis > 0)? as u32,
5741 height: kernel_size[1].as_u64().filter(|axis| *axis > 0)? as u32,
5742 depth: kernel_size[2].as_u64().filter(|axis| *axis > 0)? as u32,
5743 };
5744 let mask_b64 = memory_area
5745 .properties
5746 .get("classifier_mask_area_id")
5747 .and_then(|value| value.as_str())?;
5748 let class_mem_b64 = memory_area
5749 .properties
5750 .get("classifier_class_memory_id")
5751 .and_then(|value| value.as_str())?;
5752 let mask_id = CorticalID::try_from_base_64(mask_b64).ok()?;
5753 let class_mem_id = CorticalID::try_from_base_64(class_mem_b64).ok()?;
5754 if !self.mapping_from_src_to_dst_has_associative(memory_id, &class_mem_id) {
5755 return None;
5756 }
5757 let mask_area = self.cortical_areas.get(&mask_id)?;
5758 let class_memory_area_idx = *self.cortical_id_to_idx.get(&class_mem_id)?;
5759 let class_channel_count = mask_area.dimensions.depth;
5760 if class_channel_count == 0 {
5761 return None;
5762 }
5763 let scan_fields = self.get_episodic_scan_upstream_cortical_areas(memory_id);
5764 if scan_fields.is_empty() {
5765 return None;
5766 }
5767 let twin_map = memory_area
5768 .properties
5769 .get("memory_twin_areas")
5770 .and_then(|value| value.as_object())?;
5771 let mut sources = Vec::new();
5772 for field_idx in scan_fields {
5773 let field_id = *self.cortical_idx_to_id.get(&field_idx)?;
5774 let field_area = self.cortical_areas.get(&field_id)?;
5775 let twin_b64 = twin_map.get(&field_id.as_base_64())?.as_str()?;
5776 let twin_id = CorticalID::try_from_base_64(twin_b64).ok()?;
5777 let twin_idx = *self.cortical_id_to_idx.get(&twin_id)?;
5778 sources.push(MemoryScanSource {
5779 field_area_idx: field_idx,
5780 twin_area_idx: twin_idx,
5781 field_width: field_area.dimensions.width,
5782 field_height: field_area.dimensions.height,
5783 field_depth: field_area.dimensions.depth,
5784 });
5785 }
5786 if sources.is_empty() {
5787 return None;
5788 }
5789 Some(MemoryScanConfig {
5790 kernel,
5791 min_window_activity: mem_props.min_window_activity,
5792 scan_skip_density: mem_props.scan_skip_density,
5793 class_channel_count,
5794 class_area_width: 1,
5795 class_area_height: 1,
5796 class_memory_area_idx,
5797 sources,
5798 scanner_mask: Some(ScannerMaskSource {
5799 mask_area_idx: *self.cortical_id_to_idx.get(&mask_id)?,
5800 mask_width: mask_area.dimensions.width,
5801 mask_height: mask_area.dimensions.height,
5802 mask_depth: mask_area.dimensions.depth,
5803 }),
5804 })
5805 }
5806
5807 #[cfg(feature = "plasticity")]
5808 fn mapping_from_src_to_dst_has_associative(
5809 &self,
5810 src_area_id: &CorticalID,
5811 dst_area_id: &CorticalID,
5812 ) -> bool {
5813 let Some(src_area) = self.cortical_areas.get(src_area_id) else {
5814 return false;
5815 };
5816 let Some(mapping_dst) = src_area
5817 .properties
5818 .get("cortical_mapping_dst")
5819 .and_then(|v| v.as_object())
5820 else {
5821 return false;
5822 };
5823 let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
5824 return false;
5825 };
5826 Self::mapping_has_morphology_rule(rules, "associative_memory")
5827 }
5828
5829 #[cfg(feature = "plasticity")]
5832 pub fn set_plasticity_executor(
5833 &mut self,
5834 executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
5835 ) {
5836 if let Ok(mut exec) = executor.lock() {
5837 use feagi_npu_plasticity::executor::PlasticityExecutor;
5838 if !exec.is_running() {
5839 exec.start();
5840 }
5841 } else {
5842 warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
5843 }
5844 self.plasticity_executor = Some(executor);
5845 info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
5846 }
5847
5848 #[cfg(feature = "plasticity")]
5850 pub fn get_plasticity_executor(
5851 &self,
5852 ) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
5853 self.plasticity_executor.as_ref()
5854 }
5855
5856 pub fn get_neuron_capacity(&self) -> usize {
5868 self.config.max_neurons
5870 }
5871
5872 pub fn get_synapse_capacity(&self) -> usize {
5884 self.config.max_synapses
5886 }
5887
5888 pub fn update_fatigue_index(&self) -> Option<u8> {
5903 let mut last_calc = match self.last_fatigue_calculation.lock() {
5905 Ok(guard) => guard,
5906 Err(_) => return None, };
5908
5909 let now = std::time::Instant::now();
5910 if now.duration_since(*last_calc).as_secs() < 2 {
5911 return None; }
5913 *last_calc = now;
5914 drop(last_calc);
5915
5916 let regular_neuron_count = self.get_neuron_count();
5918 let regular_neuron_capacity = self.get_neuron_capacity();
5919 let regular_neuron_util = if regular_neuron_capacity > 0 {
5920 ((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
5921 } else {
5922 0
5923 };
5924
5925 let memory_neuron_util = match StateManager::instance().try_read() {
5929 Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
5930 None => {
5931 return None;
5933 }
5934 };
5935
5936 let synapse_count = self.get_synapse_count();
5938 let synapse_capacity = self.get_synapse_capacity();
5939 let synapse_util = if synapse_capacity > 0 {
5940 ((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
5941 } else {
5942 0
5943 };
5944
5945 let fatigue_index = regular_neuron_util
5947 .max(memory_neuron_util)
5948 .max(synapse_util);
5949
5950 let current_fatigue_active = {
5952 StateManager::instance()
5954 .try_read()
5955 .map(|m| m.get_core_state().is_fatigue_active())
5956 .unwrap_or(false)
5957 };
5958
5959 let new_fatigue_active = if fatigue_index >= 85 {
5960 true
5961 } else if fatigue_index < 80 {
5962 false
5963 } else {
5964 current_fatigue_active };
5966
5967 if let Some(state_manager) = StateManager::instance().try_write() {
5971 let core_state = state_manager.get_core_state();
5972 core_state.set_fatigue_index(fatigue_index);
5973 core_state.set_fatigue_active(new_fatigue_active);
5974 core_state.set_regular_neuron_util(regular_neuron_util);
5975 core_state.set_memory_neuron_util(memory_neuron_util);
5976 core_state.set_synapse_util(synapse_util);
5977 } else {
5978 trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
5980 }
5981
5982 if let Some(ref npu) = self.npu {
5984 if let Ok(mut npu_lock) = npu.lock() {
5985 npu_lock.set_fatigue_active(new_fatigue_active);
5986 }
5987 }
5988
5989 trace!(
5990 target: "feagi-bdu",
5991 "[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
5992 fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
5993 );
5994
5995 Some(fatigue_index)
5996 }
5997
5998 pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
6015 let area = self
6017 .cortical_areas
6018 .get(cortical_id)
6019 .ok_or_else(|| {
6020 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
6021 })?
6022 .clone();
6023
6024 let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
6026 BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
6027 })?;
6028
6029 let npu = self
6031 .npu
6032 .as_ref()
6033 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6034
6035 use crate::models::CorticalAreaExt;
6038 let per_voxel_cnt = area.neurons_per_voxel();
6039 let firing_threshold = area.firing_threshold();
6040 let firing_threshold_increment_x = area.firing_threshold_increment_x();
6041 let firing_threshold_increment_y = area.firing_threshold_increment_y();
6042 let firing_threshold_increment_z = area.firing_threshold_increment_z();
6043 let firing_threshold_limit_raw = area.firing_threshold_limit();
6045 let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
6046 f32::MAX } else {
6048 firing_threshold_limit_raw
6049 };
6050
6051 if firing_threshold_increment_x != 0.0
6053 || firing_threshold_increment_y != 0.0
6054 || firing_threshold_increment_z != 0.0
6055 {
6056 info!(
6057 target: "feagi-bdu",
6058 "🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
6059 cortical_id.as_base_64(),
6060 firing_threshold_increment_x,
6061 firing_threshold_increment_y,
6062 firing_threshold_increment_z
6063 );
6064 } else {
6065 if area.properties.contains_key("firing_threshold_increment_x")
6067 || area.properties.contains_key("firing_threshold_increment_y")
6068 || area.properties.contains_key("firing_threshold_increment_z")
6069 {
6070 info!(
6071 target: "feagi-bdu",
6072 "🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
6073 cortical_id.as_base_64(),
6074 area.properties.get("firing_threshold_increment_x"),
6075 area.properties.get("firing_threshold_increment_y"),
6076 area.properties.get("firing_threshold_increment_z")
6077 );
6078 }
6079 }
6080
6081 let leak_coefficient = area.leak_coefficient();
6082 let excitability = area.neuron_excitability();
6083 let refractory_period = area.refractory_period();
6084 let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
6086 let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
6087 u16::MAX } else {
6089 consecutive_fire_limit_raw
6090 };
6091 let snooze_length = area.snooze_period();
6092 let mp_charge_accumulation = area.mp_charge_accumulation();
6093
6094 let voxels = area.dimensions.width as usize
6096 * area.dimensions.height as usize
6097 * area.dimensions.depth as usize;
6098 let expected_neurons = voxels * per_voxel_cnt as usize;
6099
6100 trace!(
6101 target: "feagi-bdu",
6102 "Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
6103 cortical_id.as_base_64(),
6104 area.dimensions.width,
6105 area.dimensions.height,
6106 area.dimensions.depth,
6107 per_voxel_cnt,
6108 expected_neurons
6109 );
6110
6111 let neuron_count: u32 = {
6116 let mut npu_lock = npu
6117 .lock()
6118 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6119 npu_lock
6120 .create_cortical_area_neurons(
6121 *cortical_idx,
6122 area.dimensions.width,
6123 area.dimensions.height,
6124 area.dimensions.depth,
6125 per_voxel_cnt,
6126 firing_threshold,
6127 firing_threshold_increment_x,
6128 firing_threshold_increment_y,
6129 firing_threshold_increment_z,
6130 firing_threshold_limit,
6131 leak_coefficient,
6132 0.0, 0, refractory_period,
6135 excitability,
6136 consecutive_fire_limit,
6137 snooze_length,
6138 mp_charge_accumulation,
6139 )
6140 .map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
6141 };
6142
6143 trace!(
6144 target: "feagi-bdu",
6145 "Created {} neurons for area {} via NPU",
6146 neuron_count,
6147 cortical_id.as_base_64()
6148 );
6149
6150 {
6153 let mut cache = self.cached_neuron_counts_per_area.write();
6154 cache
6155 .entry(*cortical_id)
6156 .or_insert_with(|| AtomicUsize::new(0))
6157 .store(neuron_count as usize, Ordering::Relaxed);
6158 }
6159
6160 let state_manager = StateManager::instance();
6162 let state_manager = state_manager.read();
6163 state_manager
6164 .set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
6165
6166 self.cached_neuron_count
6168 .fetch_add(neuron_count as usize, Ordering::Relaxed);
6169
6170 let state_manager = StateManager::instance();
6172 let state_manager = state_manager.read();
6173 let core_state = state_manager.get_core_state();
6174 core_state.add_neuron_count(neuron_count);
6175 core_state.add_regular_neuron_count(neuron_count);
6176
6177 if let Some(npu) = &self.npu {
6179 if let Ok(mut npl) = npu.lock() {
6180 if let Some(v) = area.properties.get("rate_modulated_leak") {
6181 use crate::models::CorticalAreaExt;
6182 let idxs: Vec<usize> = npl
6183 .get_neurons_in_cortical_area(*cortical_idx)
6184 .into_iter()
6185 .map(|id| id as usize)
6186 .collect();
6187 npl.sync_rate_modulated_leak_from_cortical_property(
6188 *cortical_idx,
6189 v,
6190 area.leak_coefficient(),
6191 idxs,
6192 );
6193 } else {
6194 npl.remove_rate_modulated_leak(*cortical_idx);
6195 }
6196 }
6197 }
6198
6199 Ok(neuron_count)
6207 }
6208
6209 #[allow(clippy::too_many_arguments)]
6233 pub fn add_neuron(
6234 &mut self,
6235 cortical_id: &CorticalID,
6236 x: u32,
6237 y: u32,
6238 z: u32,
6239 firing_threshold: f32,
6240 firing_threshold_limit: f32,
6241 leak_coefficient: f32,
6242 resting_potential: f32,
6243 neuron_type: u8,
6244 refractory_period: u16,
6245 excitability: f32,
6246 consecutive_fire_limit: u16,
6247 snooze_length: u16,
6248 mp_charge_accumulation: bool,
6249 ) -> BduResult<u64> {
6250 self.add_neuron_with_area_stats(
6251 cortical_id,
6252 x,
6253 y,
6254 z,
6255 firing_threshold,
6256 firing_threshold_limit,
6257 leak_coefficient,
6258 resting_potential,
6259 neuron_type,
6260 refractory_period,
6261 excitability,
6262 consecutive_fire_limit,
6263 snooze_length,
6264 mp_charge_accumulation,
6265 true,
6266 )
6267 }
6268
6269 #[allow(clippy::too_many_arguments)]
6275 pub fn add_auxiliary_neuron(
6276 &mut self,
6277 cortical_id: &CorticalID,
6278 x: u32,
6279 y: u32,
6280 z: u32,
6281 firing_threshold: f32,
6282 firing_threshold_limit: f32,
6283 leak_coefficient: f32,
6284 resting_potential: f32,
6285 neuron_type: u8,
6286 refractory_period: u16,
6287 excitability: f32,
6288 consecutive_fire_limit: u16,
6289 snooze_length: u16,
6290 mp_charge_accumulation: bool,
6291 ) -> BduResult<u64> {
6292 self.add_neuron_with_area_stats(
6293 cortical_id,
6294 x,
6295 y,
6296 z,
6297 firing_threshold,
6298 firing_threshold_limit,
6299 leak_coefficient,
6300 resting_potential,
6301 neuron_type,
6302 refractory_period,
6303 excitability,
6304 consecutive_fire_limit,
6305 snooze_length,
6306 mp_charge_accumulation,
6307 false,
6308 )
6309 }
6310
6311 #[allow(clippy::too_many_arguments)]
6312 fn add_neuron_with_area_stats(
6313 &mut self,
6314 cortical_id: &CorticalID,
6315 x: u32,
6316 y: u32,
6317 z: u32,
6318 firing_threshold: f32,
6319 firing_threshold_limit: f32,
6320 leak_coefficient: f32,
6321 resting_potential: f32,
6322 neuron_type: u8,
6323 refractory_period: u16,
6324 excitability: f32,
6325 consecutive_fire_limit: u16,
6326 snooze_length: u16,
6327 mp_charge_accumulation: bool,
6328 update_area_stats: bool,
6329 ) -> BduResult<u64> {
6330 if !self.cortical_areas.contains_key(cortical_id) {
6332 return Err(BduError::InvalidArea(format!(
6333 "Cortical area {} not found",
6334 cortical_id
6335 )));
6336 }
6337
6338 let cortical_idx = *self
6339 .cortical_id_to_idx
6340 .get(cortical_id)
6341 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
6342
6343 let npu = self
6345 .npu
6346 .as_ref()
6347 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6348
6349 let mut npu_lock = npu
6350 .lock()
6351 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6352
6353 let neuron_id = npu_lock
6355 .add_neuron(
6356 firing_threshold,
6357 firing_threshold_limit,
6358 leak_coefficient,
6359 resting_potential,
6360 neuron_type as i32,
6361 refractory_period,
6362 excitability,
6363 consecutive_fire_limit,
6364 snooze_length,
6365 mp_charge_accumulation,
6366 cortical_idx,
6367 x,
6368 y,
6369 z,
6370 )
6371 .map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
6372
6373 trace!(
6374 target: "feagi-bdu",
6375 "Created neuron {} in area {} at ({}, {}, {})",
6376 neuron_id.0,
6377 cortical_id,
6378 x,
6379 y,
6380 z
6381 );
6382
6383 let state_manager = StateManager::instance();
6385 let state_manager = state_manager.read();
6386 let core_state = state_manager.get_core_state();
6387 core_state.add_neuron_count(1);
6388 core_state.add_regular_neuron_count(1);
6389 if update_area_stats {
6390 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
6391 }
6392
6393 Ok(neuron_id.0 as u64)
6394 }
6395
6396 pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
6407 let npu = self
6409 .npu
6410 .as_ref()
6411 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
6412
6413 let mut npu_lock = npu
6414 .lock()
6415 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
6416
6417 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
6418 let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
6419
6420 let deleted = npu_lock.delete_neuron(neuron_id as u32);
6421
6422 if deleted {
6423 trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
6424
6425 let state_manager = StateManager::instance();
6427 let state_manager = state_manager.read();
6428 let core_state = state_manager.get_core_state();
6429 core_state.subtract_neuron_count(1);
6430 core_state.subtract_regular_neuron_count(1);
6431 if let Some(cortical_id) = cortical_id {
6432 state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
6433 }
6434
6435 }
6439
6440 Ok(deleted)
6441 }
6442
6443 pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
6457 let src_area = self
6459 .cortical_areas
6460 .get(src_cortical_id)
6461 .ok_or_else(|| {
6462 BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
6463 })?
6464 .clone();
6465
6466 let dstmap = match src_area.properties.get("cortical_mapping_dst") {
6468 Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
6469 _ => return Ok(0), };
6471
6472 let src_cortical_idx = *self
6473 .cortical_id_to_idx
6474 .get(src_cortical_id)
6475 .ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
6476
6477 let mut total_synapses = 0u32;
6478 let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new(); for (dst_cortical_id_str, _rules) in dstmap {
6482 let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
6484 Ok(id) => id,
6485 Err(_) => {
6486 warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
6487 continue;
6488 }
6489 };
6490
6491 if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
6493 warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
6494 continue;
6495 }
6496
6497 let synapse_count =
6499 self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
6500 total_synapses += synapse_count as u32;
6501
6502 upstream_updates.push((dst_cortical_id, src_cortical_idx));
6505 }
6506
6507 for (dst_id, src_idx) in upstream_updates {
6509 self.add_upstream_area(&dst_id, src_idx);
6510 }
6511
6512 trace!(
6513 target: "feagi-bdu",
6514 "Created {} synapses for area {} via NPU",
6515 total_synapses,
6516 src_cortical_id
6517 );
6518
6519 if total_synapses > 0 {
6522 let mut cache = self.cached_synapse_counts_per_area.write();
6523 cache
6524 .entry(*src_cortical_id)
6525 .or_insert_with(|| AtomicUsize::new(0))
6526 .fetch_add(total_synapses as usize, Ordering::Relaxed);
6527 }
6528
6529 self.cached_synapse_count
6531 .fetch_add(total_synapses as usize, Ordering::Relaxed);
6532
6533 if total_synapses > 0 {
6535 let state_manager = StateManager::instance();
6536 let state_manager = state_manager.read();
6537 let core_state = state_manager.get_core_state();
6538 core_state.add_synapse_count(total_synapses);
6539 }
6540
6541 Ok(total_synapses)
6542 }
6543
6544 pub fn has_neuron(&self, neuron_id: u64) -> bool {
6563 if let Some(ref npu) = self.npu {
6564 if let Ok(npu_lock) = npu.lock() {
6565 npu_lock.is_neuron_valid(neuron_id as u32)
6567 } else {
6568 false
6569 }
6570 } else {
6571 false
6572 }
6573 }
6574
6575 pub fn get_neuron_count(&self) -> usize {
6587 if let Some(ref npu) = self.npu {
6589 if let Ok(npu_lock) = npu.try_lock() {
6590 let fresh_count = npu_lock.get_neuron_count();
6591 self.cached_neuron_count
6592 .store(fresh_count, Ordering::Relaxed);
6593 }
6594 }
6596
6597 self.cached_neuron_count.load(Ordering::Relaxed)
6599 }
6600
6601 pub fn update_cached_neuron_count(&self) {
6607 if let Some(ref npu) = self.npu {
6608 if let Ok(npu_lock) = npu.try_lock() {
6609 let count = npu_lock.get_neuron_count();
6610 self.cached_neuron_count.store(count, Ordering::Relaxed);
6611 }
6612 }
6613 }
6614
6615 pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
6619 let npu = self.npu.as_ref()?;
6620 let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
6621 let npu_lock = npu.lock().ok()?;
6622 let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
6623 drop(npu_lock);
6624
6625 let mut cache = self.cached_neuron_counts_per_area.write();
6626 cache
6627 .entry(*cortical_id)
6628 .or_insert_with(|| AtomicUsize::new(0))
6629 .store(count, Ordering::Relaxed);
6630
6631 let state_manager = StateManager::instance();
6633 let state_manager = state_manager.read();
6634 state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
6635
6636 self.update_cached_neuron_count();
6637
6638 Some(count)
6639 }
6640
6641 pub fn get_synapse_count(&self) -> usize {
6653 if let Some(ref npu) = self.npu {
6655 if let Ok(npu_lock) = npu.try_lock() {
6656 let fresh_count = npu_lock.get_synapse_count();
6657 self.cached_synapse_count
6658 .store(fresh_count, Ordering::Relaxed);
6659 }
6660 }
6662
6663 self.cached_synapse_count.load(Ordering::Relaxed)
6665 }
6666
6667 pub fn update_cached_synapse_count(&self) {
6673 if let Some(ref npu) = self.npu {
6674 if let Ok(npu_lock) = npu.try_lock() {
6675 let count = npu_lock.get_synapse_count();
6676 self.cached_synapse_count.store(count, Ordering::Relaxed);
6677 }
6678 }
6679 }
6680
6681 pub fn update_all_cached_stats(&self) {
6687 self.update_cached_neuron_count();
6688 self.update_cached_synapse_count();
6689 }
6690
6691 pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
6702 #[cfg(feature = "plasticity")]
6707 {
6708 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
6709 return (0, 0, 0);
6710 }
6711 }
6712 if let Some(ref npu) = self.npu {
6713 if let Ok(npu_lock) = npu.lock() {
6714 npu_lock
6715 .get_neuron_coordinates(neuron_id as u32)
6716 .unwrap_or((0, 0, 0))
6717 } else {
6718 (0, 0, 0)
6719 }
6720 } else {
6721 (0, 0, 0)
6722 }
6723 }
6724
6725 pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
6736 self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
6737 }
6738
6739 pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
6745 #[cfg(feature = "plasticity")]
6746 {
6747 if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
6748 return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
6749 }
6750 }
6751 if let Some(ref npu) = self.npu {
6752 if let Ok(npu_lock) = npu.lock() {
6753 npu_lock.get_neuron_cortical_area(neuron_id as u32)
6754 } else {
6755 None
6756 }
6757 } else {
6758 None
6759 }
6760 }
6761
6762 #[cfg(feature = "plasticity")]
6764 fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
6765 let exec = self.get_plasticity_executor()?;
6766 let guard = exec.lock().ok()?;
6767 guard
6768 .memory_neuron_detail(neuron_id)
6769 .map(|d| d.cortical_area_idx)
6770 }
6771
6772 pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
6783 let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
6785 Some(idx) => *idx,
6786 None => return Vec::new(),
6787 };
6788
6789 if let Some(ref npu) = self.npu {
6790 if let Ok(npu_lock) = npu.lock() {
6791 npu_lock
6793 .get_neurons_in_cortical_area(cortical_idx)
6794 .into_iter()
6795 .map(|id| id as u64)
6796 .collect()
6797 } else {
6798 Vec::new()
6799 }
6800 } else {
6801 Vec::new()
6802 }
6803 }
6804
6805 pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
6816 if let Some(ref npu) = self.npu {
6817 if let Ok(npu_lock) = npu.lock() {
6818 npu_lock.get_outgoing_synapses(source_neuron_id as u32)
6819 } else {
6820 Vec::new()
6821 }
6822 } else {
6823 Vec::new()
6824 }
6825 }
6826
6827 pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
6838 if let Some(ref npu) = self.npu {
6839 if let Ok(npu_lock) = npu.lock() {
6840 npu_lock.get_incoming_synapses(target_neuron_id as u32)
6841 } else {
6842 Vec::new()
6843 }
6844 } else {
6845 Vec::new()
6846 }
6847 }
6848
6849 pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6875 let is_memory_area = self
6876 .cortical_areas
6877 .get(cortical_id)
6878 .and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
6879 .is_some();
6880
6881 if is_memory_area {
6882 #[cfg(feature = "plasticity")]
6886 if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
6887 return count;
6888 }
6889
6890 return StateManager::instance()
6892 .try_read()
6893 .and_then(|state_manager| {
6894 state_manager
6895 .get_cortical_area_stats(&cortical_id.as_base_64())
6896 .map(|stats| stats.neuron_count)
6897 })
6898 .unwrap_or(0);
6899 }
6900
6901 let cache = self.cached_neuron_counts_per_area.read();
6903 cache
6904 .get(cortical_id)
6905 .map(|count| count.load(Ordering::Relaxed))
6906 .unwrap_or(0)
6907 }
6908
6909 #[cfg(feature = "plasticity")]
6911 fn active_memory_neuron_count_from_plasticity(
6912 &self,
6913 cortical_id: &CorticalID,
6914 ) -> Option<usize> {
6915 let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
6916 let exec = self.get_plasticity_executor()?;
6917 let guard = exec.lock().ok()?;
6918 let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
6919 Some(runtime.active_memory_neuron_count())
6920 }
6921
6922 pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
6929 let mut result = Vec::new();
6930
6931 for cortical_id in self.cortical_areas.keys() {
6932 let count = self.get_neuron_count_in_area(cortical_id);
6933 if count > 0 {
6934 result.push((cortical_id.to_string(), count));
6935 }
6936 }
6937
6938 result
6939 }
6940
6941 pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
6952 self.get_neuron_count_in_area(cortical_id) > 0
6953 }
6954
6955 pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6971 let cache = self.cached_synapse_counts_per_area.read();
6973 cache
6974 .get(cortical_id)
6975 .map(|count| count.load(Ordering::Relaxed))
6976 .unwrap_or(0)
6977 }
6978
6979 pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
6989 if !self.cortical_id_to_idx.contains_key(cortical_id) {
6990 return 0;
6991 }
6992
6993 if let Some(state_manager) = StateManager::instance().try_read() {
6994 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
6995 return stats.incoming_synapse_count;
6996 }
6997 }
6998
6999 0
7000 }
7001
7002 pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
7012 if !self.cortical_id_to_idx.contains_key(cortical_id) {
7013 return 0;
7014 }
7015
7016 if let Some(state_manager) = StateManager::instance().try_read() {
7017 if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
7018 return stats.outgoing_synapse_count;
7019 }
7020 }
7021
7022 0
7023 }
7024
7025 pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
7037 let synapses = self.get_outgoing_synapses(source_neuron_id);
7038 synapses
7039 .iter()
7040 .any(|(target, _, _, _)| *target == target_neuron_id as u32)
7041 }
7042
7043 pub fn get_connection_weight(
7055 &self,
7056 source_neuron_id: u64,
7057 target_neuron_id: u64,
7058 ) -> Option<f32> {
7059 let synapses = self.get_outgoing_synapses(source_neuron_id);
7060 synapses
7061 .iter()
7062 .find(|(target, _, _, _)| *target == target_neuron_id as u32)
7063 .map(|(_, weight, _, _)| *weight)
7064 }
7065
7066 pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
7077 let neurons = self.get_neurons_in_area(cortical_id);
7078 let neuron_count = neurons.len();
7079
7080 if neuron_count == 0 {
7081 return (0, 0, 0.0);
7082 }
7083
7084 let mut total_synapses = 0;
7085 for neuron_id in neurons {
7086 total_synapses += self.get_outgoing_synapses(neuron_id).len();
7087 }
7088
7089 let avg_synapses = total_synapses as f32 / neuron_count as f32;
7090
7091 (neuron_count, total_synapses, avg_synapses)
7092 }
7093
7094 pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
7105 let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
7106 self.cortical_idx_to_id.get(&cortical_idx).copied()
7107 }
7108
7109 pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
7120 let area = match self.cortical_areas.get(cortical_id) {
7121 Some(a) => a,
7122 None => return 0.0,
7123 };
7124
7125 let neuron_count = self.get_neuron_count_in_area(cortical_id);
7126 let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
7127
7128 if volume == 0 {
7129 return 0.0;
7130 }
7131
7132 neuron_count as f32 / volume as f32
7133 }
7134
7135 pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
7142 let mut stats = Vec::new();
7143
7144 for cortical_id in self.cortical_areas.keys() {
7145 let neuron_count = self.get_neuron_count_in_area(cortical_id);
7146 let synapse_count = self.get_synapse_count_in_area(cortical_id);
7147 let density = self.get_neuron_density(cortical_id);
7148
7149 stats.push((
7150 cortical_id.to_string(),
7151 neuron_count,
7152 synapse_count,
7153 density,
7154 ));
7155 }
7156
7157 stats
7158 }
7159
7160 pub fn get_config(&self) -> &ConnectomeConfig {
7166 &self.config
7167 }
7168
7169 pub fn set_config(&mut self, config: ConnectomeConfig) {
7171 self.config = config;
7172 }
7173
7174 pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
7197 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
7198
7199 use feagi_structures::genomic::cortical_area::{
7200 CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
7201 };
7202
7203 let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
7205 BduError::Internal(format!("Failed to create core area dimensions: {}", e))
7206 })?;
7207
7208 let core_position = (0, 0, 0).into();
7210
7211 let death_id = CoreCorticalType::Death.to_cortical_id();
7213 if !self.cortical_areas.contains_key(&death_id) {
7214 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
7215 let death_area = CorticalArea::new(
7216 death_id,
7217 0, "_death".to_string(),
7219 core_dimensions,
7220 core_position,
7221 CorticalAreaType::Core(CoreCorticalType::Death),
7222 )
7223 .map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
7224 match self.add_cortical_area(death_area) {
7225 Ok(idx) => {
7226 info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
7227 }
7228 Err(e) => {
7229 error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
7230 return Err(e);
7231 }
7232 }
7233 } else {
7234 info!(target: "feagi-bdu", " ✓ _death area already exists");
7235 }
7236
7237 let power_id = CoreCorticalType::Power.to_cortical_id();
7239 if !self.cortical_areas.contains_key(&power_id) {
7240 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
7241 let power_area = CorticalArea::new(
7242 power_id,
7243 1, "_power".to_string(),
7245 core_dimensions,
7246 core_position,
7247 CorticalAreaType::Core(CoreCorticalType::Power),
7248 )
7249 .map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
7250 match self.add_cortical_area(power_area) {
7251 Ok(idx) => {
7252 info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
7253 }
7254 Err(e) => {
7255 error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
7256 return Err(e);
7257 }
7258 }
7259 } else {
7260 info!(target: "feagi-bdu", " ✓ _power area already exists");
7261 }
7262
7263 let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
7265 let pain_id = CoreCorticalType::Pain.to_cortical_id();
7266 let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
7267 let fear_id = CoreCorticalType::Fear.to_cortical_id();
7268 let hope_id = CoreCorticalType::Hope.to_cortical_id();
7269 if !self.cortical_areas.contains_key(&fatigue_id) {
7270 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
7271 let fatigue_area = CorticalArea::new(
7272 fatigue_id,
7273 2, "_fatigue".to_string(),
7275 core_dimensions,
7276 core_position,
7277 CorticalAreaType::Core(CoreCorticalType::Fatigue),
7278 )
7279 .map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
7280 match self.add_cortical_area(fatigue_area) {
7281 Ok(idx) => {
7282 info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
7283 }
7284 Err(e) => {
7285 error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
7286 return Err(e);
7287 }
7288 }
7289 } else {
7290 info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
7291 }
7292
7293 if !self.cortical_areas.contains_key(&pain_id) {
7295 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
7296 let pain_area = CorticalArea::new(
7297 pain_id,
7298 3, "_pain".to_string(),
7300 core_dimensions,
7301 core_position,
7302 CorticalAreaType::Core(CoreCorticalType::Pain),
7303 )
7304 .map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
7305 match self.add_cortical_area(pain_area) {
7306 Ok(idx) => {
7307 info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
7308 }
7309 Err(e) => {
7310 error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
7311 return Err(e);
7312 }
7313 }
7314 } else {
7315 info!(target: "feagi-bdu", " ✓ _pain area already exists");
7316 }
7317
7318 if !self.cortical_areas.contains_key(&pleasure_id) {
7320 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
7321 let pleasure_area = CorticalArea::new(
7322 pleasure_id,
7323 4, "_pleasure".to_string(),
7325 core_dimensions,
7326 core_position,
7327 CorticalAreaType::Core(CoreCorticalType::Pleasure),
7328 )
7329 .map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
7330 match self.add_cortical_area(pleasure_area) {
7331 Ok(idx) => {
7332 info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
7333 }
7334 Err(e) => {
7335 error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
7336 return Err(e);
7337 }
7338 }
7339 } else {
7340 info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
7341 }
7342
7343 if !self.cortical_areas.contains_key(&fear_id) {
7345 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
7346 let fear_area = CorticalArea::new(
7347 fear_id,
7348 5, "_fear".to_string(),
7350 core_dimensions,
7351 core_position,
7352 CorticalAreaType::Core(CoreCorticalType::Fear),
7353 )
7354 .map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
7355 match self.add_cortical_area(fear_area) {
7356 Ok(idx) => {
7357 info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
7358 }
7359 Err(e) => {
7360 error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
7361 return Err(e);
7362 }
7363 }
7364 } else {
7365 info!(target: "feagi-bdu", " ✓ _fear area already exists");
7366 }
7367
7368 if !self.cortical_areas.contains_key(&hope_id) {
7370 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
7371 let hope_area = CorticalArea::new(
7372 hope_id,
7373 6, "_hope".to_string(),
7375 core_dimensions,
7376 core_position,
7377 CorticalAreaType::Core(CoreCorticalType::Hope),
7378 )
7379 .map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
7380 match self.add_cortical_area(hope_area) {
7381 Ok(idx) => {
7382 info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
7383 }
7384 Err(e) => {
7385 error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
7386 return Err(e);
7387 }
7388 }
7389 } else {
7390 info!(target: "feagi-bdu", " ✓ _hope area already exists");
7391 }
7392
7393 info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
7394 Ok(())
7395 }
7396
7397 #[deprecated(
7414 note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
7415 )]
7416 #[allow(deprecated)]
7417 pub fn save_genome_to_json(
7418 &self,
7419 genome_id: Option<String>,
7420 genome_title: Option<String>,
7421 ) -> BduResult<String> {
7422 let mut brain_regions_with_parents = std::collections::HashMap::new();
7424
7425 for region_id in self.brain_regions.get_all_region_ids() {
7426 if let Some(region) = self.brain_regions.get_region(region_id) {
7427 let parent_id = self
7428 .brain_regions
7429 .get_parent(region_id)
7430 .map(|s| s.to_string());
7431 brain_regions_with_parents
7432 .insert(region_id.to_string(), (region.clone(), parent_id));
7433 }
7434 }
7435
7436 Ok(feagi_evolutionary::GenomeSaver::save_to_json(
7438 &self.cortical_areas,
7439 &brain_regions_with_parents,
7440 genome_id,
7441 genome_title,
7442 )?)
7443 }
7444
7445 pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
7476 info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
7477
7478 self.cortical_areas.clear();
7480 self.cortical_id_to_idx.clear();
7481 self.cortical_idx_to_id.clear();
7482 self.next_cortical_idx = 7;
7484 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)");
7485
7486 self.brain_regions = BrainRegionHierarchy::new();
7488
7489 #[cfg(feature = "plasticity")]
7492 if let Some(executor) = self.plasticity_executor.as_ref() {
7493 executor
7494 .lock()
7495 .map_err(|_| {
7496 BduError::Internal(
7497 "Failed to lock PlasticityExecutor for memory state reset".to_string(),
7498 )
7499 })?
7500 .reset_all_memory_state()
7501 .map_err(BduError::Internal)?;
7502 }
7503
7504 if let Some(ref npu) = self.npu {
7506 let mut npu_lock = npu
7507 .lock()
7508 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7509 npu_lock
7510 .reset_for_new_genome()
7511 .map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
7512 }
7513
7514 info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
7515 Ok(())
7516 }
7517
7518 pub fn resize_for_genome(
7528 &mut self,
7529 genome: &feagi_evolutionary::RuntimeGenome,
7530 ) -> BduResult<()> {
7531 self.morphology_registry = genome.morphologies.clone();
7533 info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
7534
7535 let required_neurons = genome.stats.innate_neuron_count;
7537 let required_synapses = genome.stats.innate_synapse_count;
7538
7539 info!(target: "feagi-bdu",
7540 "Genome requires: {} neurons, {} synapses",
7541 required_neurons,
7542 required_synapses
7543 );
7544
7545 let mut total_voxels = 0;
7547 for area in genome.cortical_areas.values() {
7548 total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
7549 }
7550
7551 info!(target: "feagi-bdu",
7552 "Genome has {} cortical areas with {} total voxels",
7553 genome.cortical_areas.len(),
7554 total_voxels
7555 );
7556
7557 Ok(())
7562 }
7563
7564 pub fn create_synapse(
7583 &mut self,
7584 source_neuron_id: u64,
7585 target_neuron_id: u64,
7586 weight: f32,
7587 psp: f32,
7588 synapse_type: u8,
7589 ) -> BduResult<()> {
7590 let npu = self
7592 .npu
7593 .as_ref()
7594 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7595
7596 let mut npu_lock = npu
7597 .lock()
7598 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7599
7600 let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
7602 let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
7603
7604 if !source_exists {
7605 return Err(BduError::InvalidNeuron(format!(
7606 "Source neuron {} not found",
7607 source_neuron_id
7608 )));
7609 }
7610 if !target_exists {
7611 return Err(BduError::InvalidNeuron(format!(
7612 "Target neuron {} not found",
7613 target_neuron_id
7614 )));
7615 }
7616
7617 let syn_type = if synapse_type == 0 {
7619 feagi_npu_neural::synapse::SynapseType::Excitatory
7620 } else {
7621 feagi_npu_neural::synapse::SynapseType::Inhibitory
7622 };
7623
7624 let synapse_idx = npu_lock
7625 .add_synapse(
7626 NeuronId(source_neuron_id as u32),
7627 NeuronId(target_neuron_id as u32),
7628 feagi_npu_neural::types::SynapticWeight(weight),
7629 feagi_npu_neural::types::SynapticPsp(psp),
7630 syn_type,
7631 0,
7632 1,
7633 )
7634 .map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
7635
7636 debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
7637 source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
7638
7639 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
7640 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
7641 let source_cortical_id =
7642 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7643 let target_cortical_id =
7644 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7645
7646 let state_manager = StateManager::instance();
7647 let state_manager = state_manager.read();
7648 let core_state = state_manager.get_core_state();
7649 core_state.add_synapse_count(1);
7650 if let Some(cortical_id) = source_cortical_id {
7651 state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
7652 }
7653 if let Some(cortical_id) = target_cortical_id {
7654 state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
7655 }
7656
7657 Ok(())
7662 }
7663
7664 fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
7667 if let Some(ref npu) = self.npu {
7668 if let Ok(mut npu_lock) = npu.lock() {
7669 let mut psp_uniform_flags = ahash::AHashMap::new();
7671 let mut mp_driven_psp_flags = ahash::AHashMap::new();
7672 let mut postsynaptic_current_flags = ahash::AHashMap::new();
7673 let mut degeneration_flags = ahash::AHashMap::new();
7674
7675 for (cortical_id, area) in &self.cortical_areas {
7676 let default_psp_uniform = *cortical_id
7679 == CoreCorticalType::Power.to_cortical_id()
7680 || matches!(area.cortical_type, CorticalAreaType::Memory(_));
7681 let psp_uniform = area
7682 .get_property("psp_uniform_distribution")
7683 .and_then(|v| v.as_bool())
7684 .unwrap_or(default_psp_uniform);
7685 psp_uniform_flags.insert(*cortical_id, psp_uniform);
7686
7687 let mp_driven_psp = area
7689 .get_property("mp_driven_psp")
7690 .and_then(|v| v.as_bool())
7691 .unwrap_or(false);
7692 mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
7693
7694 let postsynaptic_current = area
7696 .get_property("postsynaptic_current")
7697 .and_then(|v| v.as_f64())
7698 .unwrap_or(1.0) as f32;
7699 postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
7700
7701 let degeneration = area
7703 .get_property("degeneration")
7704 .and_then(|v| v.as_f64())
7705 .unwrap_or(0.0) as f32;
7706 if degeneration > 0.0 {
7707 degeneration_flags.insert(*cortical_id, degeneration);
7708 }
7709 }
7710
7711 npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
7713 npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
7714 npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
7715 npu_lock.set_degeneration_flags(degeneration_flags);
7716
7717 trace!(
7718 target: "feagi-bdu",
7719 "Synchronized cortical area flags to NPU ({} areas)",
7720 self.cortical_areas.len()
7721 );
7722 }
7723 }
7724
7725 Ok(())
7726 }
7727
7728 pub fn get_synapse(
7740 &self,
7741 source_neuron_id: u64,
7742 target_neuron_id: u64,
7743 ) -> Option<(f32, f32, u8)> {
7744 let npu = self.npu.as_ref()?;
7746 let npu_lock = npu.lock().ok()?;
7747
7748 let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
7751
7752 for (source_id, weight, psp, synapse_type) in incoming {
7754 if source_id == source_neuron_id as u32 {
7755 return Some((weight, psp, synapse_type));
7756 }
7757 }
7758
7759 None
7760 }
7761
7762 pub fn update_synapse_weight(
7775 &mut self,
7776 source_neuron_id: u64,
7777 target_neuron_id: u64,
7778 new_weight: f32,
7779 ) -> BduResult<()> {
7780 let npu = self
7782 .npu
7783 .as_ref()
7784 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7785
7786 let mut npu_lock = npu
7787 .lock()
7788 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7789
7790 let updated = npu_lock.update_synapse_weight(
7792 NeuronId(source_neuron_id as u32),
7793 NeuronId(target_neuron_id as u32),
7794 feagi_npu_neural::types::SynapticWeight(new_weight),
7795 );
7796
7797 if updated {
7798 debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
7799 Ok(())
7800 } else {
7801 Err(BduError::InvalidSynapse(format!(
7802 "Synapse {} -> {} not found",
7803 source_neuron_id, target_neuron_id
7804 )))
7805 }
7806 }
7807
7808 pub fn remove_synapse(
7820 &mut self,
7821 source_neuron_id: u64,
7822 target_neuron_id: u64,
7823 ) -> BduResult<bool> {
7824 let npu = self
7826 .npu
7827 .as_ref()
7828 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7829
7830 let mut npu_lock = npu
7831 .lock()
7832 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7833
7834 let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
7835 let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
7836 let source_cortical_id =
7837 source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7838 let target_cortical_id =
7839 target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
7840
7841 let removed = npu_lock.remove_synapse(
7843 NeuronId(source_neuron_id as u32),
7844 NeuronId(target_neuron_id as u32),
7845 );
7846
7847 if removed {
7848 debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
7849
7850 let state_manager = StateManager::instance();
7852 let state_manager = state_manager.read();
7853 let core_state = state_manager.get_core_state();
7854 core_state.subtract_synapse_count(1);
7855 if let Some(cortical_id) = source_cortical_id {
7856 state_manager
7857 .subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
7858 }
7859 if let Some(cortical_id) = target_cortical_id {
7860 state_manager
7861 .subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
7862 }
7863 }
7864
7865 Ok(removed)
7866 }
7867
7868 pub fn batch_create_neurons(
7886 &mut self,
7887 cortical_id: &CorticalID,
7888 neurons: Vec<NeuronData>,
7889 ) -> BduResult<Vec<u64>> {
7890 let npu = self
7892 .npu
7893 .as_ref()
7894 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
7895
7896 let mut npu_lock = npu
7897 .lock()
7898 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
7899
7900 let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
7902 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
7903 })?;
7904 let cortical_idx = area.cortical_idx;
7905
7906 let count = neurons.len();
7907
7908 let mut x_coords = Vec::with_capacity(count);
7910 let mut y_coords = Vec::with_capacity(count);
7911 let mut z_coords = Vec::with_capacity(count);
7912 let mut firing_thresholds = Vec::with_capacity(count);
7913 let mut threshold_limits = Vec::with_capacity(count);
7914 let mut leak_coeffs = Vec::with_capacity(count);
7915 let mut resting_potentials = Vec::with_capacity(count);
7916 let mut neuron_types = Vec::with_capacity(count);
7917 let mut refractory_periods = Vec::with_capacity(count);
7918 let mut excitabilities = Vec::with_capacity(count);
7919 let mut consec_fire_limits = Vec::with_capacity(count);
7920 let mut snooze_lengths = Vec::with_capacity(count);
7921 let mut mp_accums = Vec::with_capacity(count);
7922 let mut cortical_areas = Vec::with_capacity(count);
7923
7924 for (
7925 x,
7926 y,
7927 z,
7928 threshold,
7929 threshold_limit,
7930 leak,
7931 resting,
7932 ntype,
7933 refract,
7934 excit,
7935 consec_limit,
7936 snooze,
7937 mp_accum,
7938 ) in neurons
7939 {
7940 x_coords.push(x);
7941 y_coords.push(y);
7942 z_coords.push(z);
7943 firing_thresholds.push(threshold);
7944 threshold_limits.push(threshold_limit);
7945 leak_coeffs.push(leak);
7946 resting_potentials.push(resting);
7947 neuron_types.push(ntype);
7948 refractory_periods.push(refract);
7949 excitabilities.push(excit);
7950 consec_fire_limits.push(consec_limit);
7951 snooze_lengths.push(snooze);
7952 mp_accums.push(mp_accum);
7953 cortical_areas.push(cortical_idx);
7954 }
7955
7956 let first_neuron_id = npu_lock.get_neuron_count() as u32;
7958
7959 let firing_thresholds_t = firing_thresholds;
7964 let threshold_limits_t = threshold_limits;
7965 let resting_potentials_t = resting_potentials;
7966 let (neurons_created, _indices) = npu_lock.add_neurons_batch(
7967 firing_thresholds_t,
7968 threshold_limits_t,
7969 leak_coeffs,
7970 resting_potentials_t,
7971 neuron_types,
7972 refractory_periods,
7973 excitabilities,
7974 consec_fire_limits,
7975 snooze_lengths,
7976 mp_accums,
7977 cortical_areas,
7978 x_coords,
7979 y_coords,
7980 z_coords,
7981 );
7982
7983 let mut neuron_ids = Vec::with_capacity(count);
7985 for i in 0..neurons_created {
7986 neuron_ids.push((first_neuron_id + i) as u64);
7987 }
7988
7989 info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
7990
7991 let state_manager = StateManager::instance();
7993 let state_manager = state_manager.read();
7994 let core_state = state_manager.get_core_state();
7995 core_state.add_neuron_count(neurons_created);
7996 core_state.add_regular_neuron_count(neurons_created);
7997 state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
7998
7999 {
8001 let mut cache = self.cached_neuron_counts_per_area.write();
8002 cache
8003 .entry(*cortical_id)
8004 .or_insert_with(|| AtomicUsize::new(0))
8005 .fetch_add(count, Ordering::Relaxed);
8006 }
8007
8008 Ok(neuron_ids)
8009 }
8010
8011 pub fn delete_neurons_batch(&self, neuron_ids: Vec<u64>) -> BduResult<usize> {
8028 let npu = self
8030 .npu
8031 .as_ref()
8032 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
8033
8034 let mut npu_lock = npu
8035 .lock()
8036 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
8037
8038 let neuron_ids_u32: Vec<u32> = neuron_ids
8039 .iter()
8040 .map(|neuron_id| *neuron_id as u32)
8041 .collect();
8042 let cortical_indices = npu_lock.cortical_area_indices_for(&neuron_ids_u32);
8043 let deleted_count = npu_lock.delete_neurons(&neuron_ids_u32);
8044
8045 let mut per_area_deleted: std::collections::HashMap<String, usize> =
8046 std::collections::HashMap::new();
8047 for cortical_idx in cortical_indices.into_iter().flatten() {
8048 if let Some(cortical_id) = self.cortical_idx_to_id.get(&cortical_idx) {
8049 let key = cortical_id.as_base_64();
8050 *per_area_deleted.entry(key).or_insert(0) += 1;
8051 }
8052 }
8053
8054 info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
8055
8056 if deleted_count > 0 {
8058 let state_manager = StateManager::instance();
8059 let state_manager = state_manager.read();
8060 let core_state = state_manager.get_core_state();
8061 core_state.subtract_neuron_count(deleted_count as u32);
8062 core_state.subtract_regular_neuron_count(deleted_count as u32);
8063 for (cortical_id, count) in per_area_deleted {
8064 state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
8065 }
8066 }
8067
8068 Ok(deleted_count)
8075 }
8076
8077 pub fn update_neuron_properties(
8096 &mut self,
8097 neuron_id: u64,
8098 firing_threshold: Option<f32>,
8099 leak_coefficient: Option<f32>,
8100 resting_potential: Option<f32>,
8101 excitability: Option<f32>,
8102 ) -> BduResult<()> {
8103 let npu = self
8105 .npu
8106 .as_ref()
8107 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
8108
8109 let mut npu_lock = npu
8110 .lock()
8111 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
8112
8113 let neuron_id_u32 = neuron_id as u32;
8114
8115 let mut updated = false;
8117
8118 if let Some(threshold) = firing_threshold {
8120 if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
8121 updated = true;
8122 debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
8123 } else if !updated {
8124 return Err(BduError::InvalidNeuron(format!(
8125 "Neuron {} not found",
8126 neuron_id
8127 )));
8128 }
8129 }
8130
8131 if let Some(leak) = leak_coefficient {
8132 if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
8133 updated = true;
8134 debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
8135 } else if !updated {
8136 return Err(BduError::InvalidNeuron(format!(
8137 "Neuron {} not found",
8138 neuron_id
8139 )));
8140 }
8141 }
8142
8143 if let Some(resting) = resting_potential {
8144 if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
8145 updated = true;
8146 debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
8147 } else if !updated {
8148 return Err(BduError::InvalidNeuron(format!(
8149 "Neuron {} not found",
8150 neuron_id
8151 )));
8152 }
8153 }
8154
8155 if let Some(excit) = excitability {
8156 if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
8157 updated = true;
8158 debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
8159 } else if !updated {
8160 return Err(BduError::InvalidNeuron(format!(
8161 "Neuron {} not found",
8162 neuron_id
8163 )));
8164 }
8165 }
8166
8167 if !updated {
8168 return Err(BduError::Internal(
8169 "No properties provided for update".to_string(),
8170 ));
8171 }
8172
8173 info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
8174
8175 Ok(())
8176 }
8177
8178 pub fn set_neuron_firing_threshold(
8190 &mut self,
8191 neuron_id: u64,
8192 new_threshold: f32,
8193 ) -> BduResult<()> {
8194 let npu = self
8196 .npu
8197 .as_ref()
8198 .ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
8199
8200 let mut npu_lock = npu
8201 .lock()
8202 .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
8203
8204 if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
8206 debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
8207 Ok(())
8208 } else {
8209 Err(BduError::InvalidNeuron(format!(
8210 "Neuron {} not found",
8211 neuron_id
8212 )))
8213 }
8214 }
8215
8216 pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
8231 self.cortical_areas
8232 .values()
8233 .find(|area| area.name == name)
8234 .cloned()
8235 }
8236
8237 pub fn resize_cortical_area(
8254 &mut self,
8255 cortical_id: &CorticalID,
8256 new_dimensions: CorticalAreaDimensions,
8257 ) -> BduResult<()> {
8258 if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
8260 return Err(BduError::InvalidArea(format!(
8261 "Invalid dimensions: {:?} (all must be > 0)",
8262 new_dimensions
8263 )));
8264 }
8265
8266 let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
8268 BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
8269 })?;
8270
8271 let old_dimensions = area.dimensions;
8272 area.dimensions = new_dimensions;
8273
8274 info!(target: "feagi-bdu",
8278 "Resized cortical area {} from {:?} to {:?}",
8279 cortical_id,
8280 old_dimensions,
8281 new_dimensions
8282 );
8283
8284 self.refresh_cortical_area_hashes(false, true);
8285
8286 Ok(())
8287 }
8288
8289 pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
8300 let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
8301 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8302 })?;
8303
8304 Ok(region
8306 .cortical_areas
8307 .iter()
8308 .map(|id| id.as_base_64())
8309 .collect())
8310 }
8311
8312 pub fn update_brain_region(
8325 &mut self,
8326 region_id: &str,
8327 new_name: Option<String>,
8328 new_description: Option<String>,
8329 ) -> BduResult<()> {
8330 let region = self
8331 .brain_regions
8332 .get_region_mut(region_id)
8333 .ok_or_else(|| {
8334 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8335 })?;
8336
8337 if let Some(name) = new_name {
8338 region.name = name;
8339 debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
8340 }
8341
8342 if let Some(desc) = new_description {
8343 region
8345 .properties
8346 .insert("description".to_string(), serde_json::json!(desc));
8347 debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
8348 }
8349
8350 info!(target: "feagi-bdu","Updated brain region {}", region_id);
8351
8352 self.refresh_brain_regions_hash();
8353
8354 Ok(())
8355 }
8356
8357 pub fn update_brain_region_properties(
8371 &mut self,
8372 region_id: &str,
8373 properties: std::collections::HashMap<String, serde_json::Value>,
8374 ) -> BduResult<Option<BrainRegionIoRegistry>> {
8375 use tracing::{debug, info};
8376
8377 let should_recompute_io = properties
8378 .contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
8379 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
8380
8381 if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
8382 || properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
8383 {
8384 let region_snapshot = self
8385 .brain_regions
8386 .get_region(region_id)
8387 .ok_or_else(|| {
8388 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8389 })?
8390 .clone();
8391 let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
8392 ®ion_snapshot,
8393 &properties,
8394 )?;
8395 crate::region_io_designation::validate_merged_designations_against_connectivity(
8396 self,
8397 ®ion_snapshot,
8398 &merged_in,
8399 &merged_out,
8400 )?;
8401 }
8402
8403 let region = self
8404 .brain_regions
8405 .get_region_mut(region_id)
8406 .ok_or_else(|| {
8407 BduError::InvalidArea(format!("Brain region {} not found", region_id))
8408 })?;
8409
8410 for (key, value) in properties {
8411 match key.as_str() {
8412 "title" | "name" | "region_title" => {
8414 if let Some(name) = value.as_str() {
8415 region.name = name.to_string();
8416 debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
8417 }
8418 }
8419 "coordinate_3d" | "coordinates_3d" => {
8420 region
8421 .properties
8422 .insert("coordinate_3d".to_string(), value.clone());
8423 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
8424 }
8425 "coordinate_2d" | "coordinates_2d" => {
8426 region
8427 .properties
8428 .insert("coordinate_2d".to_string(), value.clone());
8429 debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
8430 }
8431 "description" => {
8432 region
8433 .properties
8434 .insert("description".to_string(), value.clone());
8435 debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
8436 }
8437 "region_type" => {
8438 if let Some(type_str) = value.as_str() {
8439 region.region_type = feagi_structures::genomic::RegionType::Undefined;
8442 debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
8443 }
8444 }
8445 _ => {
8447 region.properties.insert(key.clone(), value.clone());
8448 debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
8449 }
8450 }
8451 }
8452
8453 info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
8454
8455 if should_recompute_io {
8458 let registry = self.recompute_brain_region_io_registry()?;
8459 return Ok(Some(registry));
8460 }
8461
8462 self.refresh_brain_regions_hash();
8466
8467 Ok(None)
8468 }
8469
8470 pub fn get_neuron_by_coordinates(
8488 &self,
8489 cortical_id: &CorticalID,
8490 x: u32,
8491 y: u32,
8492 z: u32,
8493 ) -> Option<u64> {
8494 let area = self.get_cortical_area(cortical_id)?;
8496 let cortical_idx = area.cortical_idx;
8497
8498 let npu = self.npu.as_ref()?;
8500 let npu_lock = npu.lock().ok()?;
8501
8502 npu_lock
8503 .get_neuron_id_at_coordinate(cortical_idx, x, y, z)
8504 .map(|id| id as u64)
8505 }
8506
8507 pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
8518 let npu = self.npu.as_ref()?;
8519 let npu_lock = npu.lock().ok()?;
8520
8521 let neuron_count = npu_lock.get_neuron_count();
8523 if (neuron_id as usize) >= neuron_count {
8524 return None;
8525 }
8526
8527 Some(
8528 npu_lock
8529 .get_neuron_coordinates(neuron_id as u32)
8530 .unwrap_or((0, 0, 0)),
8531 )
8532 }
8533
8534 pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
8545 let npu = self.npu.as_ref()?;
8546 let npu_lock = npu.lock().ok()?;
8547
8548 let neuron_count = npu_lock.get_neuron_count();
8550 if (neuron_id as usize) >= neuron_count {
8551 return None;
8552 }
8553
8554 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
8555
8556 self.cortical_areas
8558 .values()
8559 .find(|area| area.cortical_idx == cortical_idx)
8560 .map(|area| area.cortical_id)
8561 }
8562
8563 pub fn get_neuron_properties(
8574 &self,
8575 neuron_id: u64,
8576 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8577 let npu = self.npu.as_ref()?;
8578 let npu_lock = npu.lock().ok()?;
8579
8580 let neuron_id_u32 = neuron_id as u32;
8581 let idx = neuron_id as usize;
8582
8583 let neuron_count = npu_lock.get_neuron_count();
8585 if idx >= neuron_count {
8586 return None;
8587 }
8588
8589 let mut properties = std::collections::HashMap::new();
8590
8591 properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
8593
8594 let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
8596 properties.insert("x".to_string(), serde_json::json!(x));
8597 properties.insert("y".to_string(), serde_json::json!(y));
8598 properties.insert("z".to_string(), serde_json::json!(z));
8599
8600 let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
8602 properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
8603
8604 properties.insert(
8606 "mp_charge_accumulation".to_string(),
8607 serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
8608 );
8609 properties.insert(
8610 "neuron_type".to_string(),
8611 serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
8612 );
8613 let (mp_drv, psp_uni) = self
8614 .cortical_idx_to_id
8615 .get(&cortical_idx)
8616 .map(|cid| {
8617 (
8618 npu_lock.get_mp_driven_psp_for_cortical(cid),
8619 npu_lock.get_psp_uniform_distribution_for_cortical(cid),
8620 )
8621 })
8622 .unwrap_or((false, false));
8623 properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
8624 properties.insert(
8625 "psp_uniform_distribution".to_string(),
8626 serde_json::json!(psp_uni),
8627 );
8628
8629 let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
8632 .get_neuron_state(NeuronId(neuron_id_u32))
8633 .unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
8634 properties.insert(
8635 "consecutive_fire_count".to_string(),
8636 serde_json::json!(consec_count),
8637 );
8638 properties.insert(
8639 "consecutive_fire_limit".to_string(),
8640 serde_json::json!(consec_limit),
8641 );
8642 properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
8643 properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
8644 properties.insert("threshold".to_string(), serde_json::json!(threshold));
8645 properties.insert(
8646 "refractory_countdown".to_string(),
8647 serde_json::json!(refract_countdown),
8648 );
8649
8650 properties.insert(
8652 "leak_coefficient".to_string(),
8653 serde_json::json!(npu_lock
8654 .get_neuron_property_by_index(idx, "leak_coefficient")
8655 .unwrap_or(0.0)),
8656 );
8657 properties.insert(
8658 "resting_potential".to_string(),
8659 serde_json::json!(npu_lock
8660 .get_neuron_property_by_index(idx, "resting_potential")
8661 .unwrap_or(0.0)),
8662 );
8663 properties.insert(
8664 "excitability".to_string(),
8665 serde_json::json!(npu_lock
8666 .get_neuron_property_by_index(idx, "excitability")
8667 .unwrap_or(0.0)),
8668 );
8669 properties.insert(
8670 "threshold_limit".to_string(),
8671 serde_json::json!(npu_lock
8672 .get_neuron_property_by_index(idx, "threshold_limit")
8673 .unwrap_or(0.0)),
8674 );
8675 properties.insert(
8676 "refractory_period".to_string(),
8677 serde_json::json!(npu_lock
8678 .get_neuron_property_u16_by_index(idx, "refractory_period")
8679 .unwrap_or(0)),
8680 );
8681
8682 Some(properties)
8683 }
8684
8685 pub fn get_neuron_property(
8697 &self,
8698 neuron_id: u64,
8699 property_name: &str,
8700 ) -> Option<serde_json::Value> {
8701 self.get_neuron_properties(neuron_id)?
8702 .get(property_name)
8703 .cloned()
8704 }
8705
8706 pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
8717 self.cortical_areas.keys().copied().collect()
8718 }
8719
8720 pub fn get_all_cortical_indices(&self) -> Vec<u32> {
8727 self.cortical_areas
8728 .values()
8729 .map(|area| area.cortical_idx)
8730 .collect()
8731 }
8732
8733 pub fn get_cortical_area_names(&self) -> Vec<String> {
8740 self.cortical_areas
8741 .values()
8742 .map(|area| area.name.clone())
8743 .collect()
8744 }
8745
8746 pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
8753 use crate::models::CorticalAreaExt;
8754 self.cortical_areas
8755 .values()
8756 .filter(|area| area.is_input_area())
8757 .map(|area| area.cortical_id)
8758 .collect()
8759 }
8760
8761 pub fn list_opu_areas(&self) -> Vec<CorticalID> {
8768 use crate::models::CorticalAreaExt;
8769 self.cortical_areas
8770 .values()
8771 .filter(|area| area.is_output_area())
8772 .map(|area| area.cortical_id)
8773 .collect()
8774 }
8775
8776 pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
8783 self.cortical_areas
8784 .values()
8785 .fold((0, 0, 0), |(max_w, max_h, max_d), area| {
8786 (
8787 max_w.max(area.dimensions.width as usize),
8788 max_h.max(area.dimensions.height as usize),
8789 max_d.max(area.dimensions.depth as usize),
8790 )
8791 })
8792 }
8793
8794 pub fn get_cortical_area_properties(
8805 &self,
8806 cortical_id: &CorticalID,
8807 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8808 let area = self.get_cortical_area(cortical_id)?;
8809
8810 let mut properties = std::collections::HashMap::new();
8811 properties.insert(
8812 "cortical_id".to_string(),
8813 serde_json::json!(area.cortical_id),
8814 );
8815 properties.insert(
8816 "cortical_id_s".to_string(),
8817 serde_json::json!(area.cortical_id.to_string()),
8818 );
8819 properties.insert(
8820 "cortical_idx".to_string(),
8821 serde_json::json!(area.cortical_idx),
8822 );
8823 properties.insert("name".to_string(), serde_json::json!(area.name));
8824 use crate::models::CorticalAreaExt;
8825 properties.insert(
8826 "area_type".to_string(),
8827 serde_json::json!(area.get_cortical_group()),
8828 );
8829 properties.insert(
8830 "dimensions".to_string(),
8831 serde_json::json!({
8832 "width": area.dimensions.width,
8833 "height": area.dimensions.height,
8834 "depth": area.dimensions.depth,
8835 }),
8836 );
8837 properties.insert("position".to_string(), serde_json::json!(area.position));
8838
8839 for (key, value) in &area.properties {
8841 properties.insert(key.clone(), value.clone());
8842 }
8843
8844 properties.extend(area.properties.clone());
8846
8847 Some(properties)
8848 }
8849
8850 pub fn get_all_cortical_area_properties(
8857 &self,
8858 ) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
8859 self.cortical_areas
8860 .keys()
8861 .filter_map(|id| self.get_cortical_area_properties(id))
8862 .collect()
8863 }
8864
8865 pub fn get_all_brain_region_ids(&self) -> Vec<String> {
8876 self.brain_regions
8877 .get_all_region_ids()
8878 .into_iter()
8879 .cloned()
8880 .collect()
8881 }
8882
8883 pub fn get_brain_region_names(&self) -> Vec<String> {
8890 self.brain_regions
8891 .get_all_region_ids()
8892 .iter()
8893 .filter_map(|id| {
8894 self.brain_regions
8895 .get_region(id)
8896 .map(|region| region.name.clone())
8897 })
8898 .collect()
8899 }
8900
8901 pub fn get_brain_region_properties(
8912 &self,
8913 region_id: &str,
8914 ) -> Option<std::collections::HashMap<String, serde_json::Value>> {
8915 let region = self.brain_regions.get_region(region_id)?;
8916
8917 let mut properties = std::collections::HashMap::new();
8918 properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
8919 properties.insert("name".to_string(), serde_json::json!(region.name));
8920 properties.insert(
8921 "region_type".to_string(),
8922 serde_json::json!(format!("{:?}", region.region_type)),
8923 );
8924 properties.insert(
8925 "cortical_areas".to_string(),
8926 serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
8927 );
8928
8929 properties.extend(region.properties.clone());
8931
8932 Some(properties)
8933 }
8934
8935 pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
8946 self.cortical_areas.contains_key(cortical_id)
8947 }
8948
8949 pub fn brain_region_exists(&self, region_id: &str) -> bool {
8960 self.brain_regions.get_region(region_id).is_some()
8961 }
8962
8963 pub fn get_brain_region_count(&self) -> usize {
8970 self.brain_regions.region_count()
8971 }
8972
8973 pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
8984 self.get_neurons_in_area(cortical_id)
8988 }
8989}
8990
8991impl std::fmt::Debug for ConnectomeManager {
8993 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8994 f.debug_struct("ConnectomeManager")
8995 .field("cortical_areas", &self.cortical_areas.len())
8996 .field("next_cortical_idx", &self.next_cortical_idx)
8997 .field("brain_regions", &self.brain_regions)
8998 .field(
8999 "npu",
9000 &if self.npu.is_some() {
9001 "Connected"
9002 } else {
9003 "Not connected"
9004 },
9005 )
9006 .field("initialized", &self.initialized)
9007 .finish()
9008 }
9009}
9010
9011#[cfg(test)]
9012mod tests {
9013 use super::*;
9014 use feagi_structures::genomic::cortical_area::CoreCorticalType;
9015
9016 #[cfg(feature = "plasticity")]
9020 #[test]
9021 fn prepare_for_new_genome_discards_memory_neurons_from_previous_brain() {
9022 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
9023 use feagi_npu_plasticity::executor::PlasticityExecutor;
9024 use feagi_npu_plasticity::{
9025 create_memory_stats_cache, AsyncPlasticityExecutor, MemoryNeuronDetail,
9026 PlasticityConfig,
9027 };
9028 use feagi_npu_runtime::StdRuntime;
9029
9030 let npu = Arc::new(TracingMutex::new(
9031 DynamicNPU::new_f32(
9032 StdRuntime::new(),
9033 feagi_npu_burst_engine::backend::CPUBackend::new(),
9034 16,
9035 16,
9036 8,
9037 )
9038 .unwrap(),
9039 "prepare-for-new-genome-test-npu",
9040 ));
9041 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
9042 PlasticityConfig::default(),
9043 create_memory_stats_cache(),
9044 npu.clone(),
9045 )));
9046
9047 let mut manager = ConnectomeManager::new_for_testing();
9048 manager.set_npu(npu);
9049 manager.set_plasticity_executor(executor.clone());
9050
9051 let previous_brain_memory_idx = 8;
9052 {
9053 let exec = executor.lock().expect("plasticity executor");
9054 PlasticityExecutor::register_memory_area(
9055 &*exec,
9056 previous_brain_memory_idx,
9057 "previous_brain_memory".to_string(),
9058 1,
9059 vec![7],
9060 None,
9061 false,
9062 );
9063 exec.restore_long_term_memory_neurons(&[MemoryNeuronDetail {
9064 neuron_id: 50_000_003,
9065 cortical_area_idx: previous_brain_memory_idx,
9066 pattern_hash: Some(9_631_261_403_772_054_764),
9067 is_longterm_memory: true,
9068 is_active: true,
9069 lifespan_current: 120,
9070 lifespan_initial: 20,
9071 lifespan_growth_rate: 3.0,
9072 creation_burst: 0,
9073 last_activation_burst: 0,
9074 activation_count: 5,
9075 spatial_signature: None,
9076 class_channels: Vec::new(),
9077 }])
9078 .expect("seeded long-term memory neuron");
9079 assert_eq!(
9080 exec.export_long_term_memory_neurons()
9081 .expect("plasticity service is initialized")
9082 .len(),
9083 1
9084 );
9085 }
9086
9087 manager
9088 .prepare_for_new_genome()
9089 .expect("genome preparation must succeed");
9090
9091 let exec = executor.lock().expect("plasticity executor");
9092 assert!(
9093 exec.export_long_term_memory_neurons()
9094 .expect("plasticity service is initialized")
9095 .is_empty(),
9096 "memory neurons from the previous brain must not survive a genome load"
9097 );
9098 assert_eq!(
9099 exec.paginated_memory_neuron_ids_in_area(previous_brain_memory_idx, 0, 10),
9100 Some((Vec::new(), 0)),
9101 "the previous brain's memory area must no longer report any memory neurons"
9102 );
9103 }
9104
9105 #[cfg(feature = "plasticity")]
9108 #[test]
9109 fn remove_cortical_area_unregisters_its_memory() {
9110 use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
9111 use feagi_npu_plasticity::executor::PlasticityExecutor;
9112 use feagi_npu_plasticity::{
9113 create_memory_stats_cache, AsyncPlasticityExecutor, PlasticityConfig,
9114 };
9115 use feagi_npu_runtime::StdRuntime;
9116 use feagi_structures::genomic::cortical_area::{
9117 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
9118 };
9119
9120 let npu = Arc::new(TracingMutex::new(
9121 DynamicNPU::new_f32(
9122 StdRuntime::new(),
9123 feagi_npu_burst_engine::backend::CPUBackend::new(),
9124 16,
9125 16,
9126 8,
9127 )
9128 .unwrap(),
9129 "remove-memory-area-test-npu",
9130 ));
9131 let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
9132 PlasticityConfig::default(),
9133 create_memory_stats_cache(),
9134 npu.clone(),
9135 )));
9136 let mut manager = ConnectomeManager::new_for_testing();
9137 manager.set_npu(npu);
9138 manager.set_plasticity_executor(executor.clone());
9139
9140 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
9141 let mut area = CorticalArea::new(
9142 mem_id,
9143 0,
9144 "kernel_mem".to_string(),
9145 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9146 (0, 0, 0).into(),
9147 CorticalAreaType::Memory(MemoryCorticalType::Memory),
9148 )
9149 .unwrap();
9150 area.properties
9151 .insert("is_mem_type".to_string(), serde_json::json!(true));
9152 let idx = manager.add_cortical_area(area).unwrap();
9153 {
9154 let exec = executor.lock().expect("plasticity executor");
9155 PlasticityExecutor::register_memory_area(
9156 &*exec,
9157 idx,
9158 mem_id.as_base_64(),
9159 1,
9160 vec![7],
9161 None,
9162 false,
9163 );
9164 }
9165
9166 manager
9167 .remove_cortical_area(&mem_id)
9168 .expect("memory area removal");
9169
9170 assert!(manager.get_cortical_id(idx).is_none());
9171 let exec = executor.lock().expect("plasticity executor");
9172 let indexes = exec
9173 .get_service()
9174 .expect("plasticity service")
9175 .registered_memory_area_indexes();
9176 assert!(
9177 !indexes.contains(&idx),
9178 "deleted memory area idx={idx} must leave plasticity, still registered: {indexes:?}"
9179 );
9180 }
9181
9182 #[test]
9183 fn test_singleton_instance() {
9184 let instance1 = ConnectomeManager::instance();
9185 let instance2 = ConnectomeManager::instance();
9186
9187 assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
9189 }
9190
9191 #[test]
9192 fn test_add_cortical_area() {
9193 ConnectomeManager::reset_for_testing();
9194
9195 let instance = ConnectomeManager::instance();
9196 let mut manager = instance.write();
9197
9198 use feagi_structures::genomic::cortical_area::{
9199 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9200 };
9201 let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9203 let area = CorticalArea::new(
9204 cortical_id,
9205 0,
9206 "Visual Input".to_string(),
9207 CorticalAreaDimensions::new(128, 128, 20).unwrap(),
9208 (0, 0, 0).into(),
9209 cortical_type,
9210 )
9211 .unwrap();
9212
9213 let initial_count = manager.get_cortical_area_count();
9214 let _cortical_idx = manager.add_cortical_area(area).unwrap();
9215
9216 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
9217 assert!(manager.has_cortical_area(&cortical_id));
9218 assert!(manager.is_initialized());
9219 }
9220
9221 #[test]
9222 fn refresh_all_connectome_hashes_publishes_mappings() {
9223 use feagi_structures::genomic::cortical_area::{
9224 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CustomCorticalType,
9225 };
9226
9227 let src_id = CorticalID::try_from_bytes(b"chashsrc").unwrap();
9228 let dst_id = CorticalID::try_from_bytes(b"chashdst").unwrap();
9229 let mut src = CorticalArea::new(
9230 src_id,
9231 1,
9232 "src".to_string(),
9233 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
9234 (0, 0, 0).into(),
9235 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
9236 )
9237 .unwrap();
9238 src.properties.insert(
9239 "cortical_mapping_dst".to_string(),
9240 serde_json::json!({
9241 dst_id.as_base_64(): [{ "morphology_id": "projector" }]
9242 }),
9243 );
9244 let mut manager = ConnectomeManager::new_for_testing();
9245 manager.add_cortical_area(src).unwrap();
9246 manager.refresh_all_connectome_hashes();
9247 let mappings_hash = feagi_state_manager::StateManager::instance()
9248 .read()
9249 .get_cortical_mappings_hash();
9250 assert_ne!(
9251 mappings_hash, 0,
9252 "refresh_all_connectome_hashes must publish cortical_mappings_hash"
9253 );
9254 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
9255 classifier_id: "clf-hash".to_string(),
9256 name: "hash_demo".to_string(),
9257 parent_region_id: "root".to_string(),
9258 coordinates_3d: [0, 0, 0],
9259 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
9260 kernel_area_id: None,
9261 class_area_id: None,
9262 mask_area_id: None,
9263 kernel_size: None,
9264 fields: Vec::new(),
9265 kernel_memory_id: "mkmem1".to_string(),
9266 class_memory_id: "mcmem1".to_string(),
9267 properties: HashMap::new(),
9268 });
9269 manager.refresh_all_connectome_hashes();
9270 let classifiers_hash = feagi_state_manager::StateManager::instance()
9271 .read()
9272 .get_classifiers_hash();
9273 assert_ne!(
9274 classifiers_hash, 0,
9275 "refresh_all_connectome_hashes must publish classifiers_hash"
9276 );
9277 }
9278
9279 #[test]
9280 fn test_cortical_area_lookups() {
9281 ConnectomeManager::reset_for_testing();
9282
9283 let instance = ConnectomeManager::instance();
9284 let mut manager = instance.write();
9285
9286 use feagi_structures::genomic::cortical_area::{
9287 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9288 };
9289 let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9291 let area = CorticalArea::new(
9292 cortical_id,
9293 0,
9294 "Test Area".to_string(),
9295 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9296 (0, 0, 0).into(),
9297 cortical_type,
9298 )
9299 .unwrap();
9300
9301 let cortical_idx = manager.add_cortical_area(area).unwrap();
9302
9303 assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
9305
9306 assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
9308
9309 let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
9311 assert_eq!(retrieved_area.name, "Test Area");
9312 }
9313
9314 #[test]
9315 fn test_remove_cortical_area() {
9316 ConnectomeManager::reset_for_testing();
9317
9318 let instance = ConnectomeManager::instance();
9319 let mut manager = instance.write();
9320
9321 use feagi_structures::genomic::cortical_area::{
9322 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9323 };
9324 let cortical_id = CoreCorticalType::Power.to_cortical_id();
9325
9326 if manager.has_cortical_area(&cortical_id) {
9328 manager.remove_cortical_area(&cortical_id).unwrap();
9329 }
9330
9331 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9332 let area = CorticalArea::new(
9333 cortical_id,
9334 0,
9335 "Test".to_string(),
9336 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9337 (0, 0, 0).into(),
9338 cortical_type,
9339 )
9340 .unwrap();
9341
9342 let initial_count = manager.get_cortical_area_count();
9343 manager.add_cortical_area(area).unwrap();
9344 assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
9345
9346 let areas_hash_before = feagi_state_manager::StateManager::instance()
9347 .read()
9348 .get_cortical_areas_hash();
9349 let geometry_hash_before = feagi_state_manager::StateManager::instance()
9350 .read()
9351 .get_brain_geometry_hash();
9352
9353 manager.remove_cortical_area(&cortical_id).unwrap();
9354 assert_eq!(manager.get_cortical_area_count(), initial_count);
9355 assert!(!manager.has_cortical_area(&cortical_id));
9356
9357 let areas_hash_after = feagi_state_manager::StateManager::instance()
9358 .read()
9359 .get_cortical_areas_hash();
9360 let geometry_hash_after = feagi_state_manager::StateManager::instance()
9361 .read()
9362 .get_brain_geometry_hash();
9363 assert_ne!(
9364 areas_hash_before, areas_hash_after,
9365 "remove_cortical_area must publish a new cortical_areas_hash"
9366 );
9367 assert_ne!(
9368 geometry_hash_before, geometry_hash_after,
9369 "remove_cortical_area must publish a new brain_geometry_hash"
9370 );
9371 }
9372
9373 #[test]
9374 fn test_duplicate_area_error() {
9375 ConnectomeManager::reset_for_testing();
9376
9377 let instance = ConnectomeManager::instance();
9378 let mut manager = instance.write();
9379
9380 use feagi_structures::genomic::cortical_area::{
9381 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9382 };
9383 let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
9386 let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9387 let area1 = CorticalArea::new(
9388 cortical_id,
9389 0,
9390 "First".to_string(),
9391 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9392 (0, 0, 0).into(),
9393 cortical_type,
9394 )
9395 .unwrap();
9396
9397 let area2 = CorticalArea::new(
9398 cortical_id, 1,
9400 "Second".to_string(),
9401 CorticalAreaDimensions::new(10, 10, 10).unwrap(),
9402 (0, 0, 0).into(),
9403 cortical_type,
9404 )
9405 .unwrap();
9406
9407 manager.add_cortical_area(area1).unwrap();
9408 let result = manager.add_cortical_area(area2);
9409
9410 assert!(result.is_err());
9411 }
9412
9413 #[test]
9414 fn test_brain_region_management() {
9415 ConnectomeManager::reset_for_testing();
9416
9417 let instance = ConnectomeManager::instance();
9418 let mut manager = instance.write();
9419
9420 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
9421 let region_id_str = region_id.to_string();
9422 let root = BrainRegion::new(
9423 region_id,
9424 "Root".to_string(),
9425 feagi_structures::genomic::brain_regions::RegionType::Undefined,
9426 )
9427 .unwrap();
9428
9429 let initial_count = manager.get_brain_region_ids().len();
9430 manager.add_brain_region(root, None).unwrap();
9431
9432 assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
9433 assert!(manager.get_brain_region(®ion_id_str).is_some());
9434 }
9435
9436 #[test]
9437 fn test_update_brain_region_description_property() {
9438 ConnectomeManager::reset_for_testing();
9439
9440 let instance = ConnectomeManager::instance();
9441 let mut manager = instance.write();
9442
9443 let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
9444 let region_id_str = region_id.to_string();
9445 let root = BrainRegion::new(
9446 region_id,
9447 "Root".to_string(),
9448 feagi_structures::genomic::brain_regions::RegionType::Undefined,
9449 )
9450 .unwrap();
9451 manager.add_brain_region(root, None).unwrap();
9452
9453 let mut properties = std::collections::HashMap::new();
9454 properties.insert(
9455 "description".to_string(),
9456 serde_json::json!("Holds core physiology"),
9457 );
9458 manager
9459 .update_brain_region_properties(®ion_id_str, properties)
9460 .unwrap();
9461
9462 let updated = manager
9463 .get_brain_region(®ion_id_str)
9464 .expect("region exists after description update");
9465 assert_eq!(
9466 updated.get_property("description"),
9467 Some(&serde_json::json!("Holds core physiology"))
9468 );
9469 }
9470
9471 #[test]
9472 fn test_synapse_operations() {
9473 use feagi_npu_burst_engine::npu::RustNPU;
9474 use feagi_npu_burst_engine::TracingMutex;
9475 use std::sync::Arc;
9476
9477 use feagi_npu_burst_engine::backend::CPUBackend;
9479 use feagi_npu_burst_engine::DynamicNPU;
9480 use feagi_npu_runtime::StdRuntime;
9481
9482 let runtime = StdRuntime;
9483 let backend = CPUBackend::new();
9484 let npu_result =
9485 RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
9486 let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
9487 let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
9488
9489 use feagi_structures::genomic::cortical_area::{
9491 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9492 };
9493 let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
9495 let area = CorticalArea::new(
9496 cortical_id,
9497 0, "Test Area".to_string(),
9499 CorticalAreaDimensions::new(10, 10, 1).unwrap(),
9500 (0, 0, 0).into(), cortical_type,
9502 )
9503 .unwrap();
9504 let cortical_idx = manager.add_cortical_area(area).unwrap();
9505
9506 if let Some(npu_arc) = manager.get_npu() {
9508 if let Ok(mut npu_guard) = npu_arc.try_lock() {
9509 if let DynamicNPU::F32(ref mut npu) = *npu_guard {
9510 npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
9511 }
9512 }
9513 }
9514
9515 let neuron1_id = manager
9517 .add_neuron(
9518 &cortical_id,
9519 0,
9520 0,
9521 0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
9533 .unwrap();
9534
9535 let neuron2_id = manager
9536 .add_neuron(
9537 &cortical_id,
9538 1,
9539 0,
9540 0, 100.0,
9542 f32::MAX, 0.1,
9544 -60.0,
9545 0,
9546 2,
9547 1.0,
9548 5,
9549 10,
9550 false,
9551 )
9552 .unwrap();
9553
9554 manager
9556 .create_synapse(
9557 neuron1_id, neuron2_id, 128.0, 64.0, 0, )
9561 .unwrap();
9562
9563 println!("✅ Synapse creation test passed");
9566 }
9567
9568 #[test]
9569 fn test_apply_cortical_mapping_missing_rules_is_ok() {
9570 let mut manager = ConnectomeManager::new_for_testing();
9573
9574 use feagi_structures::genomic::cortical_area::{
9575 CorticalAreaType, IOCorticalAreaConfigurationFlag,
9576 };
9577
9578 let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
9579 let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
9580
9581 let src_area = CorticalArea::new(
9582 src_id,
9583 0,
9584 "src".to_string(),
9585 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9586 (0, 0, 0).into(),
9587 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9588 )
9589 .unwrap();
9590
9591 let dst_area = CorticalArea::new(
9592 dst_id,
9593 1,
9594 "dst".to_string(),
9595 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9596 (0, 0, 0).into(),
9597 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9598 )
9599 .unwrap();
9600
9601 manager.add_cortical_area(src_area).unwrap();
9602 manager.add_cortical_area(dst_area).unwrap();
9603
9604 let count = manager
9606 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
9607 .unwrap();
9608 assert_eq!(count, 0);
9609
9610 manager
9612 .update_cortical_mapping(
9613 &src_id,
9614 &dst_id,
9615 vec![serde_json::json!({"morphology_id":"m1"})],
9616 )
9617 .unwrap();
9618 manager
9619 .update_cortical_mapping(&src_id, &dst_id, vec![])
9620 .unwrap();
9621
9622 let count2 = manager
9623 .apply_cortical_mapping_for_pair(&src_id, &dst_id)
9624 .unwrap();
9625 assert_eq!(count2, 0);
9626 }
9627
9628 #[test]
9629 fn test_get_mapping_rules_for_destination_supports_legacy_key() {
9630 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
9631 let mapping_dst = serde_json::json!({
9632 "csrc0002": [
9633 {"morphology_id": "m1"}
9634 ]
9635 });
9636 let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
9637
9638 let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
9639 .expect("legacy destination key should resolve");
9640 assert_eq!(rules.len(), 1);
9641 assert_eq!(
9642 rules[0].get("morphology_id").and_then(|v| v.as_str()),
9643 Some("m1")
9644 );
9645 }
9646
9647 #[test]
9648 fn test_get_neuron_properties_always_includes_neuron_state_keys() {
9649 use feagi_npu_burst_engine::backend::CPUBackend;
9650 use feagi_npu_burst_engine::RustNPU;
9651 use feagi_npu_burst_engine::TracingMutex;
9652 use feagi_npu_runtime::StdRuntime;
9653 use feagi_structures::genomic::cortical_area::{
9654 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9655 };
9656 use std::sync::Arc;
9657
9658 let runtime = StdRuntime;
9659 let backend = CPUBackend::new();
9660 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9661 let dyn_npu = Arc::new(TracingMutex::new(
9662 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9663 "TestNPU",
9664 ));
9665 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9666
9667 let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
9668 let area = CorticalArea::new(
9669 area_id,
9670 0,
9671 "n".to_string(),
9672 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9673 (0, 0, 0).into(),
9674 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9675 )
9676 .unwrap();
9677
9678 manager.add_cortical_area(area).unwrap();
9679 let nid = manager
9680 .add_neuron(
9681 &area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
9682 )
9683 .unwrap();
9684
9685 let props = manager
9686 .get_neuron_properties(nid)
9687 .expect("neuron properties");
9688 for key in [
9689 "consecutive_fire_count",
9690 "consecutive_fire_limit",
9691 "snooze_period",
9692 "membrane_potential",
9693 "threshold",
9694 "refractory_countdown",
9695 "mp_charge_accumulation",
9696 "neuron_type",
9697 "mp_driven_psp",
9698 "psp_uniform_distribution",
9699 "leak_coefficient",
9700 "resting_potential",
9701 "excitability",
9702 "threshold_limit",
9703 "refractory_period",
9704 ] {
9705 assert!(props.contains_key(key), "missing neuron state key: {key}");
9706 }
9707 }
9708
9709 #[test]
9710 fn test_mapping_deletion_prunes_synapses_between_areas() {
9711 use feagi_npu_burst_engine::backend::CPUBackend;
9712 use feagi_npu_burst_engine::RustNPU;
9713 use feagi_npu_burst_engine::TracingMutex;
9714 use feagi_npu_runtime::StdRuntime;
9715 use feagi_structures::genomic::cortical_area::{
9716 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9717 };
9718 use std::sync::Arc;
9719
9720 let runtime = StdRuntime;
9722 let backend = CPUBackend::new();
9723 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9724 let dyn_npu = Arc::new(TracingMutex::new(
9725 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9726 "TestNPU",
9727 ));
9728 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9729
9730 let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
9732 let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
9733
9734 let src_area = CorticalArea::new(
9735 src_id,
9736 0,
9737 "src".to_string(),
9738 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9739 (0, 0, 0).into(),
9740 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9741 )
9742 .unwrap();
9743 let dst_area = CorticalArea::new(
9744 dst_id,
9745 1,
9746 "dst".to_string(),
9747 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9748 (0, 0, 0).into(),
9749 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9750 )
9751 .unwrap();
9752
9753 manager.add_cortical_area(src_area).unwrap();
9754 manager.add_cortical_area(dst_area).unwrap();
9755
9756 let s0 = manager
9758 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9759 .unwrap();
9760 let s1 = manager
9761 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9762 .unwrap();
9763 let t0 = manager
9764 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9765 .unwrap();
9766 let t1 = manager
9767 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9768 .unwrap();
9769
9770 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
9772 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
9773
9774 {
9776 let mut npu = dyn_npu.lock().unwrap();
9777 npu.rebuild_synapse_index();
9778 assert_eq!(npu.get_synapse_count(), 2);
9779 }
9780
9781 manager
9783 .update_cortical_mapping(&src_id, &dst_id, vec![])
9784 .unwrap();
9785 let created = manager
9786 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9787 .unwrap();
9788 assert_eq!(created, 0);
9789
9790 {
9792 let mut npu = dyn_npu.lock().unwrap();
9793 npu.rebuild_synapse_index();
9795 assert_eq!(npu.get_synapse_count(), 0);
9796 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
9797 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
9798 }
9799 }
9800
9801 #[test]
9802 fn test_mapping_update_prunes_synapses_between_areas() {
9803 use feagi_npu_burst_engine::backend::CPUBackend;
9804 use feagi_npu_burst_engine::RustNPU;
9805 use feagi_npu_burst_engine::TracingMutex;
9806 use feagi_npu_runtime::StdRuntime;
9807 use feagi_structures::genomic::cortical_area::{
9808 CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
9809 };
9810 use std::sync::Arc;
9811
9812 let runtime = StdRuntime;
9814 let backend = CPUBackend::new();
9815 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9816 let dyn_npu = Arc::new(TracingMutex::new(
9817 feagi_npu_burst_engine::DynamicNPU::F32(npu),
9818 "TestNPU",
9819 ));
9820 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9821
9822 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9824
9825 let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
9828 let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
9829
9830 let src_area = CorticalArea::new(
9831 src_id,
9832 0,
9833 "src".to_string(),
9834 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9835 (0, 0, 0).into(),
9836 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
9837 )
9838 .unwrap();
9839 let dst_area = CorticalArea::new(
9840 dst_id,
9841 0,
9842 "dst".to_string(),
9843 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9844 (0, 0, 0).into(),
9845 CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
9846 )
9847 .unwrap();
9848
9849 manager.add_cortical_area(src_area).unwrap();
9850 manager.add_cortical_area(dst_area).unwrap();
9851
9852 let s0 = manager
9854 .add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9855 .unwrap();
9856 let s1 = manager
9857 .add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9858 .unwrap();
9859 let t0 = manager
9860 .add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9861 .unwrap();
9862 let t1 = manager
9863 .add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
9864 .unwrap();
9865
9866 manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
9868 manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
9869
9870 {
9872 let mut npu = dyn_npu.lock().unwrap();
9873 npu.rebuild_synapse_index();
9874 assert_eq!(npu.get_synapse_count(), 2);
9875 }
9876
9877 manager
9883 .update_cortical_mapping(
9884 &src_id,
9885 &dst_id,
9886 vec![serde_json::json!({
9887 "morphology_id": "episodic_memory",
9888 "morphology_scalar": [1],
9889 "postSynapticCurrent_multiplier": 1,
9890 "plasticity_flag": false,
9891 "plasticity_constant": 0,
9892 "ltp_multiplier": 0,
9893 "ltd_multiplier": 0,
9894 "plasticity_window": 0,
9895 })],
9896 )
9897 .unwrap();
9898 let created = manager
9899 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9900 .unwrap();
9901 assert_eq!(created, 0);
9902
9903 {
9905 let mut npu = dyn_npu.lock().unwrap();
9906 npu.rebuild_synapse_index();
9908 assert_eq!(npu.get_synapse_count(), 0);
9909 assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
9910 assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
9911 }
9912 }
9913
9914 #[test]
9915 fn test_upstream_area_tracking() {
9916 use crate::models::cortical_area::CorticalArea;
9918 use feagi_npu_burst_engine::backend::CPUBackend;
9919 use feagi_npu_burst_engine::TracingMutex;
9920 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
9921 use feagi_npu_runtime::StdRuntime;
9922 use feagi_structures::genomic::cortical_area::{
9923 CorticalAreaDimensions, CorticalAreaType, CorticalID,
9924 };
9925
9926 let runtime = StdRuntime;
9928 let backend = CPUBackend::new();
9929 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
9930 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
9931 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
9932
9933 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
9936
9937 let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
9939 let src_area = CorticalArea::new(
9940 src_id,
9941 0,
9942 "Source Area".to_string(),
9943 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9944 (0, 0, 0).into(),
9945 CorticalAreaType::Custom(
9946 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9947 ),
9948 )
9949 .unwrap();
9950 let src_idx = manager.add_cortical_area(src_area).unwrap();
9951
9952 let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
9954 let dst_area = CorticalArea::new(
9955 dst_id,
9956 0,
9957 "Dest Area".to_string(),
9958 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
9959 (0, 0, 0).into(),
9960 CorticalAreaType::Custom(
9961 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
9962 ),
9963 )
9964 .unwrap();
9965 manager.add_cortical_area(dst_area).unwrap();
9966
9967 {
9969 let dst_area = manager.get_cortical_area(&dst_id).unwrap();
9970 let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
9971 assert!(
9972 upstream.as_array().unwrap().is_empty(),
9973 "Upstream areas should be empty initially"
9974 );
9975 }
9976
9977 let mapping_data = vec![serde_json::json!({
9979 "morphology_id": "episodic_memory",
9980 "morphology_scalar": 1,
9981 "postSynapticCurrent_multiplier": 1.0,
9982 })];
9983 manager
9984 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
9985 .unwrap();
9986 manager
9987 .regenerate_synapses_for_mapping(&src_id, &dst_id)
9988 .unwrap();
9989
9990 {
9992 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
9993 assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
9994 assert_eq!(
9995 upstream_areas[0], src_idx,
9996 "Upstream area should be src_idx"
9997 );
9998 }
9999
10000 manager
10002 .update_cortical_mapping(&src_id, &dst_id, vec![])
10003 .unwrap();
10004 manager
10005 .regenerate_synapses_for_mapping(&src_id, &dst_id)
10006 .unwrap();
10007
10008 {
10010 let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
10011 assert_eq!(
10012 upstream_areas.len(),
10013 0,
10014 "Should have 0 upstream areas after deletion"
10015 );
10016 }
10017 }
10018
10019 #[test]
10020 fn test_refresh_upstream_areas_for_associative_memory_pairs() {
10021 use crate::models::cortical_area::CorticalArea;
10022 use feagi_npu_burst_engine::backend::CPUBackend;
10023 use feagi_npu_burst_engine::TracingMutex;
10024 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10025 use feagi_npu_runtime::StdRuntime;
10026 use feagi_structures::genomic::cortical_area::{
10027 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10028 };
10029 use std::sync::Arc;
10030
10031 let runtime = StdRuntime;
10032 let backend = CPUBackend::new();
10033 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10034 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10035 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10036 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10037
10038 let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
10039 let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
10040 let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
10041 let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
10042
10043 let a1_area = CorticalArea::new(
10044 a1_id,
10045 0,
10046 "A1".to_string(),
10047 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10048 (0, 0, 0).into(),
10049 CorticalAreaType::Custom(
10050 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10051 ),
10052 )
10053 .unwrap();
10054 let a2_area = CorticalArea::new(
10055 a2_id,
10056 0,
10057 "A2".to_string(),
10058 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10059 (0, 0, 0).into(),
10060 CorticalAreaType::Custom(
10061 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10062 ),
10063 )
10064 .unwrap();
10065
10066 let mut m1_area = CorticalArea::new(
10067 m1_id,
10068 0,
10069 "M1".to_string(),
10070 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10071 (0, 0, 0).into(),
10072 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10073 )
10074 .unwrap();
10075 m1_area
10076 .properties
10077 .insert("is_mem_type".to_string(), serde_json::json!(true));
10078 m1_area
10079 .properties
10080 .insert("temporal_depth".to_string(), serde_json::json!(1));
10081
10082 let mut m2_area = CorticalArea::new(
10083 m2_id,
10084 0,
10085 "M2".to_string(),
10086 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10087 (0, 0, 0).into(),
10088 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10089 )
10090 .unwrap();
10091 m2_area
10092 .properties
10093 .insert("is_mem_type".to_string(), serde_json::json!(true));
10094 m2_area
10095 .properties
10096 .insert("temporal_depth".to_string(), serde_json::json!(1));
10097
10098 let a1_idx = manager.add_cortical_area(a1_area).unwrap();
10099 let a2_idx = manager.add_cortical_area(a2_area).unwrap();
10100 let m1_idx = manager.add_cortical_area(m1_area).unwrap();
10101 let m2_idx = manager.add_cortical_area(m2_area).unwrap();
10102
10103 manager
10104 .add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10105 .unwrap();
10106 manager
10107 .add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10108 .unwrap();
10109
10110 let episodic_mapping = vec![serde_json::json!({
10111 "morphology_id": "episodic_memory",
10112 "morphology_scalar": 1,
10113 "postSynapticCurrent_multiplier": 1.0,
10114 })];
10115 manager
10116 .update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
10117 .unwrap();
10118 manager
10119 .regenerate_synapses_for_mapping(&a1_id, &m1_id)
10120 .unwrap();
10121 manager
10122 .update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
10123 .unwrap();
10124 manager
10125 .regenerate_synapses_for_mapping(&a2_id, &m2_id)
10126 .unwrap();
10127
10128 let assoc_mapping = vec![serde_json::json!({
10129 "morphology_id": "associative_memory",
10130 "morphology_scalar": 1,
10131 "postSynapticCurrent_multiplier": 1.0,
10132 "plasticity_flag": true,
10133 "plasticity_constant": 1,
10134 "ltp_multiplier": 1,
10135 "ltd_multiplier": 1,
10136 "plasticity_window": 5,
10137 })];
10138 manager
10139 .update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
10140 .unwrap();
10141 manager
10142 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
10143 .unwrap();
10144 manager
10146 .update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
10147 .unwrap();
10148 manager
10149 .regenerate_synapses_for_mapping(&m2_id, &m1_id)
10150 .unwrap();
10151
10152 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
10153 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
10154 assert_eq!(
10155 upstream_m1.len(),
10156 2,
10157 "M1 should have A1 and M2 as upstreams once both directed associative edges exist"
10158 );
10159 assert_eq!(
10160 upstream_m2.len(),
10161 2,
10162 "M2 should have A2 and M1 as upstreams"
10163 );
10164
10165 manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
10166 manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
10167
10168 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
10169 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
10170 assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
10171 assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
10172 assert!(upstream_m1.contains(&a1_idx));
10173 assert!(upstream_m1.contains(&m2_idx));
10174 assert!(upstream_m2.contains(&a2_idx));
10175 assert!(upstream_m2.contains(&m1_idx));
10176
10177 {
10179 let mut npu_lock = dyn_npu.lock().unwrap();
10180 let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
10181 let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
10182 assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
10183 assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
10184 npu_lock.process_burst().expect("Burst processing failed");
10185 }
10186
10187 let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
10188 let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
10189 assert_eq!(
10190 upstream_m1.len(),
10191 2,
10192 "M1 should keep 2 upstreams after firing"
10193 );
10194 assert_eq!(
10195 upstream_m2.len(),
10196 2,
10197 "M2 should keep 2 upstreams after firing"
10198 );
10199
10200 let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
10201 let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
10202 assert_eq!(
10203 episodic_upstream_m1,
10204 vec![a1_idx],
10205 "Episodic upstream list for M1 should exclude associative-only memory source M2"
10206 );
10207 assert_eq!(
10208 episodic_upstream_m2,
10209 vec![a2_idx],
10210 "Episodic upstream list for M2 should exclude associative-only memory source M1"
10211 );
10212 }
10213
10214 #[test]
10215 fn test_memory_to_non_memory_requires_associative_morphology() {
10216 use crate::models::cortical_area::CorticalArea;
10217 use feagi_structures::genomic::cortical_area::{
10218 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10219 MemoryCorticalType,
10220 };
10221
10222 let mut manager = ConnectomeManager::new_for_testing();
10223 let memory_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10224 let destination_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
10225 let memory_area = CorticalArea::new(
10226 memory_id,
10227 0,
10228 "Memory Area".to_string(),
10229 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10230 (0, 0, 0).into(),
10231 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10232 )
10233 .unwrap();
10234 let destination_area = CorticalArea::new(
10235 destination_id,
10236 0,
10237 "Destination Area".to_string(),
10238 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10239 (1, 0, 0).into(),
10240 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10241 )
10242 .unwrap();
10243 manager.add_cortical_area(memory_area).unwrap();
10244 manager.add_cortical_area(destination_area).unwrap();
10245
10246 let invalid = manager.update_cortical_mapping(
10247 &memory_id,
10248 &destination_id,
10249 vec![serde_json::json!({"morphology_id": "episodic_memory"})],
10250 );
10251 assert!(matches!(invalid, Err(BduError::InvalidMorphology(_))));
10252
10253 manager
10254 .update_cortical_mapping(
10255 &memory_id,
10256 &destination_id,
10257 vec![serde_json::json!({"morphology_id": "associative_memory"})],
10258 )
10259 .unwrap();
10260 }
10261
10262 #[test]
10263 fn test_memory_twin_created_for_memory_mapping() {
10264 use crate::models::cortical_area::CorticalArea;
10265 use feagi_npu_burst_engine::backend::CPUBackend;
10266 use feagi_npu_burst_engine::TracingMutex;
10267 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10268 use feagi_npu_runtime::StdRuntime;
10269 use feagi_structures::genomic::cortical_area::{
10270 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10271 MemoryCorticalType,
10272 };
10273 use std::sync::Arc;
10274
10275 let runtime = StdRuntime;
10276 let backend = CPUBackend::new();
10277 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10278 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10279 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10280 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10281
10282 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
10283 let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10284
10285 let src_area = CorticalArea::new(
10286 src_id,
10287 0,
10288 "Source Area".to_string(),
10289 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10290 (0, 0, 0).into(),
10291 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10292 )
10293 .unwrap();
10294 let mut dst_area = CorticalArea::new(
10295 dst_id,
10296 0,
10297 "Memory Area".to_string(),
10298 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10299 (0, 0, 0).into(),
10300 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10301 )
10302 .unwrap();
10303 dst_area
10304 .properties
10305 .insert("is_mem_type".to_string(), serde_json::json!(true));
10306 dst_area
10307 .properties
10308 .insert("temporal_depth".to_string(), serde_json::json!(1));
10309
10310 manager.add_cortical_area(src_area).unwrap();
10311 manager.add_cortical_area(dst_area).unwrap();
10312
10313 let mapping_data = vec![serde_json::json!({
10314 "morphology_id": "episodic_memory",
10315 "morphology_scalar": 1,
10316 "postSynapticCurrent_multiplier": 1.0,
10317 })];
10318 manager
10319 .update_cortical_mapping(&src_id, &dst_id, mapping_data)
10320 .unwrap();
10321 manager
10322 .regenerate_synapses_for_mapping(&src_id, &dst_id)
10323 .unwrap();
10324
10325 let memory_area = manager.get_cortical_area(&dst_id).unwrap();
10326 let twin_map = memory_area
10327 .properties
10328 .get("memory_twin_areas")
10329 .and_then(|v| v.as_object())
10330 .expect("memory_twin_areas should be set");
10331 let twin_id_str = twin_map
10332 .get(&src_id.as_base_64())
10333 .and_then(|v| v.as_str())
10334 .expect("Missing twin entry for upstream area");
10335 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
10336 let mapping = memory_area
10337 .properties
10338 .get("cortical_mapping_dst")
10339 .and_then(|v| v.as_object())
10340 .and_then(|map| map.get(&twin_id.as_base_64()))
10341 .and_then(|v| v.as_array())
10342 .expect("Missing memory replay mapping for twin area");
10343 let uses_replay = mapping.iter().any(|rule| {
10344 rule.get("morphology_id")
10345 .and_then(|v| v.as_str())
10346 .is_some_and(|id| id == "memory_replay")
10347 });
10348 assert!(uses_replay, "Expected memory_replay mapping for twin area");
10349
10350 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10351 assert!(matches!(
10352 twin_area.cortical_type,
10353 CorticalAreaType::Custom(_)
10354 ));
10355 assert_eq!(
10356 twin_area
10357 .properties
10358 .get("memory_twin_of")
10359 .and_then(|v| v.as_str()),
10360 Some(src_id.as_base_64().as_str())
10361 );
10362 assert_eq!(
10363 twin_area
10364 .properties
10365 .get("memory_twin_for")
10366 .and_then(|v| v.as_str()),
10367 Some(dst_id.as_base_64().as_str())
10368 );
10369 }
10370
10371 #[test]
10372 fn test_associative_memory_between_memory_areas_creates_synapses() {
10373 use crate::models::cortical_area::CorticalArea;
10374 use feagi_npu_burst_engine::backend::CPUBackend;
10375 use feagi_npu_burst_engine::TracingMutex;
10376 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10377 use feagi_npu_runtime::StdRuntime;
10378 use feagi_structures::genomic::cortical_area::{
10379 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10380 };
10381 use std::sync::Arc;
10382
10383 let runtime = StdRuntime;
10384 let backend = CPUBackend::new();
10385 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10386 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10387 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10388 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10389
10390 let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
10391 let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
10392
10393 let mut m1_area = CorticalArea::new(
10394 m1_id,
10395 0,
10396 "Memory M1".to_string(),
10397 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10398 (0, 0, 0).into(),
10399 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10400 )
10401 .unwrap();
10402 m1_area
10403 .properties
10404 .insert("is_mem_type".to_string(), serde_json::json!(true));
10405 m1_area
10406 .properties
10407 .insert("temporal_depth".to_string(), serde_json::json!(1));
10408
10409 let mut m2_area = CorticalArea::new(
10410 m2_id,
10411 0,
10412 "Memory M2".to_string(),
10413 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10414 (0, 0, 0).into(),
10415 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10416 )
10417 .unwrap();
10418 m2_area
10419 .properties
10420 .insert("is_mem_type".to_string(), serde_json::json!(true));
10421 m2_area
10422 .properties
10423 .insert("temporal_depth".to_string(), serde_json::json!(1));
10424
10425 manager.add_cortical_area(m1_area).unwrap();
10426 manager.add_cortical_area(m2_area).unwrap();
10427
10428 manager
10429 .add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10430 .unwrap();
10431 manager
10432 .add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
10433 .unwrap();
10434
10435 let mapping_data = vec![serde_json::json!({
10436 "morphology_id": "associative_memory",
10437 "morphology_scalar": 1,
10438 "postSynapticCurrent_multiplier": 1.0,
10439 "plasticity_flag": true,
10440 "plasticity_constant": 1,
10441 "ltp_multiplier": 1,
10442 "ltd_multiplier": 1,
10443 "plasticity_window": 5,
10444 })];
10445 manager
10446 .update_cortical_mapping(&m1_id, &m2_id, mapping_data)
10447 .unwrap();
10448 let created = manager
10449 .regenerate_synapses_for_mapping(&m1_id, &m2_id)
10450 .unwrap();
10451 assert!(
10452 created > 0,
10453 "Expected associative memory mapping between memory areas to create synapses"
10454 );
10455 let npu_guard = dyn_npu.lock().unwrap();
10456 let assoc_tagged =
10457 npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
10458 assert!(
10459 assoc_tagged >= 1,
10460 "associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
10461 );
10462 }
10463
10464 #[test]
10465 fn test_scan_twin_uses_field_xy_and_class_count() {
10466 use crate::models::cortical_area::CorticalArea;
10467 use feagi_npu_burst_engine::backend::CPUBackend;
10468 use feagi_npu_burst_engine::TracingMutex;
10469 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10470 use feagi_npu_runtime::StdRuntime;
10471 use feagi_structures::genomic::cortical_area::{
10472 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10473 MemoryCorticalType,
10474 };
10475 use std::sync::Arc;
10476
10477 let runtime = StdRuntime;
10478 let backend = CPUBackend::new();
10479 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10480 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10481 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10482 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10483
10484 let field_id = CorticalID::try_from_bytes(b"cfld0001").unwrap();
10485 let class_id = CorticalID::try_from_bytes(b"ccls0001").unwrap();
10486 let mem_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
10487
10488 let field_area = CorticalArea::new(
10489 field_id,
10490 0,
10491 "Field".to_string(),
10492 CorticalAreaDimensions::new(8, 6, 4).unwrap(),
10493 (0, 0, 0).into(),
10494 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10495 )
10496 .unwrap();
10497 let class_area = CorticalArea::new(
10498 class_id,
10499 0,
10500 "Class".to_string(),
10501 CorticalAreaDimensions::new(1, 1, 5).unwrap(),
10502 (20, 0, 0).into(),
10503 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10504 )
10505 .unwrap();
10506 let mut mem_area = CorticalArea::new(
10507 mem_id,
10508 0,
10509 "KernelMem".to_string(),
10510 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10511 (40, 0, 0).into(),
10512 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10513 )
10514 .unwrap();
10515 mem_area
10516 .properties
10517 .insert("is_mem_type".to_string(), serde_json::json!(true));
10518 mem_area.properties.insert(
10519 "classifier_class_area_id".to_string(),
10520 serde_json::json!(class_id.as_base_64()),
10521 );
10522
10523 manager.add_cortical_area(field_area).unwrap();
10524 manager.add_cortical_area(class_area).unwrap();
10525 manager.add_cortical_area(mem_area).unwrap();
10526
10527 let mapping_data = vec![serde_json::json!({
10528 "morphology_id": "episodic_scan",
10529 "morphology_scalar": 1,
10530 "postSynapticCurrent_multiplier": 1.0,
10531 })];
10532 manager
10533 .update_cortical_mapping(&field_id, &mem_id, mapping_data)
10534 .unwrap();
10535 manager
10536 .regenerate_synapses_for_mapping(&field_id, &mem_id)
10537 .unwrap();
10538
10539 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
10540 let twin_map = memory_area
10541 .properties
10542 .get("memory_twin_areas")
10543 .and_then(|v| v.as_object())
10544 .expect("memory_twin_areas should be set");
10545 let twin_id_str = twin_map
10546 .get(&field_id.as_base_64())
10547 .and_then(|v| v.as_str())
10548 .expect("Missing scan twin entry");
10549 let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
10550 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10551 assert_eq!(twin_area.dimensions.width, 8);
10552 assert_eq!(twin_area.dimensions.height, 6);
10553 assert_eq!(twin_area.dimensions.depth, 5);
10554 assert_eq!(
10555 twin_area
10556 .properties
10557 .get("scan_twin")
10558 .and_then(|v| v.as_bool()),
10559 Some(true)
10560 );
10561 let has_replay = memory_area
10562 .properties
10563 .get("cortical_mapping_dst")
10564 .and_then(|v| v.as_object())
10565 .and_then(|map| map.get(&twin_id.as_base_64()))
10566 .is_some();
10567 assert!(!has_replay, "scan twin must not receive memory_replay");
10568 let episodic_upstreams = manager.get_episodic_memory_upstream_cortical_areas(&mem_id);
10569 let field_idx = manager.get_cortical_idx(&field_id).unwrap();
10570 assert!(
10571 !episodic_upstreams.contains(&field_idx),
10572 "scan source must not enter episodic hash upstreams"
10573 );
10574 }
10575
10576 #[test]
10577 fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
10578 use crate::models::cortical_area::CorticalArea;
10579 use feagi_npu_burst_engine::backend::CPUBackend;
10580 use feagi_npu_burst_engine::TracingMutex;
10581 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
10582 use feagi_npu_runtime::StdRuntime;
10583 use feagi_structures::genomic::cortical_area::{
10584 CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
10585 MemoryCorticalType,
10586 };
10587 use std::sync::Arc;
10588
10589 let runtime = StdRuntime;
10590 let backend = CPUBackend::new();
10591 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
10592 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
10593 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
10594 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
10595
10596 let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
10597 let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
10598
10599 let src_area = CorticalArea::new(
10600 src_id,
10601 0,
10602 "Source Area".to_string(),
10603 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10604 (0, 0, 0).into(),
10605 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
10606 )
10607 .unwrap();
10608 let mut mem_area = CorticalArea::new(
10609 mem_id,
10610 0,
10611 "Memory Area".to_string(),
10612 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10613 (0, 0, 0).into(),
10614 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10615 )
10616 .unwrap();
10617 mem_area
10618 .properties
10619 .insert("is_mem_type".to_string(), serde_json::json!(true));
10620 mem_area
10621 .properties
10622 .insert("temporal_depth".to_string(), serde_json::json!(1));
10623
10624 manager.add_cortical_area(src_area).unwrap();
10625 manager.add_cortical_area(mem_area).unwrap();
10626
10627 let twin_id = manager
10628 .build_memory_twin_id(&mem_id, &src_id)
10629 .expect("Failed to build twin id");
10630 let twin_area = CorticalArea::new(
10631 twin_id,
10632 0,
10633 "Source Area_twin".to_string(),
10634 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10635 (0, 0, 0).into(),
10636 CorticalAreaType::Custom(
10637 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
10638 ),
10639 )
10640 .unwrap();
10641 manager.add_cortical_area(twin_area).unwrap();
10642
10643 let repaired = manager
10644 .ensure_memory_twin_area(&mem_id, &src_id)
10645 .expect("Failed to repair twin");
10646 assert_eq!(repaired, twin_id);
10647
10648 let mem_area = manager.get_cortical_area(&mem_id).unwrap();
10649 let twin_map = mem_area
10650 .properties
10651 .get("memory_twin_areas")
10652 .and_then(|v| v.as_object())
10653 .expect("memory_twin_areas should be set");
10654 let twin_id_str = twin_map
10655 .get(&src_id.as_base_64())
10656 .and_then(|v| v.as_str())
10657 .expect("Missing twin entry for upstream area");
10658 assert_eq!(twin_id_str, twin_id.as_base_64());
10659
10660 let replay_map = mem_area
10661 .properties
10662 .get("cortical_mapping_dst")
10663 .and_then(|v| v.as_object())
10664 .and_then(|map| map.get(&twin_id.as_base_64()))
10665 .and_then(|v| v.as_array())
10666 .expect("Missing memory replay mapping for twin area");
10667 let uses_replay = replay_map.iter().any(|rule| {
10668 rule.get("morphology_id")
10669 .and_then(|v| v.as_str())
10670 .is_some_and(|id| id == "memory_replay")
10671 });
10672 assert!(uses_replay, "Expected memory_replay mapping for twin area");
10673
10674 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
10675 assert_eq!(
10676 twin_area
10677 .properties
10678 .get("memory_twin_of")
10679 .and_then(|v| v.as_str()),
10680 Some(src_id.as_base_64().as_str())
10681 );
10682 assert_eq!(
10683 twin_area
10684 .properties
10685 .get("memory_twin_for")
10686 .and_then(|v| v.as_str()),
10687 Some(mem_id.as_base_64().as_str())
10688 );
10689 }
10690
10691 #[test]
10692 fn classifier_mapping_change_updates_kernel_slot_not_field() {
10693 let mut manager = ConnectomeManager::new_for_testing();
10694 let mut classifier = feagi_structures::genomic::classifiers::Classifier {
10695 classifier_id: "clf-1".to_string(),
10696 name: "demo".to_string(),
10697 parent_region_id: "root".to_string(),
10698 coordinates_3d: [0, 0, 0],
10699 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
10700 kernel_area_id: None,
10701 class_area_id: None,
10702 mask_area_id: None,
10703 kernel_size: None,
10704 fields: Vec::new(),
10705 kernel_memory_id: "mkmem1".to_string(),
10706 class_memory_id: "mcmem1".to_string(),
10707 properties: HashMap::new(),
10708 };
10709 manager.upsert_classifier(classifier.clone());
10710 let hash_after_upsert = feagi_state_manager::StateManager::instance()
10711 .read()
10712 .get_classifiers_hash();
10713 assert_ne!(hash_after_upsert, 0, "upsert must publish classifiers_hash");
10714 let ignored = manager.apply_classifier_mapping_change(
10715 "cfield",
10716 "mkmem1",
10717 feagi_structures::genomic::classifiers::CLASSIFIER_SCAN_MORPHOLOGY,
10718 false,
10719 );
10720 assert_eq!(ignored, 0);
10721 assert!(manager.get_classifier("clf-1").unwrap().fields.is_empty());
10722 let updated = manager.apply_classifier_mapping_change(
10723 "ckern1",
10724 "mkmem1",
10725 feagi_structures::genomic::classifiers::CLASSIFIER_KERNEL_MORPHOLOGY,
10726 false,
10727 );
10728 assert_eq!(updated, 1);
10729 classifier = manager.get_classifier("clf-1").cloned().unwrap();
10730 assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
10731 let hash_after_kernel_bind = feagi_state_manager::StateManager::instance()
10732 .read()
10733 .get_classifiers_hash();
10734 assert_ne!(
10735 hash_after_kernel_bind, hash_after_upsert,
10736 "binding a classifier input must change classifiers_hash"
10737 );
10738 }
10739
10740 #[test]
10741 fn deleting_referenced_input_clears_classifier_slot() {
10742 let mut manager = ConnectomeManager::new_for_testing();
10743 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
10744 classifier_id: "clf-1".to_string(),
10745 name: "demo".to_string(),
10746 parent_region_id: "root".to_string(),
10747 coordinates_3d: [0, 0, 0],
10748 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
10749 kernel_area_id: Some("ckern1".to_string()),
10750 class_area_id: Some("ccls01".to_string()),
10751 mask_area_id: None,
10752 kernel_size: None,
10753 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
10754 field_area_id: "cfield".to_string(),
10755 scan_twin_id: "cscan1".to_string(),
10756 }],
10757 kernel_memory_id: "mkmem1".to_string(),
10758 class_memory_id: "mcmem1".to_string(),
10759 properties: HashMap::new(),
10760 });
10761 assert_eq!(manager.clear_classifier_inputs_for_area("cfield"), 1);
10762 let classifier = manager.get_classifier("clf-1").unwrap();
10763 assert!(classifier.binding_for_field("cfield").is_none());
10764 assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
10765 assert!(manager.classifiers_owning_area("cfield").is_empty());
10766 assert_eq!(manager.classifiers_owning_area("mkmem1").len(), 1);
10767 let hash_after_clear = feagi_state_manager::StateManager::instance()
10768 .read()
10769 .get_classifiers_hash();
10770 manager.remove_classifier("clf-1");
10771 let hash_after_remove = feagi_state_manager::StateManager::instance()
10772 .read()
10773 .get_classifiers_hash();
10774 assert_ne!(
10775 hash_after_remove, hash_after_clear,
10776 "removing a classifier must change classifiers_hash"
10777 );
10778 }
10779
10780 #[test]
10781 fn classifier_assembly_mappings_do_not_become_region_io() {
10782 use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
10783 use feagi_structures::genomic::cortical_area::{
10784 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10785 MemoryCorticalType,
10786 };
10787
10788 let mut manager = ConnectomeManager::new_for_testing();
10789 let root_id = RegionID::new();
10790 let child_id = RegionID::new();
10791 let root_key = root_id.to_string();
10792 let child_key = child_id.to_string();
10793 manager
10794 .add_brain_region(
10795 BrainRegion::new(root_id, "Root".to_string(), RegionType::Undefined).unwrap(),
10796 None,
10797 )
10798 .unwrap();
10799 manager
10800 .add_brain_region(
10801 BrainRegion::new(child_id, "Child".to_string(), RegionType::Undefined).unwrap(),
10802 Some(root_key.clone()),
10803 )
10804 .unwrap();
10805
10806 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
10807 let mem_id = CorticalID::try_from_bytes(b"mclfmem1").unwrap();
10808 let regular_field_id = CorticalID::try_from_bytes(b"cregfld1").unwrap();
10809 let regular_mem_id = CorticalID::try_from_bytes(b"mregmem1").unwrap();
10810
10811 let mut field = CorticalArea::new(
10812 field_id,
10813 0,
10814 "field".to_string(),
10815 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10816 (0, 0, 0).into(),
10817 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10818 )
10819 .unwrap();
10820 field.properties.insert(
10821 "parent_region_id".to_string(),
10822 serde_json::json!(child_key.clone()),
10823 );
10824 field.properties.insert(
10825 "cortical_mapping_dst".to_string(),
10826 serde_json::json!({
10827 mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
10828 }),
10829 );
10830
10831 let mut classifier_mem = CorticalArea::new(
10832 mem_id,
10833 0,
10834 "clf_kernel_mem".to_string(),
10835 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10836 (10, 0, 0).into(),
10837 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10838 )
10839 .unwrap();
10840 classifier_mem.properties.insert(
10841 "parent_region_id".to_string(),
10842 serde_json::json!(root_key.clone()),
10843 );
10844 classifier_mem
10845 .properties
10846 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10847 classifier_mem.properties.insert(
10848 "classifier_role".to_string(),
10849 serde_json::json!("kernel_memory"),
10850 );
10851
10852 let mut regular_field = CorticalArea::new(
10853 regular_field_id,
10854 0,
10855 "regular_field".to_string(),
10856 CorticalAreaDimensions::new(2, 2, 1).unwrap(),
10857 (20, 0, 0).into(),
10858 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10859 )
10860 .unwrap();
10861 regular_field.properties.insert(
10862 "parent_region_id".to_string(),
10863 serde_json::json!(child_key.clone()),
10864 );
10865 regular_field.properties.insert(
10866 "cortical_mapping_dst".to_string(),
10867 serde_json::json!({
10868 regular_mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
10869 }),
10870 );
10871
10872 let mut regular_mem = CorticalArea::new(
10873 regular_mem_id,
10874 0,
10875 "regular_mem".to_string(),
10876 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10877 (30, 0, 0).into(),
10878 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10879 )
10880 .unwrap();
10881 regular_mem.properties.insert(
10882 "parent_region_id".to_string(),
10883 serde_json::json!(root_key.clone()),
10884 );
10885
10886 manager.add_cortical_area(field).unwrap();
10887 manager.add_cortical_area(classifier_mem).unwrap();
10888 manager.add_cortical_area(regular_field).unwrap();
10889 manager.add_cortical_area(regular_mem).unwrap();
10890
10891 let io = manager.recompute_brain_region_io_registry().unwrap();
10892 let child_outputs = &io.get(&child_key).expect("child region io").1;
10893 assert!(
10894 !child_outputs.contains(&field_id.as_base_64()),
10895 "classifier input must stay inside the circuit; got outputs {child_outputs:?}"
10896 );
10897 assert!(
10898 child_outputs.contains(®ular_field_id.as_base_64()),
10899 "non-classifier cross-region mappings must still become region outputs; got {child_outputs:?}"
10900 );
10901 }
10902
10903 #[test]
10904 fn classifier_assembly_area_is_detected_from_role_or_flag() {
10905 use feagi_structures::genomic::cortical_area::{
10906 CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
10907 };
10908
10909 let mut flagged = CorticalArea::new(
10910 CorticalID::try_from_bytes(b"mflagged").unwrap(),
10911 0,
10912 "flagged".to_string(),
10913 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10914 (0, 0, 0).into(),
10915 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10916 )
10917 .unwrap();
10918 flagged
10919 .properties
10920 .insert("classifier_assembly".to_string(), serde_json::json!(true));
10921 assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
10922 &flagged
10923 ));
10924
10925 let mut role = CorticalArea::new(
10926 CorticalID::try_from_bytes(b"mrole001").unwrap(),
10927 0,
10928 "role".to_string(),
10929 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10930 (0, 0, 0).into(),
10931 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10932 )
10933 .unwrap();
10934 role.properties.insert(
10935 "classifier_role".to_string(),
10936 serde_json::json!("scan_twin"),
10937 );
10938 assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
10939 &role
10940 ));
10941
10942 let plain = CorticalArea::new(
10943 CorticalID::try_from_bytes(b"mplain01").unwrap(),
10944 0,
10945 "plain".to_string(),
10946 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10947 (0, 0, 0).into(),
10948 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10949 )
10950 .unwrap();
10951 assert!(!ConnectomeManager::area_belongs_to_classifier_assembly(
10952 &plain
10953 ));
10954 }
10955
10956 #[test]
10957 fn rekey_memory_twin_source_retargets_classifier_stamp() {
10958 use feagi_structures::genomic::cortical_area::{
10959 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
10960 MemoryCorticalType,
10961 };
10962
10963 let mut manager = ConnectomeManager::new_for_testing();
10964 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
10965 let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
10966 let mut mem_area = CorticalArea::new(
10967 mem_id,
10968 0,
10969 "kernel_mem".to_string(),
10970 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
10971 (0, 0, 0).into(),
10972 CorticalAreaType::Memory(MemoryCorticalType::Memory),
10973 )
10974 .unwrap();
10975 mem_area.properties.insert(
10976 "classifier_role".to_string(),
10977 serde_json::json!("kernel_memory"),
10978 );
10979 mem_area.properties.insert(
10980 "memory_twin_areas".to_string(),
10981 serde_json::json!({ "cfield01": stamp_id.as_base_64() }),
10982 );
10983 let mut stamp = CorticalArea::new(
10984 stamp_id,
10985 0,
10986 "stamp".to_string(),
10987 CorticalAreaDimensions::new(4, 4, 2).unwrap(),
10988 (0, 0, 0).into(),
10989 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
10990 )
10991 .unwrap();
10992 stamp.properties.insert(
10993 "classifier_role".to_string(),
10994 serde_json::json!("scan_twin"),
10995 );
10996 stamp
10997 .properties
10998 .insert("memory_twin_of".to_string(), serde_json::json!("cfield01"));
10999 manager.add_cortical_area(mem_area).unwrap();
11000 manager.add_cortical_area(stamp).unwrap();
11001 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
11002 classifier_id: "clf-1".to_string(),
11003 name: "demo".to_string(),
11004 parent_region_id: "root".to_string(),
11005 coordinates_3d: [0, 0, 0],
11006 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
11007 kernel_area_id: Some("ckern001".to_string()),
11008 class_area_id: Some("ccls0001".to_string()),
11009 mask_area_id: None,
11010 kernel_size: None,
11011 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
11012 field_area_id: "cfield01".to_string(),
11013 scan_twin_id: stamp_id.as_base_64(),
11014 }],
11015 kernel_memory_id: mem_id.as_base_64(),
11016 class_memory_id: "mcmem001".to_string(),
11017 properties: HashMap::new(),
11018 });
11019
11020 manager
11021 .rekey_memory_twin_source(&mem_id.as_base_64(), "cfield01", "cfield02")
11022 .unwrap();
11023
11024 let memory_area = manager.get_cortical_area(&mem_id).unwrap();
11025 let twins = memory_area
11026 .properties
11027 .get("memory_twin_areas")
11028 .and_then(|value| value.as_object())
11029 .expect("memory_twin_areas");
11030 assert_eq!(
11031 twins.get("cfield02").and_then(|value| value.as_str()),
11032 Some(stamp_id.as_base_64().as_str())
11033 );
11034 assert!(!twins.contains_key("cfield01"));
11035 let stamp = manager.get_cortical_area(&stamp_id).unwrap();
11036 assert_eq!(
11037 stamp
11038 .properties
11039 .get("memory_twin_of")
11040 .and_then(|value| value.as_str()),
11041 Some("cfield02")
11042 );
11043 }
11044
11045 #[test]
11046 fn classifier_stamp_survives_field_mapping_delete() {
11047 use feagi_npu_burst_engine::backend::CPUBackend;
11048 use feagi_npu_burst_engine::TracingMutex;
11049 use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
11050 use feagi_npu_runtime::StdRuntime;
11051 use feagi_structures::genomic::cortical_area::{
11052 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11053 IOCorticalAreaConfigurationFlag, MemoryCorticalType,
11054 };
11055 use std::sync::Arc;
11056
11057 let runtime = StdRuntime;
11058 let backend = CPUBackend::new();
11059 let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
11060 let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
11061 let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
11062 feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
11063
11064 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
11065 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11066 let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
11067 let field_area = CorticalArea::new(
11068 field_id,
11069 0,
11070 "Field".to_string(),
11071 CorticalAreaDimensions::new(4, 3, 1).unwrap(),
11072 (0, 0, 0).into(),
11073 CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
11074 )
11075 .unwrap();
11076 let mut mem_area = CorticalArea::new(
11077 mem_id,
11078 0,
11079 "kernel_mem".to_string(),
11080 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11081 (10, 0, 0).into(),
11082 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11083 )
11084 .unwrap();
11085 mem_area.properties.insert(
11086 "classifier_role".to_string(),
11087 serde_json::json!("kernel_memory"),
11088 );
11089 mem_area
11090 .properties
11091 .insert("classifier_assembly".to_string(), serde_json::json!(true));
11092 mem_area.properties.insert(
11093 "memory_twin_areas".to_string(),
11094 serde_json::json!({ field_id.as_base_64(): stamp_id.as_base_64() }),
11095 );
11096 let mut stamp = CorticalArea::new(
11097 stamp_id,
11098 0,
11099 "stamp".to_string(),
11100 CorticalAreaDimensions::new(4, 3, 2).unwrap(),
11101 (20, 0, 0).into(),
11102 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11103 )
11104 .unwrap();
11105 stamp.properties.insert(
11106 "classifier_role".to_string(),
11107 serde_json::json!("scan_twin"),
11108 );
11109 stamp
11110 .properties
11111 .insert("classifier_assembly".to_string(), serde_json::json!(true));
11112 stamp.properties.insert(
11113 "memory_twin_of".to_string(),
11114 serde_json::json!(field_id.as_base_64()),
11115 );
11116 manager.add_cortical_area(field_area).unwrap();
11117 manager.add_cortical_area(mem_area).unwrap();
11118 manager.add_cortical_area(stamp).unwrap();
11119 manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
11120 classifier_id: "clf-1".to_string(),
11121 name: "demo".to_string(),
11122 parent_region_id: "root".to_string(),
11123 coordinates_3d: [0, 0, 0],
11124 training_mode: feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
11125 kernel_area_id: Some("ckern001".to_string()),
11126 class_area_id: Some("ccls0001".to_string()),
11127 mask_area_id: None,
11128 kernel_size: None,
11129 fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
11130 field_area_id: field_id.as_base_64(),
11131 scan_twin_id: stamp_id.as_base_64(),
11132 }],
11133 kernel_memory_id: mem_id.as_base_64(),
11134 class_memory_id: "mcmem001".to_string(),
11135 properties: HashMap::new(),
11136 });
11137
11138 let mapping_data = vec![serde_json::json!({
11139 "morphology_id": "episodic_scan",
11140 "morphology_scalar": [1, 1, 1],
11141 "postSynapticCurrent_multiplier": 1,
11142 })];
11143 manager
11144 .update_cortical_mapping(&field_id, &mem_id, mapping_data)
11145 .unwrap();
11146 manager
11147 .update_cortical_mapping(&field_id, &mem_id, Vec::new())
11148 .unwrap();
11149
11150 assert!(
11151 manager.get_cortical_area(&stamp_id).is_some(),
11152 "classifier stamp must survive field mapping delete"
11153 );
11154 assert_eq!(
11155 manager
11156 .get_classifier("clf-1")
11157 .and_then(|classifier| {
11158 classifier
11159 .binding_for_field(&field_id.as_base_64())
11160 .map(|field| field.field_area_id.clone())
11161 }),
11162 Some(field_id.as_base_64()),
11163 "empty mapping delete must not clear the classifier field slot when morphology is omitted"
11164 );
11165 }
11166
11167 #[cfg(feature = "plasticity")]
11170 #[test]
11171 fn classifier_scan_config_targets_each_field_twin() {
11172 use feagi_structures::genomic::cortical_area::{
11173 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11174 MemoryCorticalType,
11175 };
11176
11177 let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
11178 let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
11179 let field_a = CorticalID::try_from_bytes(b"cfield01").unwrap();
11180 let field_b = CorticalID::try_from_bytes(b"cfield02").unwrap();
11181 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11182 let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
11183 let twin_a = CorticalID::try_from_bytes(b"ctwin001").unwrap();
11184 let twin_b = CorticalID::try_from_bytes(b"ctwin002").unwrap();
11185
11186 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11187 CorticalArea::new(
11188 id,
11189 0,
11190 name.to_string(),
11191 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11192 (0, 0, 0).into(),
11193 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11194 )
11195 .unwrap()
11196 };
11197 let memory = |id: CorticalID, name: &str| {
11198 let mut area = CorticalArea::new(
11199 id,
11200 0,
11201 name.to_string(),
11202 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11203 (0, 0, 0).into(),
11204 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11205 )
11206 .unwrap();
11207 area.properties
11208 .insert("is_mem_type".to_string(), serde_json::json!(true));
11209 area
11210 };
11211
11212 let mut manager = ConnectomeManager::new_for_testing();
11213 let kernel = custom(kernel_id, "kernel", (2, 2, 1));
11214 let class_area = custom(class_id, "class", (1, 1, 10));
11215 let mut field_a_area = custom(field_a, "field_a", (4, 3, 1));
11216 let mut field_b_area = custom(field_b, "field_b", (2, 2, 1));
11217 field_a_area.properties.insert(
11218 "cortical_mapping_dst".to_string(),
11219 serde_json::json!({
11220 mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
11221 }),
11222 );
11223 field_b_area.properties.insert(
11224 "cortical_mapping_dst".to_string(),
11225 serde_json::json!({
11226 mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
11227 }),
11228 );
11229 let mut kernel_mem = memory(mem_id, "kernel_mem");
11230 kernel_mem.properties.insert(
11231 "classifier_kernel_area_id".to_string(),
11232 serde_json::json!(kernel_id.as_base_64()),
11233 );
11234 kernel_mem.properties.insert(
11235 "classifier_class_area_id".to_string(),
11236 serde_json::json!(class_id.as_base_64()),
11237 );
11238 kernel_mem.properties.insert(
11239 "classifier_class_memory_id".to_string(),
11240 serde_json::json!(class_mem_id.as_base_64()),
11241 );
11242 kernel_mem.properties.insert(
11243 "cortical_mapping_dst".to_string(),
11244 serde_json::json!({
11245 class_mem_id.as_base_64(): [{ "morphology_id": "associative_memory" }]
11246 }),
11247 );
11248 kernel_mem.properties.insert(
11249 "memory_twin_areas".to_string(),
11250 serde_json::json!({
11251 field_a.as_base_64(): twin_a.as_base_64(),
11252 field_b.as_base_64(): twin_b.as_base_64(),
11253 }),
11254 );
11255 let class_mem = memory(class_mem_id, "class_mem");
11256 let twin_a_area = custom(twin_a, "twin_a", (4, 3, 10));
11257 let twin_b_area = custom(twin_b, "twin_b", (2, 2, 10));
11258
11259 manager.add_cortical_area(kernel).unwrap();
11260 manager.add_cortical_area(class_area).unwrap();
11261 manager.add_cortical_area(field_a_area).unwrap();
11262 manager.add_cortical_area(field_b_area).unwrap();
11263 manager.add_cortical_area(kernel_mem).unwrap();
11264 manager.add_cortical_area(class_mem).unwrap();
11265 manager.add_cortical_area(twin_a_area).unwrap();
11266 manager.add_cortical_area(twin_b_area).unwrap();
11267
11268 let scan = manager
11269 .build_memory_scan_config(&mem_id)
11270 .expect("a mapped field twin must produce a scan config");
11271 assert_eq!(scan.sources.len(), 2);
11272 assert_eq!(scan.class_channel_count, 10);
11273 assert_eq!(scan.kernel.width, 2);
11274 let source_a = scan
11275 .sources
11276 .iter()
11277 .find(|source| source.field_area_idx == manager.get_cortical_idx(&field_a).unwrap())
11278 .expect("field A scan source");
11279 assert_eq!(
11280 source_a.twin_area_idx,
11281 manager.get_cortical_idx(&twin_a).unwrap()
11282 );
11283 assert_eq!(source_a.field_width, 4);
11284 assert_eq!(source_a.field_height, 3);
11285 let source_b = scan
11286 .sources
11287 .iter()
11288 .find(|source| source.field_area_idx == manager.get_cortical_idx(&field_b).unwrap())
11289 .expect("field B scan source");
11290 assert_eq!(
11291 source_b.twin_area_idx,
11292 manager.get_cortical_idx(&twin_b).unwrap()
11293 );
11294
11295 manager
11296 .get_cortical_area_mut(&mem_id)
11297 .unwrap()
11298 .properties
11299 .insert("memory_twin_areas".to_string(), serde_json::json!({}));
11300 assert!(
11301 manager.build_memory_scan_config(&mem_id).is_none(),
11302 "an empty twin map must not inject, which is why a dark twin stays dark"
11303 );
11304 }
11305
11306 #[cfg(feature = "plasticity")]
11309 #[test]
11310 fn classifier_genome_round_trip_restores_scan_target() {
11311 use feagi_evolutionary::{
11312 convert_hierarchical_to_flat, load_genome_from_json, GenomeMetadata, GenomeSignatures,
11313 GenomeStats, PhysiologyConfig, RuntimeGenome,
11314 };
11315 use feagi_structures::genomic::classifiers::{Classifier, ClassifierField};
11316 use feagi_structures::genomic::cortical_area::{
11317 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11318 MemoryCorticalType,
11319 };
11320
11321 let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
11322 let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
11323 let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
11324 let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
11325 let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
11326 let twin_id = CorticalID::try_from_bytes(b"ctwin001").unwrap();
11327
11328 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11329 CorticalArea::new(
11330 id,
11331 0,
11332 name.to_string(),
11333 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11334 (0, 0, 0).into(),
11335 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11336 )
11337 .unwrap()
11338 };
11339 let memory = |id: CorticalID, name: &str| {
11340 let mut area = CorticalArea::new(
11341 id,
11342 0,
11343 name.to_string(),
11344 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11345 (0, 0, 0).into(),
11346 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11347 )
11348 .unwrap();
11349 area.properties
11350 .insert("is_mem_type".to_string(), serde_json::json!(true));
11351 area
11352 };
11353
11354 let mut kernel = custom(kernel_id, "kernel", (2, 2, 1));
11355 kernel.properties.insert(
11356 "cortical_mapping_dst".to_string(),
11357 serde_json::json!({
11358 mem_id.as_base_64(): [{
11359 "morphology_id": "episodic_memory",
11360 "postSynapticCurrent_multiplier": 1.0,
11361 "plasticity_flag": false
11362 }]
11363 }),
11364 );
11365 let class_area = custom(class_id, "class", (1, 1, 4));
11366 let mut field = custom(field_id, "field", (3, 2, 1));
11367 field
11368 .properties
11369 .insert("burst_engine_active".to_string(), serde_json::json!(true));
11370 field.properties.insert(
11371 "cortical_mapping_dst".to_string(),
11372 serde_json::json!({
11373 mem_id.as_base_64(): [{
11374 "morphology_id": "episodic_scan",
11375 "postSynapticCurrent_multiplier": 1.0,
11376 "plasticity_flag": false
11377 }]
11378 }),
11379 );
11380 let mut kernel_mem = memory(mem_id, "kernel_mem");
11381 kernel_mem.properties.insert(
11382 "cortical_mapping_dst".to_string(),
11383 serde_json::json!({
11384 class_mem_id.as_base_64(): [{
11385 "morphology_id": "associative_memory",
11386 "postSynapticCurrent_multiplier": 1.0,
11387 "plasticity_flag": false
11388 }]
11389 }),
11390 );
11391 let class_mem = memory(class_mem_id, "class_mem");
11392 let mut twin = custom(twin_id, "twin", (3, 2, 4));
11393 twin.properties
11394 .insert("burst_engine_active".to_string(), serde_json::json!(true));
11395 twin.properties.insert(
11396 "memory_twin_of".to_string(),
11397 serde_json::json!(field_id.as_base_64()),
11398 );
11399
11400 let mut genome = RuntimeGenome {
11401 metadata: GenomeMetadata {
11402 genome_id: "clf-round-trip".to_string(),
11403 genome_title: "clf".to_string(),
11404 genome_description: "".to_string(),
11405 version: "3.0".to_string(),
11406 timestamp: 0.0,
11407 brain_regions_root: None,
11408 },
11409 cortical_areas: HashMap::new(),
11410 brain_regions: HashMap::new(),
11411 classifiers: HashMap::new(),
11412 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
11413 physiology: PhysiologyConfig::default(),
11414 signatures: GenomeSignatures {
11415 genome: "0".to_string(),
11416 blueprint: "0".to_string(),
11417 physiology: "0".to_string(),
11418 morphologies: None,
11419 },
11420 stats: GenomeStats::default(),
11421 };
11422 for area in [kernel, class_area, field, kernel_mem, class_mem, twin] {
11423 genome.cortical_areas.insert(area.cortical_id, area);
11424 }
11425 genome.classifiers.insert(
11426 "clf-1".to_string(),
11427 Classifier {
11428 classifier_id: "clf-1".to_string(),
11429 name: "asdf".to_string(),
11430 parent_region_id: "root".to_string(),
11431 coordinates_3d: [0, 0, 0],
11432 training_mode:
11433 feagi_structures::genomic::classifiers::ClassifierTrainingMode::Kernel,
11434 kernel_area_id: Some(kernel_id.as_base_64()),
11435 class_area_id: Some(class_id.as_base_64()),
11436 mask_area_id: None,
11437 kernel_size: None,
11438 fields: vec![ClassifierField {
11439 field_area_id: field_id.as_base_64(),
11440 scan_twin_id: twin_id.as_base_64(),
11441 }],
11442 kernel_memory_id: mem_id.as_base_64(),
11443 class_memory_id: class_mem_id.as_base_64(),
11444 properties: HashMap::new(),
11445 },
11446 );
11447
11448 let flat = convert_hierarchical_to_flat(&genome).unwrap();
11449 let loaded = load_genome_from_json(&flat.to_string()).unwrap();
11450 let loaded_classifier = loaded.classifiers.get("clf-1").unwrap();
11451 assert_eq!(
11452 loaded_classifier.fields[0].scan_twin_id,
11453 twin_id.as_base_64()
11454 );
11455 assert_eq!(
11456 loaded
11457 .cortical_areas
11458 .get(&twin_id)
11459 .unwrap()
11460 .properties
11461 .get("burst_engine_active")
11462 .and_then(|value| value.as_bool()),
11463 Some(true),
11464 "twin burst must survive save and load"
11465 );
11466 assert!(
11467 !loaded.cortical_areas[&mem_id]
11468 .properties
11469 .contains_key("classifier_kernel_area_id"),
11470 "the blueprint does not store classifier area ids; load must restore them"
11471 );
11472
11473 let mut manager = ConnectomeManager::new_for_testing();
11474 for area in loaded.cortical_areas.values() {
11475 manager.add_cortical_area(area.clone()).unwrap();
11476 }
11477 manager.replace_classifiers(loaded.classifiers);
11478 manager.apply_loaded_classifier_assemblies();
11479
11480 let kernel_mem = manager.get_cortical_area(&mem_id).unwrap();
11481 assert_eq!(
11482 kernel_mem
11483 .properties
11484 .get("classifier_kernel_area_id")
11485 .and_then(|value| value.as_str()),
11486 Some(kernel_id.as_base_64().as_str())
11487 );
11488 assert_eq!(
11489 kernel_mem
11490 .properties
11491 .get("classifier_class_area_id")
11492 .and_then(|value| value.as_str()),
11493 Some(class_id.as_base_64().as_str())
11494 );
11495 assert_eq!(
11496 kernel_mem
11497 .properties
11498 .get("memory_twin_areas")
11499 .and_then(|value| value.as_object())
11500 .and_then(|map| map.get(&field_id.as_base_64()))
11501 .and_then(|value| value.as_str()),
11502 Some(twin_id.as_base_64().as_str())
11503 );
11504 assert_eq!(
11505 kernel_mem
11506 .properties
11507 .get("burst_engine_active")
11508 .and_then(|value| value.as_bool()),
11509 Some(true)
11510 );
11511 let twin_area = manager.get_cortical_area(&twin_id).unwrap();
11512 assert_eq!(
11513 twin_area
11514 .properties
11515 .get("memory_twin_for")
11516 .and_then(|value| value.as_str()),
11517 Some(mem_id.as_base_64().as_str())
11518 );
11519 assert_eq!(
11520 twin_area
11521 .properties
11522 .get("scan_twin")
11523 .and_then(|value| value.as_bool()),
11524 Some(true)
11525 );
11526
11527 assert!(
11528 manager.mapping_from_src_to_dst_has_associative(&mem_id, &class_mem_id),
11529 "kernel memory to class memory associative mapping must survive save and load, got {:?}",
11530 manager
11531 .get_cortical_area(&mem_id)
11532 .unwrap()
11533 .properties
11534 .get("cortical_mapping_dst")
11535 );
11536 assert!(
11537 !manager
11538 .get_episodic_scan_upstream_cortical_areas(&mem_id)
11539 .is_empty(),
11540 "field episodic_scan must survive save and load, got {:?}",
11541 manager
11542 .get_cortical_area(&field_id)
11543 .unwrap()
11544 .properties
11545 .get("cortical_mapping_dst")
11546 );
11547 let scan = manager
11548 .build_memory_scan_config(&mem_id)
11549 .expect("a reloaded classifier must scan into its twin");
11550 assert_eq!(scan.sources.len(), 1);
11551 assert_eq!(
11552 scan.sources[0].twin_area_idx,
11553 manager.get_cortical_idx(&twin_id).unwrap()
11554 );
11555 assert_eq!(
11556 scan.sources[0].field_area_idx,
11557 manager.get_cortical_idx(&field_id).unwrap()
11558 );
11559 }
11560
11561 #[cfg(feature = "plasticity")]
11564 #[test]
11565 fn scanner_genome_round_trip_restores_mask_kernel_and_scan() {
11566 use feagi_evolutionary::{
11567 convert_hierarchical_to_flat, load_genome_from_json, GenomeMetadata, GenomeSignatures,
11568 GenomeStats, PhysiologyConfig, RuntimeGenome,
11569 };
11570 use feagi_structures::genomic::classifiers::{
11571 Classifier, ClassifierField, ClassifierTrainingMode,
11572 };
11573 use feagi_structures::genomic::cortical_area::{
11574 CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
11575 MemoryCorticalType,
11576 };
11577
11578 let field_id = CorticalID::try_from_bytes(b"cfieldsc").unwrap();
11579 let mask_id = CorticalID::try_from_bytes(b"cmaskscn").unwrap();
11580 let mem_id = CorticalID::try_from_bytes(b"mkmemscn").unwrap();
11581 let class_mem_id = CorticalID::try_from_bytes(b"mcmemscn").unwrap();
11582 let twin_id = CorticalID::try_from_bytes(b"ctwinscn").unwrap();
11583
11584 let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
11585 CorticalArea::new(
11586 id,
11587 0,
11588 name.to_string(),
11589 CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
11590 (0, 0, 0).into(),
11591 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
11592 )
11593 .unwrap()
11594 };
11595 let memory = |id: CorticalID, name: &str| {
11596 let mut area = CorticalArea::new(
11597 id,
11598 0,
11599 name.to_string(),
11600 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
11601 (0, 0, 0).into(),
11602 CorticalAreaType::Memory(MemoryCorticalType::Memory),
11603 )
11604 .unwrap();
11605 area.properties
11606 .insert("is_mem_type".to_string(), serde_json::json!(true));
11607 area
11608 };
11609
11610 let mut field = custom(field_id, "field", (4, 3, 3));
11611 field.properties.insert(
11612 "cortical_mapping_dst".to_string(),
11613 serde_json::json!({
11614 mem_id.as_base_64(): [{
11615 "morphology_id": "episodic_scan",
11616 "postSynapticCurrent_multiplier": 1.0,
11617 "plasticity_flag": false
11618 }]
11619 }),
11620 );
11621 let mask = custom(mask_id, "mask", (4, 3, 10));
11622 let mut kernel_mem = memory(mem_id, "kernel_mem");
11623 kernel_mem.properties.insert(
11624 "cortical_mapping_dst".to_string(),
11625 serde_json::json!({
11626 class_mem_id.as_base_64(): [{
11627 "morphology_id": "associative_memory",
11628 "postSynapticCurrent_multiplier": 1.0,
11629 "plasticity_flag": false
11630 }]
11631 }),
11632 );
11633 let class_mem = memory(class_mem_id, "class_mem");
11634 let mut twin = custom(twin_id, "twin", (4, 3, 10));
11635 twin.properties.insert(
11636 "memory_twin_of".to_string(),
11637 serde_json::json!(field_id.as_base_64()),
11638 );
11639
11640 let mut genome = RuntimeGenome {
11641 metadata: GenomeMetadata {
11642 genome_id: "clf-scanner".to_string(),
11643 genome_title: "clf".to_string(),
11644 genome_description: "".to_string(),
11645 version: "3.0".to_string(),
11646 timestamp: 0.0,
11647 brain_regions_root: None,
11648 },
11649 cortical_areas: HashMap::new(),
11650 brain_regions: HashMap::new(),
11651 classifiers: HashMap::new(),
11652 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
11653 physiology: PhysiologyConfig::default(),
11654 signatures: GenomeSignatures {
11655 genome: "0".to_string(),
11656 blueprint: "0".to_string(),
11657 physiology: "0".to_string(),
11658 morphologies: None,
11659 },
11660 stats: GenomeStats::default(),
11661 };
11662 for area in [field, mask, kernel_mem, class_mem, twin] {
11663 genome.cortical_areas.insert(area.cortical_id, area);
11664 }
11665 genome.classifiers.insert(
11666 "clf-scan".to_string(),
11667 Classifier {
11668 classifier_id: "clf-scan".to_string(),
11669 name: "scanner".to_string(),
11670 parent_region_id: "root".to_string(),
11671 coordinates_3d: [1, 2, 3],
11672 training_mode: ClassifierTrainingMode::Scanner,
11673 kernel_area_id: None,
11674 class_area_id: None,
11675 mask_area_id: Some(mask_id.as_base_64()),
11676 kernel_size: Some([2, 2, 3]),
11677 fields: vec![ClassifierField {
11678 field_area_id: field_id.as_base_64(),
11679 scan_twin_id: twin_id.as_base_64(),
11680 }],
11681 kernel_memory_id: mem_id.as_base_64(),
11682 class_memory_id: class_mem_id.as_base_64(),
11683 properties: HashMap::new(),
11684 },
11685 );
11686
11687 let flat = convert_hierarchical_to_flat(&genome).unwrap();
11688 assert_eq!(flat["classifiers"]["clf-scan"]["training_mode"], "scanner");
11689 assert_eq!(
11690 flat["classifiers"]["clf-scan"]["kernel_size"],
11691 serde_json::json!([2, 2, 3])
11692 );
11693 assert!(flat["classifiers"]["clf-scan"]
11694 .get("kernel_area_id")
11695 .is_none());
11696 let loaded = load_genome_from_json(&flat.to_string()).unwrap();
11697 let loaded_classifier = loaded.classifiers.get("clf-scan").unwrap();
11698 assert_eq!(
11699 loaded_classifier.training_mode,
11700 ClassifierTrainingMode::Scanner
11701 );
11702 assert_eq!(
11703 loaded_classifier.mask_area_id.as_deref(),
11704 Some(mask_id.as_base_64().as_str())
11705 );
11706 assert_eq!(loaded_classifier.kernel_size, Some([2, 2, 3]));
11707 assert!(loaded_classifier.kernel_area_id.is_none());
11708 assert!(loaded_classifier.class_area_id.is_none());
11709
11710 let mut manager = ConnectomeManager::new_for_testing();
11711 for area in loaded.cortical_areas.values() {
11712 manager.add_cortical_area(area.clone()).unwrap();
11713 }
11714 manager.replace_classifiers(loaded.classifiers);
11715 manager.apply_loaded_classifier_assemblies();
11716
11717 let kernel_mem = manager.get_cortical_area(&mem_id).unwrap();
11718 assert_eq!(
11719 kernel_mem
11720 .properties
11721 .get("classifier_training_mode")
11722 .and_then(|value| value.as_str()),
11723 Some("scanner")
11724 );
11725 assert_eq!(
11726 kernel_mem
11727 .properties
11728 .get("classifier_mask_area_id")
11729 .and_then(|value| value.as_str()),
11730 Some(mask_id.as_base_64().as_str())
11731 );
11732 assert_eq!(
11733 kernel_mem
11734 .properties
11735 .get("classifier_kernel_size")
11736 .and_then(|value| value.as_array())
11737 .map(|values| values.len()),
11738 Some(3)
11739 );
11740 assert!(!kernel_mem
11741 .properties
11742 .contains_key("classifier_kernel_area_id"));
11743 assert!(!kernel_mem
11744 .properties
11745 .contains_key("classifier_class_area_id"));
11746 assert_eq!(
11747 manager
11748 .get_cortical_area(&mask_id)
11749 .unwrap()
11750 .properties
11751 .get("burst_engine_active")
11752 .and_then(|value| value.as_bool()),
11753 Some(true),
11754 "a reloaded scanner mask must stay a burst source"
11755 );
11756
11757 let scan = manager
11758 .build_memory_scan_config(&mem_id)
11759 .expect("a reloaded scanner classifier must scan from its kernel size");
11760 assert_eq!(scan.kernel.width, 2);
11761 assert_eq!(scan.kernel.height, 2);
11762 assert_eq!(scan.kernel.depth, 3);
11763 assert_eq!(scan.class_channel_count, 10);
11764 let mask_source = scan.scanner_mask.expect("scanner mask source");
11765 assert_eq!(
11766 mask_source.mask_area_idx,
11767 manager.get_cortical_idx(&mask_id).unwrap()
11768 );
11769 assert_eq!(mask_source.mask_depth, 10);
11770 assert_eq!(scan.sources.len(), 1);
11771 assert_eq!(
11772 scan.sources[0].field_area_idx,
11773 manager.get_cortical_idx(&field_id).unwrap()
11774 );
11775 }
11776}