use once_cell::sync::Lazy;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::hash::Hasher;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use tracing::{debug, error, info, trace, warn};
use xxhash_rust::xxh64::Xxh64;
pub type BrainRegionIoRegistry = HashMap<String, (Vec<String>, Vec<String>)>;
use crate::models::{BrainRegion, BrainRegionHierarchy, CorticalArea, CorticalAreaDimensions};
use crate::types::{BduError, BduResult};
use feagi_npu_neural::synapse::SYNAPSE_EDGE_ASSOCIATIVE_MEMORY;
use feagi_npu_neural::types::NeuronId;
use feagi_structures::genomic::cortical_area::{
CoreCorticalType, CorticalAreaType, CorticalID, CustomCorticalType,
};
use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
use feagi_state_manager::StateManager;
const DATA_HASH_SEED: u64 = 0;
const HASH_SAFE_MASK: u64 = (1u64 << 53) - 1;
static INSTANCE: Lazy<Arc<RwLock<ConnectomeManager>>> =
Lazy::new(|| Arc::new(RwLock::new(ConnectomeManager::new())));
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectomeConfig {
pub max_neurons: usize,
pub max_synapses: usize,
pub backend: String,
}
impl Default for ConnectomeConfig {
fn default() -> Self {
Self {
max_neurons: 10_000_000,
max_synapses: 100_000_000,
backend: "cpu".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryTwinDiagnostic {
pub src_cortical_area: String,
pub dst_cortical_area: String,
pub src_cortical_type: String,
pub dst_cortical_type: String,
pub mapping_exists: bool,
pub episodic_rule_count: usize,
pub twin_expected: bool,
pub twin_present: bool,
pub twin_cortical_area: Option<String>,
pub reason: Option<String>,
pub src_parent_region_id: Option<String>,
pub dst_parent_region_id: Option<String>,
pub twin_parent_region_id: Option<String>,
pub twin_parent_matches_src_parent: Option<bool>,
}
pub struct ConnectomeManager {
cortical_areas: HashMap<CorticalID, CorticalArea>,
cortical_id_to_idx: HashMap<CorticalID, u32>,
cortical_idx_to_id: HashMap<u32, CorticalID>,
next_cortical_idx: u32,
brain_regions: BrainRegionHierarchy,
classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
morphology_registry: feagi_evolutionary::MorphologyRegistry,
config: ConnectomeConfig,
npu: Option<Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>,
#[cfg(feature = "plasticity")]
plasticity_executor:
Option<Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>>,
cached_neuron_count: Arc<AtomicUsize>,
cached_synapse_count: Arc<AtomicUsize>,
cached_neuron_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
cached_synapse_counts_per_area: Arc<RwLock<HashMap<CorticalID, AtomicUsize>>>,
initialized: bool,
last_fatigue_calculation: Arc<Mutex<std::time::Instant>>,
}
type NeuronData = (
u32,
u32,
u32,
f32,
f32,
f32,
f32,
i32,
u16,
f32,
u16,
u16,
bool,
);
impl ConnectomeManager {
fn get_mapping_rules_for_destination<'a>(
mapping_dst: &'a serde_json::Map<String, serde_json::Value>,
dst_area_id: &CorticalID,
) -> Option<&'a Vec<serde_json::Value>> {
if let Some(rules) = mapping_dst
.get(&dst_area_id.as_base_64())
.and_then(|value| value.as_array())
{
return Some(rules);
}
for (raw_dst_key, rules_value) in mapping_dst {
let parsed_dst = CorticalID::try_from_base_64(raw_dst_key)
.or_else(|_| CorticalID::try_from_legacy_ascii(raw_dst_key));
if parsed_dst.as_ref().ok() != Some(dst_area_id) {
continue;
}
if let Some(rules) = rules_value.as_array() {
return Some(rules);
}
}
None
}
fn new() -> Self {
Self {
cortical_areas: HashMap::new(),
cortical_id_to_idx: HashMap::new(),
cortical_idx_to_id: HashMap::new(),
next_cortical_idx: 7, brain_regions: BrainRegionHierarchy::new(),
classifiers: HashMap::new(),
morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
config: ConnectomeConfig::default(),
npu: None,
#[cfg(feature = "plasticity")]
plasticity_executor: None,
cached_neuron_count: Arc::new(AtomicUsize::new(0)),
cached_synapse_count: Arc::new(AtomicUsize::new(0)),
cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
initialized: false,
last_fatigue_calculation: Arc::new(Mutex::new(
std::time::Instant::now() - std::time::Duration::from_secs(10),
)), }
}
pub fn instance() -> Arc<RwLock<ConnectomeManager>> {
Arc::clone(&*INSTANCE)
}
pub fn new_for_testing() -> Self {
Self {
cortical_areas: HashMap::new(),
cortical_id_to_idx: HashMap::new(),
cortical_idx_to_id: HashMap::new(),
next_cortical_idx: 0,
brain_regions: BrainRegionHierarchy::new(),
classifiers: HashMap::new(),
morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
config: ConnectomeConfig::default(),
npu: None,
#[cfg(feature = "plasticity")]
plasticity_executor: None,
cached_neuron_count: Arc::new(AtomicUsize::new(0)),
cached_synapse_count: Arc::new(AtomicUsize::new(0)),
cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
initialized: false,
last_fatigue_calculation: Arc::new(Mutex::new(
std::time::Instant::now() - std::time::Duration::from_secs(10),
)),
}
}
pub fn new_for_testing_with_npu(
npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
) -> Self {
Self {
cortical_areas: HashMap::new(),
cortical_id_to_idx: HashMap::new(),
cortical_idx_to_id: HashMap::new(),
next_cortical_idx: 7,
brain_regions: BrainRegionHierarchy::new(),
classifiers: HashMap::new(),
morphology_registry: feagi_evolutionary::MorphologyRegistry::new(),
config: ConnectomeConfig::default(),
npu: Some(npu),
#[cfg(feature = "plasticity")]
plasticity_executor: None,
cached_neuron_count: Arc::new(AtomicUsize::new(0)),
cached_synapse_count: Arc::new(AtomicUsize::new(0)),
cached_neuron_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
cached_synapse_counts_per_area: Arc::new(RwLock::new(HashMap::new())),
initialized: false,
last_fatigue_calculation: Arc::new(Mutex::new(
std::time::Instant::now() - std::time::Duration::from_secs(10),
)),
}
}
pub fn setup_core_morphologies_for_testing(&mut self) {
feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
}
#[cfg(test)]
pub fn reset_for_testing() {
let mut instance = INSTANCE.write();
*instance = Self::new();
}
fn update_state_hashes(
&self,
brain_regions: Option<u64>,
cortical_areas: Option<u64>,
brain_geometry: Option<u64>,
morphologies: Option<u64>,
cortical_mappings: Option<u64>,
) {
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
if let Some(value) = brain_regions {
state_manager.set_brain_regions_hash(value);
}
if let Some(value) = cortical_areas {
state_manager.set_cortical_areas_hash(value);
}
if let Some(value) = brain_geometry {
state_manager.set_brain_geometry_hash(value);
}
if let Some(value) = morphologies {
state_manager.set_morphologies_hash(value);
}
if let Some(value) = cortical_mappings {
state_manager.set_cortical_mappings_hash(value);
}
}
fn refresh_brain_regions_hash(&self) {
let hash = self.compute_brain_regions_hash();
self.update_state_hashes(Some(hash), None, None, None, None);
}
#[allow(dead_code)]
fn refresh_cortical_areas_hash(&self) {
let hash = self.compute_cortical_areas_hash();
self.update_state_hashes(None, Some(hash), None, None, None);
}
#[allow(dead_code)]
fn refresh_brain_geometry_hash(&self) {
let hash = self.compute_brain_geometry_hash();
self.update_state_hashes(None, None, Some(hash), None, None);
}
fn refresh_morphologies_hash(&self) {
let hash = self.compute_morphologies_hash();
self.update_state_hashes(None, None, None, Some(hash), None);
}
pub fn refresh_cortical_mappings_hash(&self) {
let hash = self.compute_cortical_mappings_hash();
self.update_state_hashes(None, None, None, None, Some(hash));
}
pub fn refresh_classifiers_hash(&self) {
let hash = self.compute_classifiers_hash();
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
state_manager.set_classifiers_hash(hash);
}
pub fn refresh_all_connectome_hashes(&self) {
self.update_state_hashes(
Some(self.compute_brain_regions_hash()),
Some(self.compute_cortical_areas_hash()),
Some(self.compute_brain_geometry_hash()),
Some(self.compute_morphologies_hash()),
Some(self.compute_cortical_mappings_hash()),
);
self.refresh_classifiers_hash();
}
pub fn refresh_cortical_area_hashes(&self, properties_changed: bool, geometry_changed: bool) {
let cortical_hash = if properties_changed {
Some(self.compute_cortical_areas_hash())
} else {
None
};
let geometry_hash = if geometry_changed {
Some(self.compute_brain_geometry_hash())
} else {
None
};
self.update_state_hashes(None, cortical_hash, geometry_hash, None, None);
}
fn compute_brain_regions_hash(&self) -> u64 {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
let mut region_ids: Vec<String> = self
.brain_regions
.get_all_region_ids()
.into_iter()
.cloned()
.collect();
region_ids.sort();
for region_id in region_ids {
let Some(region) = self.brain_regions.get_region(®ion_id) else {
continue;
};
Self::hash_str(&mut hasher, ®ion_id);
Self::hash_str(&mut hasher, ®ion.name);
Self::hash_str(&mut hasher, ®ion.region_type.to_string());
let parent_id = self.brain_regions.get_parent(®ion_id);
match parent_id {
Some(parent) => Self::hash_str(&mut hasher, parent),
None => Self::hash_str(&mut hasher, "null"),
}
let mut cortical_ids: Vec<String> = region
.cortical_areas
.iter()
.map(|id| id.as_base_64())
.collect();
cortical_ids.sort();
for cortical_id in cortical_ids {
Self::hash_str(&mut hasher, &cortical_id);
}
Self::hash_properties_filtered(&mut hasher, ®ion.properties, &[]);
}
hasher.finish() & HASH_SAFE_MASK
}
fn compute_cortical_areas_hash(&self) -> u64 {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
areas.sort_by_key(|area| area.cortical_id.as_base_64());
for area in areas {
let cortical_id = area.cortical_id.as_base_64();
Self::hash_str(&mut hasher, &cortical_id);
hasher.write_u32(area.cortical_idx);
Self::hash_str(&mut hasher, &area.name);
Self::hash_str(&mut hasher, &area.cortical_type.to_string());
let excluded = ["cortical_mapping_dst", "upstream_cortical_areas"];
Self::hash_properties_filtered(&mut hasher, &area.properties, &excluded);
}
hasher.finish() & HASH_SAFE_MASK
}
fn compute_brain_geometry_hash(&self) -> u64 {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
areas.sort_by_key(|area| area.cortical_id.as_base_64());
for area in areas {
let cortical_id = area.cortical_id.as_base_64();
Self::hash_str(&mut hasher, &cortical_id);
Self::hash_i32(&mut hasher, area.position.x);
Self::hash_i32(&mut hasher, area.position.y);
Self::hash_i32(&mut hasher, area.position.z);
Self::hash_u32(&mut hasher, area.dimensions.width);
Self::hash_u32(&mut hasher, area.dimensions.height);
Self::hash_u32(&mut hasher, area.dimensions.depth);
let coord_2d = area
.properties
.get("coordinate_2d")
.or_else(|| area.properties.get("coordinates_2d"));
match coord_2d {
Some(value) => Self::hash_json_value(&mut hasher, value),
None => Self::hash_str(&mut hasher, "null"),
}
}
hasher.finish() & HASH_SAFE_MASK
}
fn compute_morphologies_hash(&self) -> u64 {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
let mut morphology_ids = self.morphology_registry.morphology_ids();
morphology_ids.sort();
for morphology_id in morphology_ids {
if let Some(morphology) = self.morphology_registry.get(&morphology_id) {
Self::hash_str(&mut hasher, &morphology_id);
Self::hash_str(&mut hasher, &format!("{:?}", morphology.morphology_type));
Self::hash_str(&mut hasher, &morphology.class);
if let Ok(value) = serde_json::to_value(&morphology.parameters) {
Self::hash_json_value(&mut hasher, &value);
}
}
}
hasher.finish() & HASH_SAFE_MASK
}
fn compute_cortical_mappings_hash(&self) -> u64 {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
let mut areas: Vec<&CorticalArea> = self.cortical_areas.values().collect();
areas.sort_by_key(|area| area.cortical_id.as_base_64());
for area in areas {
let cortical_id = area.cortical_id.as_base_64();
Self::hash_str(&mut hasher, &cortical_id);
if let Some(serde_json::Value::Object(map)) =
area.properties.get("cortical_mapping_dst")
{
let mut dest_ids: Vec<&String> = map.keys().collect();
dest_ids.sort();
for dest_id in dest_ids {
Self::hash_str(&mut hasher, dest_id);
if let Some(value) = map.get(dest_id) {
Self::hash_json_value(&mut hasher, value);
}
}
} else {
Self::hash_str(&mut hasher, "null");
}
}
hasher.finish() & HASH_SAFE_MASK
}
fn compute_classifiers_hash(&self) -> u64 {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
let mut classifier_ids: Vec<String> = self.classifiers.keys().cloned().collect();
classifier_ids.sort();
for classifier_id in classifier_ids {
let Some(classifier) = self.classifiers.get(&classifier_id) else {
continue;
};
Self::hash_str(&mut hasher, &classifier.classifier_id);
Self::hash_str(&mut hasher, &classifier.name);
Self::hash_str(&mut hasher, &classifier.parent_region_id);
Self::hash_i32(&mut hasher, classifier.coordinates_3d[0]);
Self::hash_i32(&mut hasher, classifier.coordinates_3d[1]);
Self::hash_i32(&mut hasher, classifier.coordinates_3d[2]);
match &classifier.kernel_area_id {
Some(area_id) => Self::hash_str(&mut hasher, area_id),
None => Self::hash_str(&mut hasher, "null"),
}
match &classifier.class_area_id {
Some(area_id) => Self::hash_str(&mut hasher, area_id),
None => Self::hash_str(&mut hasher, "null"),
}
for field in &classifier.fields {
Self::hash_str(&mut hasher, &field.field_area_id);
Self::hash_str(&mut hasher, &field.scan_twin_id);
}
Self::hash_str(&mut hasher, &classifier.kernel_memory_id);
Self::hash_str(&mut hasher, &classifier.class_memory_id);
Self::hash_properties_filtered(&mut hasher, &classifier.properties, &[]);
}
hasher.finish() & HASH_SAFE_MASK
}
fn hash_str(hasher: &mut Xxh64, value: &str) {
hasher.write(value.as_bytes());
hasher.write_u8(0);
}
fn hash_i32(hasher: &mut Xxh64, value: i32) {
hasher.write(&value.to_le_bytes());
}
fn hash_u32(hasher: &mut Xxh64, value: u32) {
hasher.write(&value.to_le_bytes());
}
fn hash_json_value(hasher: &mut Xxh64, value: &serde_json::Value) {
match value {
serde_json::Value::Null => {
hasher.write_u8(0);
}
serde_json::Value::Bool(val) => {
hasher.write_u8(1);
hasher.write_u8(*val as u8);
}
serde_json::Value::Number(num) => {
hasher.write_u8(2);
Self::hash_str(hasher, &num.to_string());
}
serde_json::Value::String(val) => {
hasher.write_u8(3);
Self::hash_str(hasher, val);
}
serde_json::Value::Array(items) => {
hasher.write_u8(4);
for item in items {
Self::hash_json_value(hasher, item);
}
}
serde_json::Value::Object(map) => {
hasher.write_u8(5);
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
for key in keys {
Self::hash_str(hasher, key);
if let Some(val) = map.get(key) {
Self::hash_json_value(hasher, val);
}
}
}
}
}
fn hash_properties_filtered(
hasher: &mut Xxh64,
properties: &HashMap<String, serde_json::Value>,
excluded_keys: &[&str],
) {
let mut keys: Vec<&String> = properties.keys().collect();
keys.sort();
for key in keys {
if excluded_keys.contains(&key.as_str()) {
continue;
}
Self::hash_str(hasher, key);
if let Some(value) = properties.get(key) {
Self::hash_json_value(hasher, value);
}
}
}
pub fn add_cortical_area(&mut self, mut area: CorticalArea) -> BduResult<u32> {
if self.cortical_areas.contains_key(&area.cortical_id) {
return Err(BduError::InvalidArea(format!(
"Cortical area {} already exists",
area.cortical_id
)));
}
use feagi_structures::genomic::cortical_area::CoreCorticalType;
let death_id = CoreCorticalType::Death.to_cortical_id();
let power_id = CoreCorticalType::Power.to_cortical_id();
let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
let pain_id = CoreCorticalType::Pain.to_cortical_id();
let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
let fear_id = CoreCorticalType::Fear.to_cortical_id();
let hope_id = CoreCorticalType::Hope.to_cortical_id();
let is_death_area = area.cortical_id == death_id;
let is_power_area = area.cortical_id == power_id;
let is_fatigue_area = area.cortical_id == fatigue_id;
let is_pain_area = area.cortical_id == pain_id;
let is_pleasure_area = area.cortical_id == pleasure_id;
let is_fear_area = area.cortical_id == fear_id;
let is_hope_area = area.cortical_id == hope_id;
if is_death_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=0 to _death area (id={})",
area.cortical_id
);
area.cortical_idx = 0;
} else if is_power_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=1 to _power area (id={})",
area.cortical_id
);
area.cortical_idx = 1;
} else if is_fatigue_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=2 to _fatigue area (id={})",
area.cortical_id
);
area.cortical_idx = 2;
} else if is_pain_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=3 to _pain area (id={})",
area.cortical_id
);
area.cortical_idx = 3;
} else if is_pleasure_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=4 to _pleasure area (id={})",
area.cortical_id
);
area.cortical_idx = 4;
} else if is_fear_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=5 to _fear area (id={})",
area.cortical_id
);
area.cortical_idx = 5;
} else if is_hope_area {
trace!(
target: "feagi-bdu",
"[CORE-AREA] Assigning RESERVED cortical_idx=6 to _hope area (id={})",
area.cortical_id
);
area.cortical_idx = 6;
} else {
if area.cortical_idx == 0 {
area.cortical_idx = self.next_cortical_idx;
self.next_cortical_idx += 1;
trace!(
target: "feagi-bdu",
"[REGULAR-AREA] Assigned cortical_idx={} to area '{}' (should be >=7)",
area.cortical_idx,
area.cortical_id.as_base_64()
);
} else {
if area.cortical_idx <= 6 {
warn!(
"Regular area '{}' attempted to use RESERVED cortical_idx={}! Reassigning to next available.",
area.cortical_id, area.cortical_idx);
area.cortical_idx = self.next_cortical_idx;
self.next_cortical_idx += 1;
info!(
" Reassigned '{}' to cortical_idx={}",
area.cortical_id, area.cortical_idx
);
} else if self.cortical_idx_to_id.contains_key(&area.cortical_idx) {
return Err(BduError::InvalidArea(format!(
"Cortical index {} is already in use",
area.cortical_idx
)));
}
if area.cortical_idx >= self.next_cortical_idx {
self.next_cortical_idx = area.cortical_idx + 1;
}
}
}
let cortical_id = area.cortical_id;
let cortical_idx = area.cortical_idx;
self.cortical_id_to_idx.insert(cortical_id, cortical_idx);
self.cortical_idx_to_id.insert(cortical_idx, cortical_id);
area.properties
.insert("upstream_cortical_areas".to_string(), serde_json::json!([]));
let parent_region_id = area
.properties
.get("parent_region_id")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
self.cortical_areas.insert(cortical_id, area);
if let Some(region_id) = parent_region_id {
let region = self
.brain_regions
.get_region_mut(®ion_id)
.ok_or_else(|| {
BduError::InvalidArea(format!(
"Unknown parent_region_id '{}' for cortical area {}",
region_id,
cortical_id.as_base_64()
))
})?;
region.add_area(cortical_id);
}
{
let mut neuron_cache = self.cached_neuron_counts_per_area.write();
neuron_cache.insert(cortical_id, AtomicUsize::new(0));
let mut synapse_cache = self.cached_synapse_counts_per_area.write();
synapse_cache.insert(cortical_id, AtomicUsize::new(0));
}
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
state_manager.init_cortical_area_stats(&cortical_id.as_base_64());
if let Some(ref npu) = self.npu {
trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: attempting NPU lock for registration");
if let Ok(mut npu_lock) = npu.lock() {
trace!(target: "feagi-bdu", "[LOCK-TRACE] add_cortical_area: acquired NPU lock for registration");
npu_lock.register_cortical_area(cortical_idx, cortical_id.as_base_64());
trace!(
target: "feagi-bdu",
"Registered cortical area idx={} -> '{}' in NPU",
cortical_idx,
cortical_id.as_base_64()
);
}
}
self.sync_cortical_area_flags_to_npu()?;
self.initialized = true;
self.refresh_cortical_area_hashes(true, true);
self.refresh_brain_regions_hash();
Ok(cortical_idx)
}
pub fn remove_cortical_area(&mut self, cortical_id: &CorticalID) -> BduResult<()> {
let area = self.cortical_areas.remove(cortical_id).ok_or_else(|| {
BduError::InvalidArea(format!("Cortical area {} does not exist", cortical_id))
})?;
#[cfg(feature = "plasticity")]
if let Some(executor) = self.plasticity_executor.as_ref() {
let exec = match executor.lock() {
Ok(exec) => exec,
Err(_) => {
self.cortical_areas.insert(*cortical_id, area);
return Err(BduError::Internal(format!(
"Failed to lock PlasticityExecutor while removing cortical area {}",
cortical_id
)));
}
};
exec.unregister_memory_area(area.cortical_idx);
}
self.cortical_id_to_idx.remove(cortical_id);
self.cortical_idx_to_id.remove(&area.cortical_idx);
self.refresh_cortical_area_hashes(true, true);
Ok(())
}
pub fn rename_cortical_area_id(
&mut self,
old_id: &CorticalID,
new_id: CorticalID,
new_cortical_type: CorticalAreaType,
) -> BduResult<()> {
self.rename_cortical_area_id_with_options(old_id, new_id, new_cortical_type, true)
}
pub fn rename_cortical_area_id_with_options(
&mut self,
old_id: &CorticalID,
new_id: CorticalID,
new_cortical_type: CorticalAreaType,
update_npu_registry: bool,
) -> BduResult<()> {
if !self.cortical_areas.contains_key(old_id) {
return Err(BduError::InvalidArea(format!(
"Cortical area {} does not exist",
old_id
)));
}
if self.cortical_areas.contains_key(&new_id) {
return Err(BduError::InvalidArea(format!(
"Cortical area {} already exists",
new_id
)));
}
let mut area = self.cortical_areas.remove(old_id).ok_or_else(|| {
BduError::InvalidArea(format!("Cortical area {} does not exist", old_id))
})?;
let cortical_idx = area.cortical_idx;
area.cortical_id = new_id;
area.cortical_type = new_cortical_type;
self.cortical_areas.insert(new_id, area);
self.cortical_id_to_idx.remove(old_id);
self.cortical_id_to_idx.insert(new_id, cortical_idx);
self.cortical_idx_to_id.insert(cortical_idx, new_id);
{
let mut neuron_cache = self.cached_neuron_counts_per_area.write();
if let Some(value) = neuron_cache.remove(old_id) {
neuron_cache.insert(new_id, value);
}
let mut synapse_cache = self.cached_synapse_counts_per_area.write();
if let Some(value) = synapse_cache.remove(old_id) {
synapse_cache.insert(new_id, value);
}
}
self.brain_regions.rename_cortical_area_id(old_id, new_id);
let old_id_str = old_id.as_base_64();
let new_id_str = new_id.as_base_64();
for area in self.cortical_areas.values_mut() {
if let Some(mapping) = area
.properties
.get_mut("cortical_mapping_dst")
.and_then(|v| v.as_object_mut())
{
if let Some(value) = mapping.remove(&old_id_str) {
mapping.insert(new_id_str.clone(), value);
}
}
}
if update_npu_registry {
if let Some(ref npu) = self.npu {
if let Ok(mut npu_lock) = npu.lock() {
npu_lock.register_cortical_area(cortical_idx, new_id.as_base_64());
}
}
}
self.refresh_cortical_area_hashes(true, true);
self.refresh_brain_regions_hash();
self.refresh_cortical_mappings_hash();
Ok(())
}
pub fn get_cortical_area(&self, cortical_id: &CorticalID) -> Option<&CorticalArea> {
self.cortical_areas.get(cortical_id)
}
pub fn get_cortical_area_mut(&mut self, cortical_id: &CorticalID) -> Option<&mut CorticalArea> {
self.cortical_areas.get_mut(cortical_id)
}
pub fn get_cortical_idx(&self, cortical_id: &CorticalID) -> Option<u32> {
self.cortical_id_to_idx.get(cortical_id).copied()
}
pub fn get_parent_region_id_for_area(&self, cortical_id: &CorticalID) -> Option<String> {
self.brain_regions.find_region_containing_area(cortical_id)
}
pub fn has_cross_region_outgoing(&self, area: &CorticalID) -> bool {
let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
return false;
};
let Some(src_area) = self.cortical_areas.get(area) else {
return false;
};
let Some(dst_obj) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
return false;
};
for dst_key in dst_obj.keys() {
let Ok(dst_id) = CorticalID::try_from_base_64(dst_key) else {
continue;
};
match self.brain_regions.find_region_containing_area(&dst_id) {
None => return true,
Some(rid) if rid != my_region => return true,
_ => {}
}
}
false
}
pub fn has_cross_region_incoming(&self, area: &CorticalID) -> bool {
let Some(my_region) = self.brain_regions.find_region_containing_area(area) else {
return false;
};
let my_b64 = area.as_base_64();
for (src_id, src_area) in &self.cortical_areas {
if src_id == area {
continue;
}
let Some(dst_map) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
continue;
};
if !dst_map.contains_key(&my_b64) {
continue;
}
match self.brain_regions.find_region_containing_area(src_id) {
None => return true,
Some(rid) if rid != my_region => return true,
_ => {}
}
}
false
}
pub fn recompute_brain_region_io_registry(&mut self) -> BduResult<BrainRegionIoRegistry> {
use std::collections::HashSet;
let region_ids: Vec<String> = self
.brain_regions
.get_all_region_ids()
.into_iter()
.cloned()
.collect();
let mut inputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
let mut outputs_by_region: HashMap<String, HashSet<String>> = HashMap::new();
for rid in ®ion_ids {
inputs_by_region.insert(rid.clone(), HashSet::new());
outputs_by_region.insert(rid.clone(), HashSet::new());
}
for (src_id, src_area) in &self.cortical_areas {
let Some(dstmap) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
continue;
};
let Some(src_region_id) = self.brain_regions.find_region_containing_area(src_id) else {
debug!(
target: "feagi-bdu",
"Skipping region IO for source area {} (not in any region)",
src_id.as_base_64()
);
continue;
};
for dst_id_str in dstmap.keys() {
let dst_id = CorticalID::try_from_base_64(dst_id_str).map_err(|e| {
BduError::InvalidArea(format!(
"Unable to recompute region IO: invalid destination cortical id '{}' in cortical_mapping_dst for {}: {}",
dst_id_str,
src_id.as_base_64(),
e
))
})?;
let Some(dst_region_id) = self.brain_regions.find_region_containing_area(&dst_id)
else {
warn!(
target: "feagi-bdu",
"Skipping region IO for destination area {} (not in any region)",
dst_id.as_base_64()
);
continue;
};
if src_region_id == dst_region_id {
continue;
}
if self.mapping_is_classifier_assembly_edge(src_id, &dst_id) {
continue;
}
outputs_by_region
.entry(src_region_id.clone())
.or_default()
.insert(src_id.as_base_64());
inputs_by_region
.entry(dst_region_id.clone())
.or_default()
.insert(dst_id.as_base_64());
}
}
for rid in ®ion_ids {
let Some(region) = self.brain_regions.get_region(rid) else {
continue;
};
let in_ids = crate::region_io_designation::parse_designated_id_list(
region
.properties
.get(crate::region_io_designation::DESIGNATED_INPUTS_KEY),
)?;
let out_ids = crate::region_io_designation::parse_designated_id_list(
region
.properties
.get(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY),
)?;
for id in in_ids {
inputs_by_region
.entry(rid.clone())
.or_default()
.insert(id.as_base_64());
}
for id in out_ids {
outputs_by_region
.entry(rid.clone())
.or_default()
.insert(id.as_base_64());
}
}
let mut computed: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
for rid in region_ids {
let mut inputs: Vec<String> = inputs_by_region
.remove(&rid)
.unwrap_or_default()
.into_iter()
.collect();
let mut outputs: Vec<String> = outputs_by_region
.remove(&rid)
.unwrap_or_default()
.into_iter()
.collect();
inputs.sort();
outputs.sort();
let region = self.brain_regions.get_region_mut(&rid).ok_or_else(|| {
BduError::InvalidArea(format!(
"Unable to recompute region IO: region '{}' not found in hierarchy",
rid
))
})?;
if inputs.is_empty() {
region.properties.remove("inputs");
} else {
region
.properties
.insert("inputs".to_string(), serde_json::json!(inputs.clone()));
}
if outputs.is_empty() {
region.properties.remove("outputs");
} else {
region
.properties
.insert("outputs".to_string(), serde_json::json!(outputs.clone()));
}
computed.insert(rid, (inputs, outputs));
}
self.refresh_brain_regions_hash();
Ok(computed)
}
pub fn get_root_region_id(&self) -> Option<String> {
self.brain_regions.get_root_region_id()
}
pub fn get_cortical_id(&self, cortical_idx: u32) -> Option<&CorticalID> {
self.cortical_idx_to_id.get(&cortical_idx)
}
pub fn get_all_cortical_idx_to_id_mappings(&self) -> ahash::AHashMap<u32, String> {
self.cortical_idx_to_id
.iter()
.map(|(idx, id)| (*idx, id.as_base_64()))
.collect()
}
pub fn get_all_visualization_granularities(&self) -> ahash::AHashMap<u32, (u32, u32, u32)> {
let mut granularities = ahash::AHashMap::new();
for (cortical_id, area) in &self.cortical_areas {
let cortical_idx = self
.cortical_id_to_idx
.get(cortical_id)
.copied()
.unwrap_or(0);
if let Some(granularity_json) = area.properties.get("visualization_voxel_granularity") {
if let Some(arr) = granularity_json.as_array() {
if arr.len() == 3 {
let x_opt = arr[0]
.as_u64()
.or_else(|| arr[0].as_f64().map(|f| f as u64));
let y_opt = arr[1]
.as_u64()
.or_else(|| arr[1].as_f64().map(|f| f as u64));
let z_opt = arr[2]
.as_u64()
.or_else(|| arr[2].as_f64().map(|f| f as u64));
if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
let granularity = (x as u32, y as u32, z as u32);
if granularity != (1, 1, 1) {
granularities.insert(cortical_idx, granularity);
}
}
}
}
}
}
granularities
}
pub fn get_cortical_area_ids(&self) -> Vec<&CorticalID> {
self.cortical_areas.keys().collect()
}
pub fn get_cortical_area_count(&self) -> usize {
self.cortical_areas.len()
}
pub fn get_upstream_cortical_areas(&self, target_cortical_id: &CorticalID) -> Vec<u32> {
if let Some(area) = self.cortical_areas.get(target_cortical_id) {
if let Some(upstream_prop) = area.properties.get("upstream_cortical_areas") {
if let Some(upstream_array) = upstream_prop.as_array() {
return upstream_array
.iter()
.filter_map(|v| v.as_u64().map(|n| n as u32))
.collect();
}
}
warn!(target: "feagi-bdu",
"Cortical area '{}' missing 'upstream_cortical_areas' property - treating as empty",
target_cortical_id.as_base_64()
);
}
Vec::new()
}
pub fn get_episodic_memory_upstream_cortical_areas(
&self,
target_cortical_id: &CorticalID,
) -> Vec<u32> {
let mut episodic_upstream = Vec::new();
for (src_id, src_area) in &self.cortical_areas {
if src_id == target_cortical_id {
continue;
}
let Some(mapping_dst) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
continue;
};
let Some(rules) =
Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
else {
continue;
};
if !Self::mapping_has_morphology_rule(rules, "episodic_memory") {
continue;
}
let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
continue;
};
episodic_upstream.push(src_idx);
}
episodic_upstream.sort_unstable();
episodic_upstream.dedup();
episodic_upstream
}
fn mapping_has_morphology_rule(rules: &[serde_json::Value], morphology: &str) -> bool {
rules.iter().any(|rule| {
let morphology_id = rule
.as_object()
.and_then(|obj| obj.get("morphology_id"))
.and_then(|v| v.as_str())
.or_else(|| {
rule.as_array()
.and_then(|arr| arr.first())
.and_then(|v| v.as_str())
});
morphology_id == Some(morphology)
})
}
fn area_belongs_to_classifier_assembly(area: &CorticalArea) -> bool {
if area
.properties
.get("classifier_assembly")
.and_then(|value| value.as_bool())
== Some(true)
{
return true;
}
matches!(
area.properties
.get("classifier_role")
.and_then(|value| value.as_str()),
Some("kernel_memory") | Some("class_memory") | Some("scan_twin")
)
}
fn mapping_is_classifier_assembly_edge(
&self,
src_id: &CorticalID,
dst_id: &CorticalID,
) -> bool {
let src_is_classifier = self
.cortical_areas
.get(src_id)
.is_some_and(Self::area_belongs_to_classifier_assembly);
let dst_is_classifier = self
.cortical_areas
.get(dst_id)
.is_some_and(Self::area_belongs_to_classifier_assembly);
if src_is_classifier || dst_is_classifier {
return true;
}
let src_key = src_id.as_base_64();
let dst_key = dst_id.as_base_64();
self.classifiers.values().any(|classifier| {
classifier.owns_area(&src_key)
|| classifier.owns_area(&dst_key)
|| (classifier.references_input(&src_key) && classifier.owns_area(&dst_key))
})
}
pub fn get_episodic_scan_upstream_cortical_areas(
&self,
target_cortical_id: &CorticalID,
) -> Vec<u32> {
let mut scan_upstream = Vec::new();
for (src_id, src_area) in &self.cortical_areas {
if src_id == target_cortical_id {
continue;
}
let Some(mapping_dst) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
continue;
};
let Some(rules) =
Self::get_mapping_rules_for_destination(mapping_dst, target_cortical_id)
else {
continue;
};
if !Self::mapping_has_morphology_rule(rules, "episodic_scan") {
continue;
}
let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
continue;
};
scan_upstream.push(src_idx);
}
scan_upstream.sort_unstable();
scan_upstream.dedup();
scan_upstream
}
fn mapping_from_src_to_dst_has_scan(
&self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
) -> bool {
let Some(src_area) = self.cortical_areas.get(src_area_id) else {
return false;
};
let Some(mapping_dst) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
return false;
};
let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
return false;
};
Self::mapping_has_morphology_rule(rules, "episodic_scan")
}
pub fn filter_non_memory_upstream_areas(&self, upstream: &[u32]) -> Vec<u32> {
upstream
.iter()
.filter_map(|idx| {
let cortical_id = self.cortical_idx_to_id.get(idx)?;
let area = self.cortical_areas.get(cortical_id)?;
if matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
None
} else {
Some(*idx)
}
})
.collect()
}
pub fn refresh_upstream_cortical_areas_from_mappings(
&mut self,
target_cortical_id: &CorticalID,
) -> Vec<u32> {
use std::collections::HashSet;
let target_id_str = target_cortical_id.as_base_64();
let mut upstream_idxs = HashSet::new();
for (src_id, src_area) in &self.cortical_areas {
if src_id == target_cortical_id {
continue;
}
if let Some(mapping) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
{
if mapping.contains_key(&target_id_str) {
if let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) {
upstream_idxs.insert(src_idx);
}
}
}
}
let mut upstream_list: Vec<u32> = upstream_idxs.into_iter().collect();
upstream_list.sort_unstable();
if let Some(target_area) = self.cortical_areas.get_mut(target_cortical_id) {
target_area.properties.insert(
"upstream_cortical_areas".to_string(),
serde_json::json!(upstream_list),
);
}
self.get_upstream_cortical_areas(target_cortical_id)
}
pub fn add_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
let upstream_array = area
.properties
.entry("upstream_cortical_areas".to_string())
.or_insert_with(|| serde_json::json!([]));
if let Some(arr) = upstream_array.as_array_mut() {
let src_value = serde_json::json!(src_cortical_idx);
if !arr.contains(&src_value) {
arr.push(src_value);
debug!(target: "feagi-bdu",
"Added upstream area idx={} to cortical area '{}'",
src_cortical_idx, target_cortical_id.as_base_64()
);
}
}
}
}
pub fn get_memory_twin_for_upstream_idx(
&self,
memory_area_idx: u32,
upstream_idx: u32,
) -> Option<CorticalID> {
let memory_id = self.cortical_idx_to_id.get(&memory_area_idx)?;
let upstream_id = self.cortical_idx_to_id.get(&upstream_idx)?;
let area = self.cortical_areas.get(memory_id)?;
let mapping = area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())?;
let twin_b64 = mapping.get(&upstream_id.as_base_64())?.as_str()?;
CorticalID::try_from_base_64(twin_b64).ok()
}
pub fn diagnose_memory_twin_for_mapping(
&self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
) -> BduResult<MemoryTwinDiagnostic> {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id.as_base_64()
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id.as_base_64()
))
})?;
let src_is_memory = matches!(src_area.cortical_type, CorticalAreaType::Memory(_));
let dst_is_memory = matches!(dst_area.cortical_type, CorticalAreaType::Memory(_));
let rules_opt = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
.and_then(|mapping_dst| {
Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
});
let mapping_exists = rules_opt.is_some();
let episodic_rule_count = rules_opt
.map(|rules| {
rules
.iter()
.filter(|rule| {
let morphology_id = rule
.as_object()
.and_then(|obj| obj.get("morphology_id"))
.and_then(|v| v.as_str())
.or_else(|| {
rule.as_array()
.and_then(|arr| arr.first())
.and_then(|v| v.as_str())
});
morphology_id == Some("episodic_memory")
})
.count()
})
.unwrap_or(0);
let twin_expected =
mapping_exists && episodic_rule_count > 0 && dst_is_memory && !src_is_memory;
let twin_cortical_area = dst_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())
.and_then(|map| map.get(&src_area_id.as_base_64()))
.and_then(|v| v.as_str())
.map(ToString::to_string);
let twin_present = twin_cortical_area.is_some();
let twin_parent_region_id = twin_cortical_area.as_ref().and_then(|twin_b64| {
CorticalID::try_from_base_64(twin_b64)
.ok()
.and_then(|twin_id| {
self.cortical_areas.get(&twin_id).and_then(|area| {
area.properties
.get("parent_region_id")
.and_then(|v| v.as_str())
.map(ToString::to_string)
})
})
});
let src_parent_region_id = src_area
.properties
.get("parent_region_id")
.and_then(|v| v.as_str())
.map(ToString::to_string);
let dst_parent_region_id = dst_area
.properties
.get("parent_region_id")
.and_then(|v| v.as_str())
.map(ToString::to_string);
let twin_parent_matches_src_parent = match (&twin_parent_region_id, &src_parent_region_id) {
(Some(twin_parent), Some(src_parent)) => Some(twin_parent == src_parent),
(None, None) if twin_present => Some(true),
_ if twin_present => Some(false),
_ => None,
};
let reason = if !mapping_exists {
Some("no_mapping_rule_between_src_and_dst".to_string())
} else if episodic_rule_count == 0 {
Some("mapping_exists_but_not_episodic_memory".to_string())
} else if !dst_is_memory {
Some("destination_is_not_memory_area".to_string())
} else if src_is_memory {
Some("upstream_is_memory_area_twin_not_supported".to_string())
} else if !twin_present {
Some("twin_expected_but_missing".to_string())
} else if twin_parent_matches_src_parent == Some(false) {
Some("twin_parent_region_mismatch_with_source".to_string())
} else {
None
};
Ok(MemoryTwinDiagnostic {
src_cortical_area: src_area_id.as_base_64(),
dst_cortical_area: dst_area_id.as_base_64(),
src_cortical_type: src_area.cortical_type.to_string(),
dst_cortical_type: dst_area.cortical_type.to_string(),
mapping_exists,
episodic_rule_count,
twin_expected,
twin_present,
twin_cortical_area,
reason,
src_parent_region_id,
dst_parent_region_id,
twin_parent_region_id,
twin_parent_matches_src_parent,
})
}
pub fn ensure_memory_twin_area(
&mut self,
memory_area_id: &CorticalID,
upstream_area_id: &CorticalID,
) -> BduResult<CorticalID> {
use crate::models::CorticalAreaExt;
let register_replay_mapping = |manager: &mut ConnectomeManager,
twin_id: &CorticalID|
-> BduResult<()> {
let Some(npu) = manager.npu.as_ref() else {
return Ok(());
};
let memory_area_idx =
*manager
.cortical_id_to_idx
.get(memory_area_id)
.ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area idx missing for {}",
memory_area_id.as_base_64()
))
})?;
let upstream_area_idx = *manager
.cortical_id_to_idx
.get(upstream_area_id)
.ok_or_else(|| {
BduError::InvalidArea(format!(
"Upstream area idx missing for {}",
upstream_area_id.as_base_64()
))
})?;
let twin_area_idx = *manager.cortical_id_to_idx.get(twin_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Twin area idx missing for {}",
twin_id.as_base_64()
))
})?;
let twin_area = manager.cortical_areas.get(twin_id).ok_or_else(|| {
BduError::InvalidArea(format!("Twin area {} not found", twin_id.as_base_64()))
})?;
let potential = twin_area.firing_threshold() + twin_area.firing_threshold_increment();
if let Ok(mut npu_lock) = npu.lock() {
npu_lock.register_memory_twin_mapping(
memory_area_idx,
upstream_area_idx,
twin_area_idx,
potential,
);
}
Ok(())
};
let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area {} not found",
memory_area_id.as_base_64()
))
})?;
let upstream_area = self.cortical_areas.get(upstream_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Upstream area {} not found",
upstream_area_id.as_base_64()
))
})?;
if matches!(upstream_area.cortical_type, CorticalAreaType::Memory(_)) {
return Err(BduError::InvalidArea(format!(
"Upstream area {} is memory type; twin creation is only for non-memory areas",
upstream_area_id.as_base_64()
)));
}
if let Some(existing) = memory_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())
.and_then(|map| map.get(&upstream_area_id.as_base_64()))
.and_then(|v| v.as_str())
.and_then(|s| CorticalID::try_from_base_64(s).ok())
{
self.ensure_memory_replay_mapping(memory_area_id, &existing)?;
register_replay_mapping(self, &existing)?;
self.refresh_cortical_mappings_hash();
return Ok(existing);
}
let twin_id = self.build_memory_twin_id(memory_area_id, upstream_area_id)?;
if self.cortical_areas.contains_key(&twin_id) {
if let Some(existing) = self.cortical_areas.get_mut(&twin_id) {
let expected_source = upstream_area_id.as_base_64();
let expected_target = memory_area_id.as_base_64();
let existing_source = existing
.properties
.get("memory_twin_of")
.and_then(|v| v.as_str());
let existing_target = existing
.properties
.get("memory_twin_for")
.and_then(|v| v.as_str());
if existing_source != Some(expected_source.as_str())
|| existing_target != Some(expected_target.as_str())
{
warn!(
target: "feagi-bdu",
"Twin cortical ID properties missing/mismatched for {} -> {}; repairing",
upstream_area_id.as_base_64(),
memory_area_id.as_base_64()
);
existing.properties.insert(
"memory_twin_of".to_string(),
serde_json::json!(expected_source),
);
existing.properties.insert(
"memory_twin_for".to_string(),
serde_json::json!(expected_target),
);
}
}
self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
register_replay_mapping(self, &twin_id)?;
self.refresh_cortical_mappings_hash();
return Ok(twin_id);
}
let twin_name = format!("{}_twin", upstream_area.name.replace(' ', "_"));
let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
let twin_position = self.build_memory_twin_position(memory_area, upstream_area);
let mut twin_area = CorticalArea::new(
twin_id,
0,
twin_name,
upstream_area.dimensions,
twin_position,
twin_type,
)?;
twin_area.properties = self.build_memory_twin_properties(
memory_area,
upstream_area,
memory_area_id,
upstream_area_id,
);
let _twin_idx = self.add_cortical_area(twin_area)?;
let _ = self.create_neurons_for_area(&twin_id);
self.set_memory_twin_mapping(memory_area_id, upstream_area_id, &twin_id);
self.ensure_memory_replay_mapping(memory_area_id, &twin_id)?;
register_replay_mapping(self, &twin_id)?;
self.refresh_cortical_mappings_hash();
Ok(twin_id)
}
pub fn ensure_scan_twin_area(
&mut self,
memory_area_id: &CorticalID,
field_area_id: &CorticalID,
) -> BduResult<CorticalID> {
let class_channel_count = self.classifier_class_channel_count(memory_area_id)?;
let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area {} not found",
memory_area_id.as_base_64()
))
})?;
let field_area = self.cortical_areas.get(field_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Scan field area {} not found",
field_area_id.as_base_64()
))
})?;
if matches!(field_area.cortical_type, CorticalAreaType::Memory(_)) {
return Err(BduError::InvalidArea(format!(
"Scan field {} is memory type; scan twins are only for non-memory fields",
field_area_id.as_base_64()
)));
}
if let Some(existing) = memory_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())
.and_then(|map| map.get(&field_area_id.as_base_64()))
.and_then(|v| v.as_str())
.and_then(|s| CorticalID::try_from_base_64(s).ok())
{
self.refresh_cortical_mappings_hash();
return Ok(existing);
}
let twin_id = self.build_scan_twin_id(memory_area_id, field_area_id)?;
if self.cortical_areas.contains_key(&twin_id) {
self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
self.refresh_cortical_mappings_hash();
return Ok(twin_id);
}
let twin_name = format!("{}_scan_twin", field_area.name.replace(' ', "_"));
let twin_type = CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire);
let twin_position = self.build_memory_twin_position(memory_area, field_area);
let twin_dims = CorticalAreaDimensions::new(
field_area.dimensions.width,
field_area.dimensions.height,
class_channel_count,
)
.map_err(|e| BduError::Internal(format!("Invalid scan twin dimensions: {}", e)))?;
let mut twin_area =
CorticalArea::new(twin_id, 0, twin_name, twin_dims, twin_position, twin_type)?;
twin_area.properties = self.build_memory_twin_properties(
memory_area,
field_area,
memory_area_id,
field_area_id,
);
twin_area
.properties
.insert("scan_twin".to_string(), serde_json::json!(true));
let _twin_idx = self.add_cortical_area(twin_area)?;
let _ = self.create_neurons_for_area(&twin_id);
self.set_memory_twin_mapping(memory_area_id, field_area_id, &twin_id);
self.refresh_cortical_mappings_hash();
Ok(twin_id)
}
fn classifier_class_channel_count(&self, memory_area_id: &CorticalID) -> BduResult<u32> {
let memory_area = self.cortical_areas.get(memory_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area {} not found",
memory_area_id.as_base_64()
))
})?;
let class_id = memory_area
.properties
.get("classifier_class_area_id")
.and_then(|v| v.as_str())
.ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area {} has no classifier_class_area_id; scan twin cannot be sized",
memory_area_id.as_base_64()
))
})?;
let class_cortical_id = CorticalID::try_from_base_64(class_id).map_err(|e| {
BduError::InvalidArea(format!(
"Invalid classifier_class_area_id '{}': {}",
class_id, e
))
})?;
let class_area = self.cortical_areas.get(&class_cortical_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Classifier class area {} not found",
class_cortical_id.as_base_64()
))
})?;
let count = class_area
.dimensions
.width
.saturating_mul(class_area.dimensions.height)
.saturating_mul(class_area.dimensions.depth);
if count == 0 {
return Err(BduError::InvalidArea(format!(
"Classifier class area {} has zero volume",
class_cortical_id.as_base_64()
)));
}
Ok(count)
}
fn build_scan_twin_id(
&self,
memory_area_id: &CorticalID,
field_area_id: &CorticalID,
) -> BduResult<CorticalID> {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
hasher.write(memory_area_id.as_base_64().as_bytes());
hasher.write(field_area_id.as_base_64().as_bytes());
hasher.write(b"scan_twin");
let hash = hasher.finish();
let mut bytes = hash.to_be_bytes();
bytes[0] = b'c';
CorticalID::try_from_bytes(&bytes).map_err(|e| {
BduError::Internal(format!("Failed to build scan twin cortical ID: {}", e))
})
}
fn build_memory_twin_position(
&self,
_memory_area: &CorticalArea,
upstream_area: &CorticalArea,
) -> GenomeCoordinate3D {
let width = upstream_area.dimensions.width as f32;
let margin = (width * 0.25).ceil() as i32;
let offset = upstream_area.dimensions.width as i32 + margin;
GenomeCoordinate3D::new(
upstream_area.position.x + offset,
upstream_area.position.y,
upstream_area.position.z,
)
}
fn build_memory_twin_id(
&self,
memory_area_id: &CorticalID,
upstream_area_id: &CorticalID,
) -> BduResult<CorticalID> {
let mut hasher = Xxh64::new(DATA_HASH_SEED);
hasher.write(memory_area_id.as_base_64().as_bytes());
hasher.write(upstream_area_id.as_base_64().as_bytes());
hasher.write(b"memory_twin");
let hash = hasher.finish();
let mut bytes = hash.to_be_bytes();
bytes[0] = b'c';
CorticalID::try_from_bytes(&bytes)
.map_err(|e| BduError::Internal(format!("Failed to build twin cortical ID: {}", e)))
}
fn build_memory_twin_properties(
&self,
_memory_area: &CorticalArea,
upstream_area: &CorticalArea,
memory_area_id: &CorticalID,
upstream_area_id: &CorticalID,
) -> HashMap<String, serde_json::Value> {
let mut props = upstream_area.properties.clone();
props.remove("cortical_mapping_dst");
props.remove("upstream_cortical_areas");
props.remove("parent_region_id");
props.insert("cortical_group".to_string(), serde_json::json!("CUSTOM"));
props.insert("is_mem_type".to_string(), serde_json::json!(false));
props.insert(
"memory_twin_of".to_string(),
serde_json::json!(upstream_area_id.as_base_64()),
);
props.insert(
"memory_twin_for".to_string(),
serde_json::json!(memory_area_id.as_base_64()),
);
if let Some(parent_region_id) = upstream_area
.properties
.get("parent_region_id")
.and_then(|v| v.as_str())
{
props.insert(
"parent_region_id".to_string(),
serde_json::json!(parent_region_id),
);
}
props
}
pub fn remove_memory_twin_mapping(
&mut self,
memory_area_id: &str,
field_area_id: &str,
) -> BduResult<()> {
let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
})?;
if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
if let Some(twins) = memory_area
.properties
.get_mut("memory_twin_areas")
.and_then(|value| value.as_object_mut())
{
twins.remove(field_area_id);
}
}
Ok(())
}
fn set_memory_twin_mapping(
&mut self,
memory_area_id: &CorticalID,
upstream_area_id: &CorticalID,
twin_id: &CorticalID,
) {
if let Some(memory_area) = self.cortical_areas.get_mut(memory_area_id) {
let mapping = memory_area
.properties
.entry("memory_twin_areas".to_string())
.or_insert_with(|| serde_json::json!({}));
if let Some(map) = mapping.as_object_mut() {
map.insert(
upstream_area_id.as_base_64(),
serde_json::json!(twin_id.as_base_64()),
);
}
}
}
fn twin_is_classifier_stamp(&self, twin_id: &CorticalID) -> bool {
let twin_b64 = twin_id.as_base_64();
if self
.classifiers
.values()
.any(|classifier| classifier.field_for_twin(&twin_b64).is_some())
{
return true;
}
self.cortical_areas
.get(twin_id)
.is_some_and(Self::area_belongs_to_classifier_assembly)
}
pub fn rekey_memory_twin_source(
&mut self,
memory_area_id: &str,
old_src_area_id: &str,
new_src_area_id: &str,
) -> BduResult<()> {
if old_src_area_id == new_src_area_id {
return Ok(());
}
let memory_id = CorticalID::try_from_base_64(memory_area_id).map_err(|e| {
BduError::InvalidArea(format!("Invalid memory area {}: {}", memory_area_id, e))
})?;
let twin_entries: Vec<(String, serde_json::Value)> = {
let memory_area = self.cortical_areas.get(&memory_id).ok_or_else(|| {
BduError::InvalidArea(format!("Memory area {} not found", memory_area_id))
})?;
memory_area
.properties
.get("memory_twin_areas")
.and_then(|value| value.as_object())
.map(|twins| {
twins
.iter()
.map(|(key, value)| (key.clone(), value.clone()))
.collect()
})
.unwrap_or_default()
};
if twin_entries.is_empty() {
return Ok(());
}
let mut used_key: Option<String> = None;
let mut twin_id: Option<serde_json::Value> = None;
if let Some((_, value)) = twin_entries.iter().find(|(key, _)| key == old_src_area_id) {
used_key = Some(old_src_area_id.to_string());
twin_id = Some(value.clone());
} else {
for (key, value) in &twin_entries {
let Some(twin_b64) = value.as_str() else {
continue;
};
let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) else {
continue;
};
let owned_by_old_field = self
.cortical_areas
.get(&twin_cortical_id)
.and_then(|twin| twin.properties.get("memory_twin_of"))
.and_then(|property| property.as_str())
== Some(old_src_area_id);
if owned_by_old_field {
used_key = Some(key.clone());
twin_id = Some(value.clone());
break;
}
}
}
let Some(used_key) = used_key else {
return Ok(());
};
let Some(twin_id) = twin_id else {
return Ok(());
};
if let Some(memory_area) = self.cortical_areas.get_mut(&memory_id) {
if let Some(twins) = memory_area
.properties
.get_mut("memory_twin_areas")
.and_then(|value| value.as_object_mut())
{
twins.remove(&used_key);
twins.insert(new_src_area_id.to_string(), twin_id.clone());
}
}
if let Some(twin_b64) = twin_id.as_str() {
if let Ok(twin_cortical_id) = CorticalID::try_from_base_64(twin_b64) {
if let Some(twin) = self.cortical_areas.get_mut(&twin_cortical_id) {
twin.properties.insert(
"memory_twin_of".to_string(),
serde_json::json!(new_src_area_id),
);
}
}
}
Ok(())
}
fn ensure_memory_replay_mapping(
&mut self,
memory_area_id: &CorticalID,
twin_id: &CorticalID,
) -> BduResult<()> {
if !self.morphology_registry.contains("memory_replay") {
feagi_evolutionary::add_core_morphologies(&mut self.morphology_registry);
}
self.refresh_morphologies_hash();
let mapping_data = vec![serde_json::json!({
"morphology_id": "memory_replay",
"morphology_scalar": [1, 1, 1],
"postSynapticCurrent_multiplier": 1,
"plasticity_flag": false,
"plasticity_constant": 0,
"ltp_multiplier": 0,
"ltd_multiplier": 0,
"plasticity_window": 0,
})];
self.update_cortical_mapping(memory_area_id, twin_id, mapping_data)?;
let _ = self.regenerate_synapses_for_mapping(memory_area_id, twin_id)?;
self.refresh_cortical_area_hashes(true, false);
Ok(())
}
pub fn teardown_owned_memory_twin_for_mapping(
&mut self,
memory_area_id: &CorticalID,
upstream_area_id: &CorticalID,
) -> BduResult<Option<CorticalID>> {
let upstream_id = upstream_area_id.as_base_64();
let twin_id = self
.cortical_areas
.get(memory_area_id)
.and_then(|memory_area| memory_area.properties.get("memory_twin_areas"))
.and_then(|value| value.as_object())
.and_then(|twins| twins.get(&upstream_id))
.and_then(|value| value.as_str())
.map(CorticalID::try_from_base_64)
.transpose()
.map_err(|error| {
BduError::InvalidArea(format!(
"Invalid owned memory twin ID for {} -> {}: {}",
upstream_area_id.as_base_64(),
memory_area_id.as_base_64(),
error
))
})?;
let Some(twin_id) = twin_id else {
return Ok(None);
};
if self.twin_is_classifier_stamp(&twin_id) {
return Ok(None);
}
let twin = self.cortical_areas.get(&twin_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Owned memory twin {} for {} -> {} does not exist",
twin_id.as_base_64(),
upstream_area_id.as_base_64(),
memory_area_id.as_base_64()
))
})?;
let owned_by_source = twin
.properties
.get("memory_twin_of")
.and_then(|value| value.as_str())
== Some(upstream_id.as_str());
let owned_by_memory = twin
.properties
.get("memory_twin_for")
.and_then(|value| value.as_str())
== Some(memory_area_id.as_base_64().as_str());
if !owned_by_source || !owned_by_memory {
return Err(BduError::InvalidArea(format!(
"Cortical area {} is not the generated twin owned by {} -> {}",
twin_id.as_base_64(),
upstream_area_id.as_base_64(),
memory_area_id.as_base_64()
)));
}
let memory_idx = *self.cortical_id_to_idx.get(memory_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area idx missing for {}",
memory_area_id.as_base_64()
))
})?;
let upstream_idx = *self
.cortical_id_to_idx
.get(upstream_area_id)
.ok_or_else(|| {
BduError::InvalidArea(format!(
"Upstream area idx missing for {}",
upstream_area_id.as_base_64()
))
})?;
self.update_cortical_mapping(memory_area_id, &twin_id, Vec::new())?;
let _ = self.regenerate_synapses_for_mapping(memory_area_id, &twin_id)?;
if let Some(npu) = &self.npu {
npu.lock()
.unwrap()
.unregister_memory_twin_mapping(memory_idx, upstream_idx);
}
let memory_area = self.cortical_areas.get_mut(memory_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area {} not found",
memory_area_id.as_base_64()
))
})?;
let twins = memory_area
.properties
.get_mut("memory_twin_areas")
.and_then(|value| value.as_object_mut())
.ok_or_else(|| {
BduError::InvalidArea(format!(
"Memory area {} has invalid memory_twin_areas metadata",
memory_area_id.as_base_64()
))
})?;
twins.remove(&upstream_id);
if twins.is_empty() {
memory_area.properties.remove("memory_twin_areas");
}
for region_id in self
.get_brain_region_ids()
.into_iter()
.cloned()
.collect::<Vec<_>>()
{
if let Some(region) = self.get_brain_region_mut(®ion_id) {
region.remove_area(&twin_id);
}
}
self.remove_cortical_area(&twin_id)?;
self.refresh_cortical_mappings_hash();
Ok(Some(twin_id))
}
pub fn remove_upstream_area(&mut self, target_cortical_id: &CorticalID, src_cortical_idx: u32) {
if let Some(area) = self.cortical_areas.get_mut(target_cortical_id) {
if let Some(upstream_prop) = area.properties.get_mut("upstream_cortical_areas") {
if let Some(arr) = upstream_prop.as_array_mut() {
let src_value = serde_json::json!(src_cortical_idx);
if let Some(pos) = arr.iter().position(|v| v == &src_value) {
arr.remove(pos);
debug!(target: "feagi-bdu",
"Removed upstream area idx={} from cortical area '{}'",
src_cortical_idx, target_cortical_id.as_base_64()
);
}
}
}
}
}
pub fn has_cortical_area(&self, cortical_id: &CorticalID) -> bool {
self.cortical_areas.contains_key(cortical_id)
}
pub fn is_initialized(&self) -> bool {
self.initialized && !self.cortical_areas.is_empty()
}
pub fn add_brain_region(
&mut self,
region: BrainRegion,
parent_id: Option<String>,
) -> BduResult<()> {
self.brain_regions.add_region(region, parent_id)?;
self.refresh_brain_regions_hash();
Ok(())
}
pub fn remove_brain_region(&mut self, region_id: &str) -> BduResult<()> {
self.brain_regions.remove_region(region_id)?;
self.refresh_brain_regions_hash();
Ok(())
}
pub fn clear_brain_regions(&mut self) {
self.brain_regions.clear();
self.refresh_brain_regions_hash();
}
pub fn replace_classifiers(
&mut self,
classifiers: HashMap<String, feagi_structures::genomic::classifiers::Classifier>,
) {
self.classifiers = classifiers;
self.refresh_classifiers_hash();
}
pub fn apply_loaded_classifier_assemblies(&mut self) {
let classifiers: Vec<_> = self.classifiers.values().cloned().collect();
for classifier in classifiers {
self.apply_loaded_classifier_assembly(&classifier);
}
}
fn apply_loaded_classifier_assembly(
&mut self,
classifier: &feagi_structures::genomic::classifiers::Classifier,
) {
let Ok(kernel_mem_id) = CorticalID::try_from_base_64(&classifier.kernel_memory_id) else {
return;
};
let Ok(class_mem_id) = CorticalID::try_from_base_64(&classifier.class_memory_id) else {
return;
};
let mut twin_map = serde_json::Map::new();
for field in &classifier.fields {
if field.field_area_id.is_empty() || field.scan_twin_id.is_empty() {
continue;
}
let Ok(twin_id) = CorticalID::try_from_base_64(&field.scan_twin_id) else {
continue;
};
if !self.cortical_areas.contains_key(&twin_id) {
continue;
}
twin_map.insert(
field.field_area_id.clone(),
serde_json::json!(field.scan_twin_id),
);
if let Ok(field_id) = CorticalID::try_from_base_64(&field.field_area_id) {
if let Some(field_area) = self.cortical_areas.get_mut(&field_id) {
Self::ensure_assembly_burst(field_area);
}
}
if let Some(twin_area) = self.cortical_areas.get_mut(&twin_id) {
twin_area
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
twin_area.properties.insert(
"classifier_role".to_string(),
serde_json::json!("scan_twin"),
);
twin_area
.properties
.insert("scan_twin".to_string(), serde_json::json!(true));
twin_area.properties.insert(
"memory_twin_of".to_string(),
serde_json::json!(field.field_area_id),
);
twin_area.properties.insert(
"memory_twin_for".to_string(),
serde_json::json!(classifier.kernel_memory_id),
);
Self::ensure_assembly_burst(twin_area);
}
}
if let Some(kernel_mem) = self.cortical_areas.get_mut(&kernel_mem_id) {
kernel_mem
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
kernel_mem.properties.insert(
"classifier_role".to_string(),
serde_json::json!("kernel_memory"),
);
if let Some(kernel_area_id) = &classifier.kernel_area_id {
kernel_mem.properties.insert(
"classifier_kernel_area_id".to_string(),
serde_json::json!(kernel_area_id),
);
}
if let Some(class_area_id) = &classifier.class_area_id {
kernel_mem.properties.insert(
"classifier_class_area_id".to_string(),
serde_json::json!(class_area_id),
);
}
kernel_mem.properties.insert(
"classifier_class_memory_id".to_string(),
serde_json::json!(classifier.class_memory_id),
);
kernel_mem.properties.insert(
"memory_twin_areas".to_string(),
serde_json::Value::Object(twin_map),
);
Self::ensure_assembly_burst(kernel_mem);
}
if let Some(class_mem) = self.cortical_areas.get_mut(&class_mem_id) {
class_mem
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
class_mem.properties.insert(
"classifier_role".to_string(),
serde_json::json!("class_memory"),
);
class_mem.properties.insert(
"classifier_kernel_memory_id".to_string(),
serde_json::json!(classifier.kernel_memory_id),
);
if let Some(class_area_id) = &classifier.class_area_id {
class_mem.properties.insert(
"classifier_class_area_id".to_string(),
serde_json::json!(class_area_id),
);
}
Self::ensure_assembly_burst(class_mem);
}
}
fn ensure_assembly_burst(area: &mut CorticalArea) {
if !area.properties.contains_key("burst_engine_active") {
area.properties
.insert("burst_engine_active".to_string(), serde_json::json!(true));
}
}
pub fn upsert_classifier(
&mut self,
classifier: feagi_structures::genomic::classifiers::Classifier,
) {
self.classifiers
.insert(classifier.classifier_id.clone(), classifier);
self.refresh_classifiers_hash();
}
pub fn get_classifier(
&self,
classifier_id: &str,
) -> Option<&feagi_structures::genomic::classifiers::Classifier> {
self.classifiers.get(classifier_id)
}
pub fn list_classifiers(
&self,
) -> HashMap<String, feagi_structures::genomic::classifiers::Classifier> {
self.classifiers.clone()
}
pub fn remove_classifier(
&mut self,
classifier_id: &str,
) -> Option<feagi_structures::genomic::classifiers::Classifier> {
let removed = self.classifiers.remove(classifier_id);
if removed.is_some() {
self.refresh_classifiers_hash();
}
removed
}
pub fn classifiers_owning_area(
&self,
area_id: &str,
) -> Vec<feagi_structures::genomic::classifiers::Classifier> {
self.classifiers
.values()
.filter(|classifier| classifier.owns_area(area_id))
.cloned()
.collect()
}
pub fn clear_classifier_inputs_for_area(&mut self, area_id: &str) -> usize {
let mut updated = 0usize;
for classifier in self.classifiers.values_mut() {
if classifier.references_input(area_id) {
classifier.clear_input(area_id);
updated += 1;
}
}
if updated > 0 {
self.refresh_classifiers_hash();
}
updated
}
pub fn apply_classifier_mapping_change(
&mut self,
src_area_id: &str,
dst_area_id: &str,
morphology_id: &str,
removed: bool,
) -> usize {
let mut updated = 0usize;
for classifier in self.classifiers.values_mut() {
if classifier.apply_mapping_change(src_area_id, dst_area_id, morphology_id, removed) {
updated += 1;
}
}
if updated > 0 {
self.refresh_classifiers_hash();
}
updated
}
pub fn change_brain_region_parent(
&mut self,
region_id: &str,
new_parent_id: &str,
) -> BduResult<()> {
self.brain_regions.change_parent(region_id, new_parent_id)?;
self.refresh_brain_regions_hash();
Ok(())
}
pub fn get_brain_region(&self, region_id: &str) -> Option<&BrainRegion> {
self.brain_regions.get_region(region_id)
}
pub fn get_brain_region_mut(&mut self, region_id: &str) -> Option<&mut BrainRegion> {
self.brain_regions.get_region_mut(region_id)
}
pub fn get_brain_region_ids(&self) -> Vec<&String> {
self.brain_regions.get_all_region_ids()
}
pub fn get_brain_region_hierarchy(&self) -> &BrainRegionHierarchy {
&self.brain_regions
}
pub fn get_morphologies(&self) -> &feagi_evolutionary::MorphologyRegistry {
&self.morphology_registry
}
pub fn get_morphology_count(&self) -> usize {
self.morphology_registry.count()
}
pub fn upsert_morphology(
&mut self,
morphology_id: String,
morphology: feagi_evolutionary::Morphology,
) {
self.morphology_registry
.add_morphology(morphology_id, morphology);
self.refresh_morphologies_hash();
}
pub fn remove_morphology(&mut self, morphology_id: &str) -> bool {
let removed = self.morphology_registry.remove_morphology(morphology_id);
if removed {
self.refresh_morphologies_hash();
}
removed
}
pub fn update_cortical_mapping(
&mut self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
mapping_data: Vec<serde_json::Value>,
) -> BduResult<()> {
use tracing::info;
self.validate_memory_outbound_mapping_contract(src_area_id, dst_area_id, &mapping_data)?;
crate::region_io_designation::validate_cross_region_mapping_proposal(
self,
src_area_id,
dst_area_id,
&mapping_data,
)?;
debug!(target: "feagi-bdu", "Updating cortical mapping: {} -> {}", src_area_id, dst_area_id);
{
let src_area = self.cortical_areas.get_mut(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let cortical_mapping_dst =
if let Some(existing) = src_area.properties.get_mut("cortical_mapping_dst") {
existing.as_object_mut().ok_or_else(|| {
crate::types::BduError::InvalidMorphology(
"cortical_mapping_dst is not an object".to_string(),
)
})?
} else {
src_area
.properties
.insert("cortical_mapping_dst".to_string(), serde_json::json!({}));
src_area
.properties
.get_mut("cortical_mapping_dst")
.unwrap()
.as_object_mut()
.unwrap()
};
if mapping_data.is_empty() {
cortical_mapping_dst.remove(&dst_area_id.as_base_64());
info!(target: "feagi-bdu", "Removed mapping from {} to {}", src_area_id, dst_area_id);
} else {
cortical_mapping_dst.insert(
dst_area_id.as_base_64(),
serde_json::Value::Array(mapping_data.clone()),
);
debug!(target: "feagi-bdu", "Updated mapping from {} to {} with {} connections",
src_area_id, dst_area_id, mapping_data.len());
}
}
self.refresh_cortical_mappings_hash();
Ok(())
}
pub fn validate_memory_outbound_mapping_contract(
&self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
mapping_data: &[serde_json::Value],
) -> BduResult<()> {
if mapping_data.is_empty() {
return Ok(());
}
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
BduError::InvalidArea(format!("Destination area not found: {}", dst_area_id))
})?;
if !matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
|| matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
{
return Ok(());
}
for rule in mapping_data {
let morphology_id = rule
.as_object()
.and_then(|rule_obj| rule_obj.get("morphology_id"))
.and_then(|value| value.as_str())
.or_else(|| {
rule.as_array()
.and_then(|rule_array| rule_array.first())
.and_then(|value| value.as_str())
});
let is_replay_edge_to_own_twin = morphology_id == Some("memory_replay")
&& dst_area
.properties
.get("memory_twin_for")
.and_then(|value| value.as_str())
== Some(src_area_id.as_base_64().as_str());
if morphology_id != Some("associative_memory") && !is_replay_edge_to_own_twin {
return Err(BduError::InvalidMorphology(format!(
"Memory-to-non-memory mapping {} -> {} only supports associative_memory \
(or memory_replay to its own twin)",
src_area_id, dst_area_id
)));
}
}
Ok(())
}
pub fn regenerate_synapses_for_mapping(
&mut self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
) -> BduResult<usize> {
use tracing::info;
debug!(target: "feagi-bdu", "Regenerating synapses: {} -> {}", src_area_id, dst_area_id);
let mapping_rules_len = self
.cortical_areas
.get(src_area_id)
.and_then(|area| area.properties.get("cortical_mapping_dst"))
.and_then(|v| v.as_object())
.and_then(|map| map.get(&dst_area_id.as_base_64()))
.and_then(|v| v.as_array())
.map(|arr| arr.len())
.unwrap_or(0);
tracing::debug!(
target: "feagi-bdu",
"Mapping rules for {} -> {}: {}",
src_area_id,
dst_area_id,
mapping_rules_len
);
let Some(npu_arc) = self.npu.clone() else {
info!(target: "feagi-bdu", "NPU not available - skipping synapse regeneration");
return Ok(0);
};
let src_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
BduError::InvalidArea(format!("No cortical idx for source area {}", src_area_id))
})?;
let dst_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
BduError::InvalidArea(format!(
"No cortical idx for destination area {}",
dst_area_id
))
})?;
let mut pruned_synapse_count: usize = 0;
use std::time::Instant;
let start = Instant::now();
let (sources, targets) = {
let lock_start = std::time::Instant::now();
let npu = npu_arc.lock().unwrap();
let lock_wait = lock_start.elapsed();
tracing::debug!(
target: "feagi-bdu",
"[NPU-LOCK] prune list lock wait {:.2}ms for {} -> {}",
lock_wait.as_secs_f64() * 1000.0,
src_area_id,
dst_area_id
);
let sources: Vec<NeuronId> = npu
.get_neurons_in_cortical_area(src_idx)
.into_iter()
.map(NeuronId)
.collect();
let targets: Vec<NeuronId> = npu
.get_neurons_in_cortical_area(dst_idx)
.into_iter()
.map(NeuronId)
.collect();
(sources, targets)
};
tracing::debug!(
target: "feagi-bdu",
"Prune synapses: {} sources, {} targets",
sources.len(),
targets.len()
);
if !sources.is_empty() && !targets.is_empty() {
let remove_start = Instant::now();
pruned_synapse_count = {
let lock_start = std::time::Instant::now();
let mut npu = npu_arc.lock().unwrap();
let lock_wait = lock_start.elapsed();
tracing::debug!(
target: "feagi-bdu",
"[NPU-LOCK] prune remove lock wait {:.2}ms for {} -> {}",
lock_wait.as_secs_f64() * 1000.0,
src_area_id,
dst_area_id
);
npu.remove_synapses_between(sources, targets)
};
let remove_time = remove_start.elapsed();
let total_time = start.elapsed();
info!(
target: "feagi-bdu",
"Pruned {} existing synapses for mapping {} -> {} (total={}ms, remove={}ms)",
pruned_synapse_count,
src_area_id,
dst_area_id,
total_time.as_millis(),
remove_time.as_millis()
);
if pruned_synapse_count > 0 {
let pruned_u32 = u32::try_from(pruned_synapse_count).map_err(|_| {
BduError::Internal(format!(
"Pruned synapse count overflow (usize -> u32): {}",
pruned_synapse_count
))
})?;
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.subtract_synapse_count(pruned_u32);
state_manager.subtract_cortical_area_outgoing_synapses(
&src_area_id.as_base_64(),
pruned_synapse_count,
);
state_manager.subtract_cortical_area_incoming_synapses(
&dst_area_id.as_base_64(),
pruned_synapse_count,
);
{
let mut cache = self.cached_synapse_counts_per_area.write();
let entry = cache
.entry(*src_area_id)
.or_insert_with(|| AtomicUsize::new(0));
let mut current = entry.load(Ordering::Relaxed);
loop {
let next = current.saturating_sub(pruned_synapse_count);
match entry.compare_exchange(
current,
next,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(v) => current = v,
}
}
}
}
}
let synapse_count = self.apply_cortical_mapping_for_pair(src_area_id, dst_area_id)?;
tracing::debug!(
target: "feagi-bdu",
"Synaptogenesis created {} synapses for {} -> {}",
synapse_count,
src_area_id,
dst_area_id
);
if synapse_count > 0 {
let created_u32 = u32::try_from(synapse_count).map_err(|_| {
BduError::Internal(format!(
"Created synapse count overflow (usize -> u32): {}",
synapse_count
))
})?;
{
let mut cache = self.cached_synapse_counts_per_area.write();
cache
.entry(*src_area_id)
.or_insert_with(|| AtomicUsize::new(0))
.fetch_add(synapse_count, Ordering::Relaxed);
}
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.add_synapse_count(created_u32);
state_manager
.add_cortical_area_outgoing_synapses(&src_area_id.as_base_64(), synapse_count);
state_manager
.add_cortical_area_incoming_synapses(&dst_area_id.as_base_64(), synapse_count);
}
let src_idx_for_upstream = src_idx;
let has_mapping = self
.cortical_areas
.get(src_area_id)
.and_then(|area| area.properties.get("cortical_mapping_dst"))
.and_then(|v| v.as_object())
.and_then(|map| map.get(&dst_area_id.as_base_64()))
.is_some();
debug!(target: "feagi-bdu",
"Mapping result: {} synapses, {} -> {} (mapping_exists={}, will {}update upstream)",
synapse_count,
src_area_id.as_base_64(),
dst_area_id.as_base_64(),
has_mapping,
if has_mapping { "" } else { "NOT " }
);
if has_mapping {
self.add_upstream_area(dst_area_id, src_idx_for_upstream);
if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
if matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
&& !Self::area_belongs_to_classifier_assembly(dst_area)
{
let is_scan = self.mapping_from_src_to_dst_has_scan(src_area_id, dst_area_id);
let twin_result = if is_scan {
self.ensure_scan_twin_area(dst_area_id, src_area_id)
} else {
self.ensure_memory_twin_area(dst_area_id, src_area_id)
};
if let Err(e) = twin_result {
warn!(
target: "feagi-bdu",
"Failed to ensure {} twin for {} -> {}: {}",
if is_scan { "scan" } else { "memory" },
src_area_id.as_base_64(),
dst_area_id.as_base_64(),
e
);
}
}
}
#[cfg(feature = "plasticity")]
if let Some(ref executor) = self.plasticity_executor {
use feagi_evolutionary::extract_memory_properties;
if let Some(dst_area) = self.cortical_areas.get(dst_area_id) {
if let Some(mem_props) = extract_memory_properties(&dst_area.properties) {
let upstream_areas =
self.get_episodic_memory_upstream_cortical_areas(dst_area_id);
debug!(
target: "feagi-bdu",
"Registering memory area idx={} id={} upstream={} depth={}",
dst_area.cortical_idx,
dst_area_id.as_base_64(),
upstream_areas.len(),
mem_props.temporal_depth
);
if let Some(ref npu_arc) = self.npu {
if let Ok(mut npu) = npu_arc.lock() {
let existing_configs = npu.get_all_fire_ledger_configs();
for &upstream_idx in &upstream_areas {
let existing = existing_configs
.iter()
.find(|(idx, _)| *idx == upstream_idx)
.map(|(_, w)| *w)
.unwrap_or(0);
let desired = mem_props.temporal_depth as usize;
let resolved = existing.max(desired);
if resolved != existing {
if let Err(e) =
npu.configure_fire_ledger_window(upstream_idx, resolved)
{
warn!(
target: "feagi-bdu",
"Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
upstream_idx,
resolved,
e
);
}
}
}
} else {
warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
}
}
if let Ok(exec) = executor.lock() {
use feagi_npu_plasticity::{
MemoryNeuronLifecycleConfig, PlasticityExecutor,
};
let lifecycle_config = MemoryNeuronLifecycleConfig {
initial_lifespan: mem_props.init_lifespan,
lifespan_growth_rate: mem_props.lifespan_growth_rate,
longterm_threshold: mem_props.longterm_threshold,
max_reactivations: 1000,
};
exec.register_memory_area(
dst_area.cortical_idx,
dst_area_id.as_base_64(),
mem_props.temporal_depth,
upstream_areas.clone(),
Some(lifecycle_config),
mem_props.mp_learning_enabled,
);
self.configure_memory_scan_on_executor(&*exec, dst_area_id);
} else {
warn!(target: "feagi-bdu", "Failed to lock PlasticityExecutor");
}
} else {
debug!(
target: "feagi-bdu",
"Skipping plasticity registration: no memory properties for area {}",
dst_area_id.as_base_64()
);
}
} else {
warn!(target: "feagi-bdu", "Destination area {} not found in cortical_areas", dst_area_id.as_base_64());
}
} else {
warn!(
target: "feagi-bdu",
"PlasticityExecutor not available; memory area {} not registered",
dst_area_id.as_base_64()
);
}
#[cfg(not(feature = "plasticity"))]
{
info!(target: "feagi-bdu", "Plasticity feature disabled at compile time");
}
} else {
self.remove_upstream_area(dst_area_id, src_idx_for_upstream);
let destination_is_memory = self
.cortical_areas
.get(dst_area_id)
.is_some_and(|area| matches!(area.cortical_type, CorticalAreaType::Memory(_)));
if destination_is_memory {
self.teardown_owned_memory_twin_for_mapping(dst_area_id, src_area_id)?;
}
let mut npu = npu_arc.lock().unwrap();
let _was_registered = npu.unregister_stdp_mapping(src_idx, dst_idx);
}
debug!(
target: "feagi-bdu",
"Created {} new synapses: {} -> {}",
synapse_count,
src_area_id,
dst_area_id
);
if pruned_synapse_count > 0 || synapse_count == 0 {
let mut npu = npu_arc.lock().unwrap();
npu.rebuild_synapse_index();
info!(
target: "feagi-bdu",
"Rebuilt synapse index after regenerating {} -> {} (pruned={}, created={})",
src_area_id,
dst_area_id,
pruned_synapse_count,
synapse_count
);
} else {
debug!(
target: "feagi-bdu",
"Skipped synapse index rebuild for mapping {} -> {} (created={}, pruned=0; index rebuilt during synaptogenesis)",
src_area_id,
dst_area_id,
synapse_count
);
}
{
let npu = npu_arc.lock().unwrap();
let fresh_count = npu.get_synapse_count();
self.cached_synapse_count
.store(fresh_count, Ordering::Relaxed);
}
Ok(synapse_count)
}
fn json_number_as_i64_for_stdp(v: &serde_json::Value) -> Option<i64> {
v.as_i64().or_else(|| v.as_f64().map(|f| f as i64))
}
fn json_number_as_usize_for_stdp(v: &serde_json::Value) -> Option<usize> {
v.as_u64()
.map(|n| n as usize)
.or_else(|| v.as_f64().map(|f| f as usize))
}
#[allow(clippy::too_many_arguments)]
fn register_stdp_mapping_for_rule(
npu: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
src_cortical_idx: u32,
dst_cortical_idx: u32,
rule_obj: &serde_json::Map<String, serde_json::Value>,
bidirectional_stdp: bool,
synapse_psp: f32,
synapse_type: feagi_npu_neural::SynapseType,
) -> BduResult<()> {
let plasticity_window = rule_obj
.get("plasticity_window")
.and_then(Self::json_number_as_usize_for_stdp)
.ok_or_else(|| {
BduError::Internal(format!(
"Missing plasticity_window in plastic mapping rule {} -> {}",
src_area_id, dst_area_id
))
})?;
let plasticity_constant = rule_obj
.get("plasticity_constant")
.and_then(Self::json_number_as_i64_for_stdp)
.ok_or_else(|| {
BduError::Internal(format!(
"Missing plasticity_constant in plastic mapping rule {} -> {}",
src_area_id, dst_area_id
))
})?;
let ltp_i64 = rule_obj
.get("ltp_multiplier")
.and_then(Self::json_number_as_i64_for_stdp)
.ok_or_else(|| {
BduError::Internal(format!(
"Missing ltp_multiplier in plastic mapping rule {} -> {}",
src_area_id, dst_area_id
))
})?;
let ltp_multiplier = i8::try_from(ltp_i64).map_err(|_| {
BduError::Internal(format!(
"ltp_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
i8::MIN,
i8::MAX,
ltp_i64,
src_area_id,
dst_area_id
))
})?;
let ltd_i64 = rule_obj
.get("ltd_multiplier")
.and_then(Self::json_number_as_i64_for_stdp)
.ok_or_else(|| {
BduError::Internal(format!(
"Missing ltd_multiplier in plastic mapping rule {} -> {}",
src_area_id, dst_area_id
))
})?;
let ltd_multiplier = i8::try_from(ltd_i64).map_err(|_| {
BduError::Internal(format!(
"ltd_multiplier must fit in i8 range {}..={} (got {}) on mapping {} -> {}",
i8::MIN,
i8::MAX,
ltd_i64,
src_area_id,
dst_area_id
))
})?;
let plasticity_mode = match rule_obj.get("plasticity_mode").and_then(|v| v.as_str()) {
Some(s) if s.eq_ignore_ascii_case("rstdp") || s.eq_ignore_ascii_case("r-stdp") => {
feagi_npu_burst_engine::npu::PlasticityMode::RStdp
}
Some(s) if s.eq_ignore_ascii_case("stdp") => {
feagi_npu_burst_engine::npu::PlasticityMode::Stdp
}
Some(s) if s.eq_ignore_ascii_case("off") => {
feagi_npu_burst_engine::npu::PlasticityMode::Off
}
Some(other) => {
return Err(BduError::Internal(format!(
"Unknown plasticity_mode '{}' in mapping rule {} -> {}",
other, src_area_id, dst_area_id
)));
}
None => feagi_npu_burst_engine::npu::PlasticityMode::Stdp,
};
let eligibility_decay_bursts = rule_obj
.get("eligibility_decay_bursts")
.and_then(|v| v.as_u64())
.map(|n| n as u32)
.unwrap_or(0);
let reward_source_area_id = rule_obj
.get("reward_source_area")
.and_then(|v| v.as_str())
.map(str::to_string);
let punishment_source_area_id = rule_obj
.get("punishment_source_area")
.and_then(|v| v.as_str())
.map(str::to_string);
let max_weight_provided = rule_obj.get("max_weight").is_some()
&& !rule_obj
.get("max_weight")
.map(|v| v.is_null())
.unwrap_or(true);
let max_weight: f32 = if max_weight_provided {
let raw = rule_obj
.get("max_weight")
.and_then(|v| v.as_f64())
.ok_or_else(|| {
BduError::Internal(format!(
"max_weight must be a number on mapping {} -> {}",
src_area_id, dst_area_id
))
})?;
if raw.is_nan() || raw <= 0.0 {
return Err(BduError::Internal(format!(
"max_weight must be strictly positive (got {}) on mapping {} -> {}",
raw, src_area_id, dst_area_id
)));
}
raw as f32
} else {
f32::INFINITY
};
let plasticity_eta_provided = rule_obj.get("plasticity_eta").is_some()
&& !rule_obj
.get("plasticity_eta")
.map(|v| v.is_null())
.unwrap_or(true);
let plasticity_eta: f32 = if plasticity_eta_provided {
let raw = rule_obj
.get("plasticity_eta")
.and_then(|v| v.as_f64())
.ok_or_else(|| {
BduError::Internal(format!(
"plasticity_eta must be a number on mapping {} -> {}",
src_area_id, dst_area_id
))
})?;
if raw.is_nan() || raw <= 0.0 || !raw.is_finite() {
return Err(BduError::Internal(format!(
"plasticity_eta must be finite and strictly positive (got {}) on mapping {} -> {}",
raw, src_area_id, dst_area_id
)));
}
raw as f32
} else {
1.0
};
if !matches!(
plasticity_mode,
feagi_npu_burst_engine::npu::PlasticityMode::RStdp
) && (reward_source_area_id.is_some()
|| punishment_source_area_id.is_some()
|| eligibility_decay_bursts != 0)
{
return Err(BduError::Internal(format!(
"R-STDP fields (reward_source_area / punishment_source_area / eligibility_decay_bursts) \
only valid when plasticity_mode='rstdp' on mapping {} -> {}",
src_area_id, dst_area_id
)));
}
if matches!(
plasticity_mode,
feagi_npu_burst_engine::npu::PlasticityMode::Off
) && max_weight_provided
{
return Err(BduError::Internal(format!(
"max_weight is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
src_area_id, dst_area_id
)));
}
if matches!(
plasticity_mode,
feagi_npu_burst_engine::npu::PlasticityMode::Off
) && plasticity_eta_provided
{
return Err(BduError::Internal(format!(
"plasticity_eta is only valid when plasticity_mode is 'stdp' or 'rstdp' (got off) on mapping {} -> {}",
src_area_id, dst_area_id
)));
}
trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: attempting NPU lock");
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
trace!(target: "feagi-bdu", "[LOCK-TRACE] create_neurons_for_area: acquired NPU lock");
let resolve_optional_area =
|label: &str, name_opt: &Option<String>| -> BduResult<Option<u32>> {
let Some(name) = name_opt else {
return Ok(None);
};
match npu_lock.get_cortical_area_id(name.as_str()) {
Some(idx) => Ok(Some(idx)),
None => Err(BduError::Internal(format!(
"Unknown {} cortical area '{}' on R-STDP mapping {} -> {}",
label, name, src_area_id, dst_area_id
))),
}
};
let reward_source_area =
resolve_optional_area("reward_source_area", &reward_source_area_id)?;
let punishment_source_area =
resolve_optional_area("punishment_source_area", &punishment_source_area_id)?;
if matches!(
plasticity_mode,
feagi_npu_burst_engine::npu::PlasticityMode::Off
) {
return Ok(());
}
let params = feagi_npu_burst_engine::npu::StdpMappingParams {
plasticity_window,
plasticity_constant,
ltp_multiplier,
ltd_multiplier,
bidirectional_stdp,
associative_memory_mapping: rule_obj
.get("morphology_id")
.and_then(|value| value.as_str())
== Some("associative_memory"),
synapse_psp,
synapse_type,
plasticity_mode,
eligibility_decay_bursts,
reward_source_area,
punishment_source_area,
max_weight,
plasticity_eta,
};
npu_lock
.register_stdp_mapping(src_cortical_idx, dst_cortical_idx, params)
.map_err(|e| {
BduError::Internal(format!(
"Failed to register STDP mapping {} -> {}: {}",
src_area_id, dst_area_id, e
))
})?;
let mut areas_to_track: Vec<(u32, usize)> = vec![
(src_cortical_idx, plasticity_window),
(dst_cortical_idx, plasticity_window),
];
if let Some(area) = reward_source_area {
areas_to_track.push((area, 1));
}
if let Some(area) = punishment_source_area {
areas_to_track.push((area, 1));
}
let existing_configs = npu_lock.get_all_fire_ledger_configs();
for (area_idx, required_depth) in areas_to_track {
let existing = existing_configs
.iter()
.find(|(idx, _)| *idx == area_idx)
.map(|(_, w)| *w)
.unwrap_or(0);
let resolved = existing.max(required_depth);
if resolved != existing {
npu_lock
.configure_fire_ledger_window(area_idx, resolved)
.map_err(|e| {
BduError::Internal(format!(
"Failed to configure FireLedger window for area idx={} (requested={}): {}",
area_idx, resolved, e
))
})?;
}
}
Ok(())
}
fn resolve_synapse_params_for_rule(
&self,
src_area_id: &CorticalID,
rule: &serde_json::Value,
) -> BduResult<(f32, f32, feagi_npu_neural::SynapseType, u8)> {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!("Source area not found: {}", src_area_id))
})?;
let (weight, synapse_type) = {
let parse_f64 = |v: &serde_json::Value| -> Option<f64> {
if let Some(i) = v.as_i64() {
return Some(i as f64);
}
v.as_f64()
};
let mult: f64 = if let Some(obj) = rule.as_object() {
obj.get("postSynapticCurrent_multiplier")
.and_then(parse_f64)
.unwrap_or(1.0)
} else if let Some(arr) = rule.as_array() {
arr.get(2).and_then(parse_f64).unwrap_or(1.0)
} else {
128.0
};
if mult < 0.0 {
(mult.abs() as f32, feagi_npu_neural::SynapseType::Inhibitory)
} else {
(mult as f32, feagi_npu_neural::SynapseType::Excitatory)
}
};
use crate::models::cortical_area::CorticalAreaExt;
let psp_f32 = src_area.postsynaptic_current();
let delay_bursts: u8 = if let Some(obj) = rule.as_object() {
obj.get("synaptic_delay_bursts")
.and_then(|v| v.as_u64())
.map(|d| u8::try_from(d).unwrap_or(1))
.unwrap_or(1)
} else if let Some(arr) = rule.as_array() {
arr.get(8)
.and_then(|v| v.as_u64())
.map(|d| u8::try_from(d).unwrap_or(1))
.unwrap_or(1)
} else {
1
};
if delay_bursts < 1 {
return Err(crate::types::BduError::Internal(format!(
"synaptic_delay_bursts must be >= 1 (src area {})",
src_area_id.as_base_64()
)));
}
tracing::debug!(
target: "feagi-bdu",
"Resolved synapse params src={} weight={} psp={} type={:?} delay_bursts={}",
src_area_id.as_base_64(),
weight,
psp_f32,
synapse_type,
delay_bursts
);
Ok((weight, psp_f32, synapse_type, delay_bursts))
}
fn apply_cortical_mapping_for_pair(
&mut self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
) -> BduResult<usize> {
let rules = {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let Some(mapping_dst) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
return Ok(0);
};
let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id)
else {
return Ok(0);
};
rules.clone()
};
if rules.is_empty() {
return Ok(0);
}
let src_cortical_idx = *self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!("No index for {}", src_area_id))
})?;
let dst_cortical_idx = *self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!("No index for {}", dst_area_id))
})?;
let npu_arc = self
.npu
.as_ref()
.ok_or_else(|| crate::types::BduError::Internal("NPU not connected".to_string()))?
.clone();
tracing::debug!(
target: "feagi-bdu",
"Applying {} mapping rule(s) for {} -> {}",
rules.len(),
src_area_id,
dst_area_id
);
let mut total_synapses = 0;
for rule in &rules {
let morphology_id = if let Some(rule_obj) = rule.as_object() {
rule_obj
.get("morphology_id")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string()
} else if let Some(rule_arr) = rule.as_array() {
rule_arr
.first()
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string()
} else {
"unknown".to_string()
};
let rule_keys: Vec<String> = rule
.as_object()
.map(|obj| obj.keys().cloned().collect())
.unwrap_or_default();
let mut plasticity_flag = rule
.as_object()
.and_then(|obj| obj.get("plasticity_flag"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
if morphology_id == "associative_memory" {
plasticity_flag = true;
}
if plasticity_flag {
let Some(rule_obj) = rule.as_object() else {
return Err(crate::types::BduError::InvalidMorphology(
"Plasticity mapping rule must be an object format".to_string(),
));
};
let (_weight, psp, synapse_type, _delay_bursts) =
self.resolve_synapse_params_for_rule(src_area_id, rule)?;
let bidirectional_stdp = false;
if let Err(e) = Self::register_stdp_mapping_for_rule(
&npu_arc,
src_area_id,
dst_area_id,
src_cortical_idx,
dst_cortical_idx,
rule_obj,
bidirectional_stdp,
psp,
synapse_type,
) {
tracing::error!(
target: "feagi-bdu",
"STDP mapping registration failed for {} -> {} (morphology={}, keys={:?}): {}",
src_area_id,
dst_area_id,
morphology_id,
rule_keys,
e
);
return Err(e);
}
}
if let Some(gate_area_str) = rule
.as_object()
.and_then(|obj| obj.get("gate_source_area"))
.and_then(|v| v.as_str())
{
let gate_area_id = CorticalID::try_from_base_64(gate_area_str).map_err(|_| {
crate::types::BduError::Internal(format!(
"Invalid gate_source_area '{}' on mapping {} -> {}",
gate_area_str, src_area_id, dst_area_id
))
})?;
let gate_cortical_idx =
self.cortical_id_to_idx.get(&gate_area_id).ok_or_else(|| {
crate::types::BduError::Internal(format!(
"Unknown gate_source_area '{}' on mapping {} -> {}",
gate_area_str, src_area_id, dst_area_id
))
})?;
let mut npu_lock = npu_arc.lock().map_err(|_| {
crate::types::BduError::Internal(
"Failed to acquire NPU lock for gate registration".to_string(),
)
})?;
if let Err(e) = npu_lock.register_gate_mapping(
src_cortical_idx,
dst_cortical_idx,
*gate_cortical_idx,
) {
tracing::error!(
target: "feagi-bdu",
"Gate mapping registration failed for {} -> {} (gate={}): {}",
src_area_id,
dst_area_id,
gate_area_str,
e
);
return Err(crate::types::BduError::Internal(format!(
"Gate registration failed: {}",
e
)));
}
}
let synapse_count = match self.apply_single_morphology_rule(
src_area_id,
dst_area_id,
rule,
) {
Ok(count) => count,
Err(e) => {
tracing::error!(
target: "feagi-bdu",
"Mapping rule application failed for {} -> {} (morphology={}, keys={:?}): {}",
src_area_id,
dst_area_id,
morphology_id,
rule_keys,
e
);
return Err(e);
}
};
total_synapses += synapse_count;
tracing::debug!(
target: "feagi-bdu",
"Rule {} created {} synapses for {} -> {}",
morphology_id,
synapse_count,
src_area_id,
dst_area_id
);
}
Ok(total_synapses)
}
#[allow(clippy::too_many_arguments)]
fn apply_function_morphology(
&self,
morphology_id: &str,
rule: &serde_json::Value,
npu_arc: &Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
npu: &mut feagi_npu_burst_engine::DynamicNPU,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
src_idx: u32,
dst_idx: u32,
weight: f32,
psp: f32,
synapse_attractivity: u8,
synapse_type: feagi_npu_neural::SynapseType,
delay_bursts: u8,
) -> BduResult<usize> {
match morphology_id {
"projector" | "transpose_xy" | "transpose_yz" | "transpose_xz" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let transpose = match morphology_id {
"transpose_xy" => Some((1, 0, 2)),
"transpose_yz" => Some((0, 2, 1)),
"transpose_xz" => Some((2, 1, 0)),
_ => None,
};
use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
let count = apply_projector_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
transpose,
None, weight,
psp,
synapse_attractivity,
synapse_type,
0,
delay_bursts,
)?;
npu.rebuild_synapse_index();
Ok(count as usize)
}
"centered_projector" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let count = crate::connectivity::core_morphologies::apply_centered_projector_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
"episodic_memory" => {
use tracing::trace;
trace!(
target: "feagi-bdu",
"Episodic memory morphology: {} -> {} (no physical synapses, plasticity-driven)",
src_idx, dst_idx
);
Ok(0)
}
"episodic_scan" => {
use tracing::trace;
trace!(
target: "feagi-bdu",
"Episodic scan morphology: {} -> {} (no physical synapses, plasticity-driven)",
src_idx, dst_idx
);
Ok(0)
}
"memory_replay" => {
use tracing::trace;
trace!(
target: "feagi-bdu",
"Memory replay morphology: {} -> {} (no physical synapses)",
src_idx, dst_idx
);
Ok(0)
}
"associative_memory" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
if matches!(src_area.cortical_type, CorticalAreaType::Memory(_))
&& matches!(dst_area.cortical_type, CorticalAreaType::Memory(_))
{
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
use crate::connectivity::core_morphologies::apply_projector_morphology_with_dimensions;
let count = apply_projector_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
None,
None,
weight,
psp,
synapse_attractivity,
synapse_type,
SYNAPSE_EDGE_ASSOCIATIVE_MEMORY,
delay_bursts,
)?;
npu.rebuild_synapse_index();
Ok(count as usize)
} else {
Ok(0)
}
}
"block_to_block" => {
tracing::warn!(
target: "feagi-bdu",
"🔍 DEBUG apply_function_morphology: block_to_block case reached with src_idx={}, dst_idx={}",
src_idx, dst_idx
);
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let scalar = if let Some(obj) = rule.as_object() {
if let Some(scalar_arr) =
obj.get("morphology_scalar").and_then(|v| v.as_array())
{
scalar_arr.first().and_then(|v| v.as_i64()).unwrap_or(1) as u32
} else {
1 }
} else if let Some(arr) = rule.as_array() {
arr.get(1).and_then(|v| v.as_i64()).unwrap_or(1) as u32
} else {
1 };
let estimated_neurons = src_dimensions.0 * src_dimensions.1 * src_dimensions.2;
let count = if estimated_neurons > 100_000 {
let _ = npu;
crate::connectivity::synaptogenesis::apply_block_connection_morphology_batched(
npu_arc,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
scalar, weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)? as usize
} else {
tracing::warn!(
target: "feagi-bdu",
"🔍 DEBUG connectome_manager: Calling apply_block_connection_morphology with src_idx={}, dst_idx={}, src_dim={:?}, dst_dim={:?}",
src_idx, dst_idx, src_dimensions, dst_dimensions
);
let count =
crate::connectivity::synaptogenesis::apply_block_connection_morphology(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
scalar, weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)? as usize;
tracing::warn!(
target: "feagi-bdu",
"🔍 DEBUG connectome_manager: apply_block_connection_morphology returned count={}",
count
);
if count > 0 {
npu.rebuild_synapse_index();
}
count
};
if count > 0 && estimated_neurons > 100_000 {
let mut npu_lock = npu_arc.lock().unwrap();
npu_lock.rebuild_synapse_index();
}
Ok(count)
}
"bitmask_encoder_x" | "bitmask_encoder_y" | "bitmask_encoder_z"
| "bitmask_decoder_x" | "bitmask_decoder_y" | "bitmask_decoder_z" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let (axis, mode) = match morphology_id {
"bitmask_encoder_x" => (
crate::connectivity::core_morphologies::BitmaskAxis::X,
crate::connectivity::core_morphologies::BitmaskMode::Encoder,
),
"bitmask_encoder_y" => (
crate::connectivity::core_morphologies::BitmaskAxis::Y,
crate::connectivity::core_morphologies::BitmaskMode::Encoder,
),
"bitmask_encoder_z" => (
crate::connectivity::core_morphologies::BitmaskAxis::Z,
crate::connectivity::core_morphologies::BitmaskMode::Encoder,
),
"bitmask_decoder_x" => (
crate::connectivity::core_morphologies::BitmaskAxis::X,
crate::connectivity::core_morphologies::BitmaskMode::Decoder,
),
"bitmask_decoder_y" => (
crate::connectivity::core_morphologies::BitmaskAxis::Y,
crate::connectivity::core_morphologies::BitmaskMode::Decoder,
),
"bitmask_decoder_z" => (
crate::connectivity::core_morphologies::BitmaskAxis::Z,
crate::connectivity::core_morphologies::BitmaskMode::Decoder,
),
_ => unreachable!("matched bitmask morphology above"),
};
let count =
crate::connectivity::core_morphologies::apply_bitmask_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
axis,
mode,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
"sweeper" => {
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let count =
crate::connectivity::core_morphologies::apply_sweeper_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
dst_dimensions,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
"last_to_first" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let count = crate::connectivity::core_morphologies::apply_last_to_first_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
"first_to_last" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let count = crate::connectivity::core_morphologies::apply_first_to_last_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
"rotator_z" => {
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let count = crate::connectivity::core_morphologies::apply_rotator_z_morphology_with_dimensions(
npu,
src_idx,
dst_idx,
src_dimensions,
dst_dimensions,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
_ => {
use tracing::debug;
debug!(target: "feagi-bdu", "Function morphology {} not yet implemented", morphology_id);
Ok(0)
}
}
}
fn apply_single_morphology_rule(
&mut self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
rule: &serde_json::Value,
) -> BduResult<usize> {
let morphology_id = if let Some(arr) = rule.as_array() {
arr.first().and_then(|v| v.as_str()).unwrap_or("")
} else if let Some(obj) = rule.as_object() {
obj.get("morphology_id")
.and_then(|v| v.as_str())
.unwrap_or("")
} else {
return Ok(0);
};
if morphology_id.is_empty() {
return Ok(0);
}
let morphology = self.morphology_registry.get(morphology_id).ok_or_else(|| {
crate::types::BduError::InvalidMorphology(format!(
"Morphology not found: {}",
morphology_id
))
})?;
let src_idx = self.cortical_id_to_idx.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area ID not found: {}",
src_area_id
))
})?;
let dst_idx = self.cortical_id_to_idx.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area ID not found: {}",
dst_area_id
))
})?;
if let Some(ref npu_arc) = self.npu {
let lock_start = std::time::Instant::now();
let mut npu = npu_arc.lock().unwrap();
let lock_wait = lock_start.elapsed();
tracing::debug!(
target: "feagi-bdu",
"[NPU-LOCK] synaptogenesis lock wait {:.2}ms for {} -> {} (morphology={})",
lock_wait.as_secs_f64() * 1000.0,
src_area_id,
dst_area_id,
morphology_id
);
let (weight, psp, synapse_type, delay_bursts) =
self.resolve_synapse_params_for_rule(src_area_id, rule)?;
let synapse_attractivity = if let Some(obj) = rule.as_object() {
obj.get("synapse_attractivity")
.and_then(|v| v.as_u64())
.unwrap_or(100) as u8
} else {
100 };
match morphology.morphology_type {
feagi_evolutionary::MorphologyType::Functions => {
tracing::debug!(
target: "feagi-bdu",
"apply_single_morphology_rule: Functions type, morphology_id={}, calling apply_function_morphology",
morphology_id
);
self.apply_function_morphology(
morphology_id,
rule,
npu_arc,
&mut npu,
src_area_id,
dst_area_id,
*src_idx,
*dst_idx,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)
}
feagi_evolutionary::MorphologyType::Vectors => {
use crate::connectivity::synaptogenesis::apply_vectors_morphology_with_dimensions;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
if let feagi_evolutionary::MorphologyParameters::Vectors { ref vectors } =
morphology.parameters
{
let vectors_tuples: Vec<(i32, i32, i32)> =
vectors.iter().map(|v| (v[0], v[1], v[2])).collect();
let count = apply_vectors_morphology_with_dimensions(
&mut npu,
*src_idx,
*dst_idx,
vectors_tuples,
dst_dimensions,
weight, psp, synapse_attractivity, synapse_type,
delay_bursts,
)?;
npu.rebuild_synapse_index();
Ok(count as usize)
} else {
Ok(0)
}
}
feagi_evolutionary::MorphologyType::Patterns => {
use crate::connectivity::core_morphologies::apply_patterns_morphology;
use crate::connectivity::rules::patterns::{
Pattern3D, PatternElement as RulePatternElement,
};
use feagi_evolutionary::PatternElement as EvoPatternElement;
let feagi_evolutionary::MorphologyParameters::Patterns { ref patterns } =
morphology.parameters
else {
return Ok(0);
};
let convert_element =
|element: &EvoPatternElement|
-> crate::types::BduResult<RulePatternElement> {
match element {
EvoPatternElement::Value(value) => {
if *value < 0 {
return Err(crate::types::BduError::InvalidMorphology(
format!(
"Pattern morphology {} contains negative voxel coordinate {}",
morphology_id, value
),
));
}
Ok(RulePatternElement::Exact(*value))
}
EvoPatternElement::Wildcard => Ok(RulePatternElement::Wildcard),
EvoPatternElement::Skip => Ok(RulePatternElement::Skip),
EvoPatternElement::Exclude => Ok(RulePatternElement::Exclude),
EvoPatternElement::DirectionPositive => {
Ok(RulePatternElement::DirectionExclusive(
crate::connectivity::rules::patterns::Direction::Positive,
))
}
EvoPatternElement::DirectionNegative => {
Ok(RulePatternElement::DirectionExclusive(
crate::connectivity::rules::patterns::Direction::Negative,
))
}
EvoPatternElement::DirectionPositiveInclusive => {
Ok(RulePatternElement::DirectionInclusive(
crate::connectivity::rules::patterns::Direction::Positive,
))
}
EvoPatternElement::DirectionNegativeInclusive => {
Ok(RulePatternElement::DirectionInclusive(
crate::connectivity::rules::patterns::Direction::Negative,
))
}
EvoPatternElement::Offset(off) => {
Ok(RulePatternElement::Offset(*off))
}
EvoPatternElement::Range(lo, hi) => {
Ok(RulePatternElement::Range(*lo, *hi))
}
EvoPatternElement::AbsoluteRange(lo, hi) => {
Ok(RulePatternElement::AbsoluteRange(*lo, *hi))
}
}
};
let mut converted_patterns = Vec::with_capacity(patterns.len());
for pattern_pair in patterns {
if pattern_pair.len() != 2 {
return Err(crate::types::BduError::InvalidMorphology(format!(
"Pattern morphology {} must contain [src, dst] pairs",
morphology_id
)));
}
let src_pattern = &pattern_pair[0];
let dst_pattern = &pattern_pair[1];
if src_pattern.len() != 3 || dst_pattern.len() != 3 {
return Err(crate::types::BduError::InvalidMorphology(format!(
"Pattern morphology {} requires 3-axis patterns",
morphology_id
)));
}
let src: Pattern3D = (
convert_element(&src_pattern[0])?,
convert_element(&src_pattern[1])?,
convert_element(&src_pattern[2])?,
);
let dst: Pattern3D = (
convert_element(&dst_pattern[0])?,
convert_element(&dst_pattern[1])?,
convert_element(&dst_pattern[2])?,
);
converted_patterns.push((src, dst));
}
let count = apply_patterns_morphology(
&mut npu,
*src_idx,
*dst_idx,
converted_patterns,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
feagi_evolutionary::MorphologyType::Composite => {
let feagi_evolutionary::MorphologyParameters::Composite { .. } =
morphology.parameters
else {
return Ok(0);
};
if morphology_id != "tile" {
use tracing::debug;
debug!(
target: "feagi-bdu",
"Composite morphology {} not yet implemented",
morphology_id
);
return Ok(0);
}
let src_area = self.cortical_areas.get(src_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Source area not found: {}",
src_area_id
))
})?;
let dst_area = self.cortical_areas.get(dst_area_id).ok_or_else(|| {
crate::types::BduError::InvalidArea(format!(
"Destination area not found: {}",
dst_area_id
))
})?;
let src_dimensions = (
src_area.dimensions.width as usize,
src_area.dimensions.height as usize,
src_area.dimensions.depth as usize,
);
let dst_dimensions = (
dst_area.dimensions.width as usize,
dst_area.dimensions.height as usize,
dst_area.dimensions.depth as usize,
);
let count =
crate::connectivity::core_morphologies::apply_tile_morphology_with_dimensions(
&mut npu,
*src_idx,
*dst_idx,
src_dimensions,
dst_dimensions,
weight,
psp,
synapse_attractivity,
synapse_type,
delay_bursts,
)?;
if count > 0 {
npu.rebuild_synapse_index();
}
Ok(count as usize)
}
}
} else {
Ok(0) }
}
pub fn set_npu(
&mut self,
npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
) {
self.npu = Some(Arc::clone(&npu));
info!(target: "feagi-bdu","🔗 ConnectomeManager: NPU reference set");
#[cfg(not(feature = "wasm"))]
{
use feagi_state_manager::StateManager;
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.set_neuron_capacity(self.config.max_neurons as u32);
core_state.set_synapse_capacity(self.config.max_synapses as u32);
info!(
target: "feagi-bdu",
"📊 Updated State Manager with capacity: {} neurons, {} synapses",
self.config.max_neurons, self.config.max_synapses
);
}
let existing_area_count = self.cortical_id_to_idx.len();
if existing_area_count > 0 {
match npu.lock() {
Ok(mut npu_lock) => {
for (cortical_id, cortical_idx) in self.cortical_id_to_idx.iter() {
npu_lock.register_cortical_area(*cortical_idx, cortical_id.as_base_64());
}
info!(
target: "feagi-bdu",
"🔁 Backfilled {} cortical area registrations into NPU",
existing_area_count
);
}
Err(e) => {
warn!(
target: "feagi-bdu",
"⚠️ Failed to lock NPU for cortical area backfill registration: {}",
e
);
}
}
}
self.update_all_cached_stats();
info!(target: "feagi-bdu","📊 Initialized cached stats: {} neurons, {} synapses",
self.get_neuron_count(), self.get_synapse_count());
}
pub fn has_npu(&self) -> bool {
self.npu.is_some()
}
pub fn get_npu(
&self,
) -> Option<&Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>>
{
self.npu.as_ref()
}
pub fn rebind_npu_runtime_after_connectome_apply(&mut self) -> BduResult<()> {
#[cfg(feature = "plasticity")]
if let Some(executor) = self.plasticity_executor.as_ref() {
executor
.lock()
.map_err(|_| {
BduError::Internal(
"Failed to lock PlasticityExecutor for registration reset".to_string(),
)
})?
.clear_memory_area_registrations()
.map_err(BduError::Internal)?;
}
self.refresh_all_upstream_cortical_areas_from_mappings();
self.rebuild_memory_twin_mappings()?;
self.rebind_memory_twin_mappings_to_npu()?;
self.rebind_stdp_mappings_to_npu()?;
#[cfg(feature = "plasticity")]
self.reregister_memory_areas_with_plasticity()?;
Ok(())
}
pub fn refresh_all_upstream_cortical_areas_from_mappings(&mut self) {
let area_ids: Vec<CorticalID> = self.cortical_areas.keys().copied().collect();
for area_id in area_ids {
self.refresh_upstream_cortical_areas_from_mappings(&area_id);
}
}
pub fn rebuild_memory_twin_mappings(&mut self) -> BduResult<usize> {
let jobs = self.collect_memory_twin_rebuild_jobs()?;
let mut restored = 0usize;
for (memory_id, upstream_id, known_twin) in jobs {
match known_twin {
Some(twin_id) => {
self.restore_memory_twin_index(&memory_id, &upstream_id, &twin_id)?;
restored += 1;
}
None => {
self.ensure_memory_twin_area(&memory_id, &upstream_id)?;
restored += 1;
}
}
}
if restored > 0 {
info!(
target: "feagi-bdu",
"Rebuilt {} memory twin mapping(s) after connectome apply",
restored
);
self.refresh_cortical_mappings_hash();
}
self.rebuild_scan_twin_mappings()?;
Ok(restored)
}
fn rebuild_scan_twin_mappings(&mut self) -> BduResult<usize> {
let mut pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
for (src_id, src_area) in &self.cortical_areas {
if Self::area_is_memory(src_area) {
continue;
}
let Some(dstmap) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|value| value.as_object())
else {
continue;
};
for (dst_b64, rules) in dstmap {
if !Self::mapping_rules_use_morphology(rules, "episodic_scan") {
continue;
}
let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
continue;
};
pairs.push((memory_id, *src_id));
}
}
let mut restored = 0usize;
for (memory_id, field_id) in pairs {
if self.ensure_scan_twin_area(&memory_id, &field_id).is_ok() {
restored += 1;
}
}
Ok(restored)
}
fn collect_memory_twin_rebuild_jobs(
&self,
) -> BduResult<Vec<(CorticalID, CorticalID, Option<CorticalID>)>> {
let mut jobs: Vec<(CorticalID, CorticalID, Option<CorticalID>)> = Vec::new();
let mut seen: HashSet<(CorticalID, CorticalID)> = HashSet::new();
for (memory_id, memory_area) in &self.cortical_areas {
if !Self::area_is_memory(memory_area) {
continue;
}
let Some(dstmap) = memory_area
.properties
.get("cortical_mapping_dst")
.and_then(|value| value.as_object())
else {
continue;
};
for (dst_b64, rules) in dstmap {
if !Self::mapping_rules_use_morphology(rules, "memory_replay") {
continue;
}
let twin_id = match CorticalID::try_from_base_64(dst_b64) {
Ok(id) => id,
Err(_) => continue,
};
let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
continue;
};
let Some(upstream_b64) = twin_area
.properties
.get("memory_twin_of")
.and_then(|value| value.as_str())
else {
continue;
};
let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
BduError::InvalidArea(format!(
"Invalid memory_twin_of '{}' on {}: {}",
upstream_b64,
twin_id.as_base_64(),
error
))
})?;
if seen.insert((*memory_id, upstream_id)) {
jobs.push((*memory_id, upstream_id, Some(twin_id)));
}
}
}
for (twin_id, twin_area) in &self.cortical_areas {
let Some(upstream_b64) = twin_area
.properties
.get("memory_twin_of")
.and_then(|value| value.as_str())
else {
continue;
};
let Some(memory_b64) = twin_area
.properties
.get("memory_twin_for")
.and_then(|value| value.as_str())
else {
continue;
};
let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
BduError::InvalidArea(format!(
"Invalid memory_twin_of '{}' on {}: {}",
upstream_b64,
twin_id.as_base_64(),
error
))
})?;
let memory_id = CorticalID::try_from_base_64(memory_b64).map_err(|error| {
BduError::InvalidArea(format!(
"Invalid memory_twin_for '{}' on {}: {}",
memory_b64,
twin_id.as_base_64(),
error
))
})?;
if !self.cortical_areas.contains_key(&memory_id)
|| !self.cortical_areas.contains_key(&upstream_id)
{
continue;
}
if seen.insert((memory_id, upstream_id)) {
jobs.push((memory_id, upstream_id, Some(*twin_id)));
}
}
let mut episodic_pairs: Vec<(CorticalID, CorticalID)> = Vec::new();
for (src_id, src_area) in &self.cortical_areas {
if Self::area_is_memory(src_area) {
continue;
}
let Some(dstmap) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|value| value.as_object())
else {
continue;
};
for (dst_b64, rules) in dstmap {
if !Self::mapping_rules_use_morphology(rules, "episodic_memory") {
continue;
}
let Ok(memory_id) = CorticalID::try_from_base_64(dst_b64) else {
continue;
};
let Some(memory_area) = self.cortical_areas.get(&memory_id) else {
continue;
};
if !Self::area_is_memory(memory_area) {
continue;
}
episodic_pairs.push((memory_id, *src_id));
}
}
for (memory_id, upstream_id) in episodic_pairs {
if !seen.insert((memory_id, upstream_id)) {
continue;
}
let indexed_twin = self
.cortical_areas
.get(&memory_id)
.and_then(|area| area.properties.get("memory_twin_areas"))
.and_then(|value| value.as_object())
.and_then(|map| map.get(&upstream_id.as_base_64()))
.and_then(|value| value.as_str())
.and_then(|twin_b64| CorticalID::try_from_base_64(twin_b64).ok());
if let Some(twin_id) = indexed_twin {
jobs.push((memory_id, upstream_id, Some(twin_id)));
continue;
}
if let Ok(hash_twin_id) = self.build_memory_twin_id(&memory_id, &upstream_id) {
if self.cortical_areas.contains_key(&hash_twin_id) {
jobs.push((memory_id, upstream_id, Some(hash_twin_id)));
continue;
}
}
jobs.push((memory_id, upstream_id, None));
}
Ok(jobs)
}
fn restore_memory_twin_index(
&mut self,
memory_area_id: &CorticalID,
upstream_area_id: &CorticalID,
twin_id: &CorticalID,
) -> BduResult<()> {
let expected_source = upstream_area_id.as_base_64();
let expected_target = memory_area_id.as_base_64();
let Some(existing) = self.cortical_areas.get_mut(twin_id) else {
return Err(BduError::InvalidArea(format!(
"Twin area {} not found while restoring memory twin index",
twin_id.as_base_64()
)));
};
let existing_source = existing
.properties
.get("memory_twin_of")
.and_then(|value| value.as_str())
.map(ToString::to_string);
let existing_target = existing
.properties
.get("memory_twin_for")
.and_then(|value| value.as_str())
.map(ToString::to_string);
if existing_source.as_deref() != Some(expected_source.as_str())
|| existing_target.as_deref() != Some(expected_target.as_str())
{
existing.properties.insert(
"memory_twin_of".to_string(),
serde_json::json!(expected_source),
);
existing.properties.insert(
"memory_twin_for".to_string(),
serde_json::json!(expected_target),
);
}
self.set_memory_twin_mapping(memory_area_id, upstream_area_id, twin_id);
let has_replay = self
.cortical_areas
.get(memory_area_id)
.and_then(|area| area.properties.get("cortical_mapping_dst"))
.and_then(|value| value.as_object())
.and_then(|map| map.get(&twin_id.as_base_64()))
.is_some_and(|rules| Self::mapping_rules_use_morphology(rules, "memory_replay"));
if !has_replay {
self.ensure_memory_replay_mapping(memory_area_id, twin_id)?;
}
Ok(())
}
fn area_is_memory(area: &CorticalArea) -> bool {
matches!(area.cortical_type, CorticalAreaType::Memory(_))
|| area
.properties
.get("is_mem_type")
.and_then(|value| value.as_bool())
== Some(true)
}
fn mapping_rules_use_morphology(rules: &serde_json::Value, morphology_id: &str) -> bool {
rules.as_array().is_some_and(|arr| {
arr.iter().any(|rule| {
rule.as_object()
.and_then(|obj| obj.get("morphology_id"))
.and_then(|value| value.as_str())
== Some(morphology_id)
})
})
}
fn rebind_memory_twin_mappings_to_npu(&mut self) -> BduResult<()> {
use crate::models::CorticalAreaExt;
let Some(npu_arc) = self.npu.clone() else {
return Ok(());
};
let mut bindings: Vec<(u32, u32, u32, f32)> = Vec::new();
for (memory_id, area) in &self.cortical_areas {
if !matches!(area.cortical_type, CorticalAreaType::Memory(_)) {
continue;
}
let Some(twins) = area
.properties
.get("memory_twin_areas")
.and_then(|value| value.as_object())
else {
continue;
};
let Some(&memory_idx) = self.cortical_id_to_idx.get(memory_id) else {
continue;
};
for (upstream_b64, twin_val) in twins {
let Some(twin_b64) = twin_val.as_str() else {
return Err(BduError::InvalidArea(format!(
"memory_twin_areas entry for {} is not a string",
upstream_b64
)));
};
let upstream_id = CorticalID::try_from_base_64(upstream_b64).map_err(|error| {
BduError::InvalidArea(format!(
"Invalid upstream cortical ID {}: {}",
upstream_b64, error
))
})?;
let twin_id = CorticalID::try_from_base_64(twin_b64).map_err(|error| {
BduError::InvalidArea(format!(
"Invalid twin cortical ID {}: {}",
twin_b64, error
))
})?;
let Some(&upstream_idx) = self.cortical_id_to_idx.get(&upstream_id) else {
continue;
};
let Some(&twin_idx) = self.cortical_id_to_idx.get(&twin_id) else {
continue;
};
let Some(twin_area) = self.cortical_areas.get(&twin_id) else {
continue;
};
let potential =
twin_area.firing_threshold() + twin_area.firing_threshold_increment();
bindings.push((memory_idx, upstream_idx, twin_idx, potential));
}
}
let mut npu = npu_arc.lock().unwrap();
for (memory_idx, upstream_idx, twin_idx, potential) in bindings {
npu.register_memory_twin_mapping(memory_idx, upstream_idx, twin_idx, potential);
}
Ok(())
}
fn rebind_stdp_mappings_to_npu(&mut self) -> BduResult<()> {
let Some(npu_arc) = self.npu.clone() else {
return Ok(());
};
let mut jobs: Vec<(CorticalID, CorticalID, u32, u32, Vec<serde_json::Value>)> = Vec::new();
for (src_id, src_area) in &self.cortical_areas {
let Some(dst_map) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|value| value.as_object())
else {
continue;
};
let Some(&src_idx) = self.cortical_id_to_idx.get(src_id) else {
continue;
};
for (dst_b64, rules) in dst_map {
let dst_id = CorticalID::try_from_base_64(dst_b64).map_err(|error| {
BduError::InvalidArea(format!(
"Invalid destination cortical ID {}: {}",
dst_b64, error
))
})?;
let Some(&dst_idx) = self.cortical_id_to_idx.get(&dst_id) else {
continue;
};
let Some(rules_arr) = rules.as_array() else {
continue;
};
jobs.push((*src_id, dst_id, src_idx, dst_idx, rules_arr.clone()));
}
}
for (src_id, dst_id, src_idx, dst_idx, rules) in jobs {
for rule in &rules {
let morphology_id = rule
.as_object()
.and_then(|obj| obj.get("morphology_id"))
.and_then(|value| value.as_str())
.unwrap_or("");
let mut plasticity_flag = rule
.as_object()
.and_then(|obj| obj.get("plasticity_flag"))
.and_then(|value| value.as_bool())
.unwrap_or(false);
if morphology_id == "associative_memory" {
plasticity_flag = true;
}
if !plasticity_flag {
continue;
}
let Some(rule_obj) = rule.as_object() else {
return Err(BduError::InvalidMorphology(
"Plasticity mapping rule must be an object format".to_string(),
));
};
let (_weight, psp, synapse_type, _delay) =
self.resolve_synapse_params_for_rule(&src_id, rule)?;
Self::register_stdp_mapping_for_rule(
&npu_arc,
&src_id,
&dst_id,
src_idx,
dst_idx,
rule_obj,
false,
psp,
synapse_type,
)?;
}
}
Ok(())
}
#[cfg(feature = "plasticity")]
fn reregister_memory_areas_with_plasticity(&mut self) -> BduResult<()> {
use feagi_evolutionary::extract_memory_properties;
use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
let Some(executor) = self.plasticity_executor.clone() else {
return Ok(());
};
let memory_ids: Vec<CorticalID> = self
.cortical_areas
.iter()
.filter(|(_, area)| matches!(area.cortical_type, CorticalAreaType::Memory(_)))
.map(|(id, _)| *id)
.collect();
for memory_id in memory_ids {
let Some(area) = self.cortical_areas.get(&memory_id) else {
continue;
};
let Some(mem_props) = extract_memory_properties(&area.properties) else {
continue;
};
let Some(&area_idx) = self.cortical_id_to_idx.get(&memory_id) else {
continue;
};
let area_name = memory_id.as_base_64();
let upstream_areas = self.get_episodic_memory_upstream_cortical_areas(&memory_id);
let lifecycle_config = MemoryNeuronLifecycleConfig {
initial_lifespan: mem_props.init_lifespan,
lifespan_growth_rate: mem_props.lifespan_growth_rate,
longterm_threshold: mem_props.longterm_threshold,
max_reactivations: 1000,
};
let exec = executor.lock().map_err(|_| {
BduError::Internal(
"Failed to lock PlasticityExecutor for memory-area rebind".to_string(),
)
})?;
exec.register_memory_area(
area_idx,
area_name,
mem_props.temporal_depth,
upstream_areas,
Some(lifecycle_config),
mem_props.mp_learning_enabled,
);
self.configure_memory_scan_on_executor(&*exec, &memory_id);
}
Ok(())
}
#[cfg(feature = "plasticity")]
pub(crate) fn configure_memory_scan_on_executor(
&self,
exec: &dyn feagi_npu_plasticity::PlasticityExecutor,
memory_id: &CorticalID,
) {
let Some(scan) = self.build_memory_scan_config(memory_id) else {
if let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) {
exec.configure_memory_scan(area_idx, None);
}
return;
};
let Some(&area_idx) = self.cortical_id_to_idx.get(memory_id) else {
return;
};
exec.configure_memory_scan(area_idx, Some(scan));
}
#[cfg(feature = "plasticity")]
fn build_memory_scan_config(
&self,
memory_id: &CorticalID,
) -> Option<feagi_npu_plasticity::MemoryScanConfig> {
use feagi_evolutionary::extract_memory_properties;
use feagi_npu_plasticity::{MemoryScanConfig, MemoryScanSource, ScanKernel};
let memory_area = self.cortical_areas.get(memory_id)?;
let mem_props = extract_memory_properties(&memory_area.properties)?;
let kernel_b64 = memory_area
.properties
.get("classifier_kernel_area_id")
.and_then(|v| v.as_str())?;
let class_b64 = memory_area
.properties
.get("classifier_class_area_id")
.and_then(|v| v.as_str())?;
let class_mem_b64 = memory_area
.properties
.get("classifier_class_memory_id")
.and_then(|v| v.as_str())?;
let kernel_id = CorticalID::try_from_base_64(kernel_b64).ok()?;
let class_id = CorticalID::try_from_base_64(class_b64).ok()?;
let class_mem_id = CorticalID::try_from_base_64(class_mem_b64).ok()?;
if !self.mapping_from_src_to_dst_has_associative(memory_id, &class_mem_id) {
return None;
}
let kernel_area = self.cortical_areas.get(&kernel_id)?;
let class_area = self.cortical_areas.get(&class_id)?;
let class_memory_area_idx = *self.cortical_id_to_idx.get(&class_mem_id)?;
let class_channel_count = class_area
.dimensions
.width
.saturating_mul(class_area.dimensions.height)
.saturating_mul(class_area.dimensions.depth);
if class_channel_count == 0 {
return None;
}
let scan_fields = self.get_episodic_scan_upstream_cortical_areas(memory_id);
if scan_fields.is_empty() {
return None;
}
let twin_map = memory_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())?;
let mut sources = Vec::new();
for field_idx in scan_fields {
let field_id = *self.cortical_idx_to_id.get(&field_idx)?;
let field_area = self.cortical_areas.get(&field_id)?;
let twin_b64 = twin_map.get(&field_id.as_base_64())?.as_str()?;
let twin_id = CorticalID::try_from_base_64(twin_b64).ok()?;
let twin_idx = *self.cortical_id_to_idx.get(&twin_id)?;
sources.push(MemoryScanSource {
field_area_idx: field_idx,
twin_area_idx: twin_idx,
field_width: field_area.dimensions.width,
field_height: field_area.dimensions.height,
field_depth: field_area.dimensions.depth,
});
}
if sources.is_empty() {
return None;
}
Some(MemoryScanConfig {
kernel: ScanKernel {
width: kernel_area.dimensions.width,
height: kernel_area.dimensions.height,
depth: kernel_area.dimensions.depth,
},
min_window_activity: mem_props.min_window_activity,
scan_skip_density: mem_props.scan_skip_density,
class_channel_count,
class_area_width: class_area.dimensions.width,
class_area_height: class_area.dimensions.height,
class_memory_area_idx,
sources,
})
}
#[cfg(feature = "plasticity")]
fn mapping_from_src_to_dst_has_associative(
&self,
src_area_id: &CorticalID,
dst_area_id: &CorticalID,
) -> bool {
let Some(src_area) = self.cortical_areas.get(src_area_id) else {
return false;
};
let Some(mapping_dst) = src_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
else {
return false;
};
let Some(rules) = Self::get_mapping_rules_for_destination(mapping_dst, dst_area_id) else {
return false;
};
Self::mapping_has_morphology_rule(rules, "associative_memory")
}
#[cfg(feature = "plasticity")]
pub fn set_plasticity_executor(
&mut self,
executor: Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>,
) {
if let Ok(mut exec) = executor.lock() {
use feagi_npu_plasticity::executor::PlasticityExecutor;
if !exec.is_running() {
exec.start();
}
} else {
warn!(target: "feagi-bdu", "⚠️ Failed to lock PlasticityExecutor for startup");
}
self.plasticity_executor = Some(executor);
info!(target: "feagi-bdu", "🔗 ConnectomeManager: PlasticityExecutor reference set");
}
#[cfg(feature = "plasticity")]
pub fn get_plasticity_executor(
&self,
) -> Option<&Arc<std::sync::Mutex<feagi_npu_plasticity::AsyncPlasticityExecutor>>> {
self.plasticity_executor.as_ref()
}
pub fn get_neuron_capacity(&self) -> usize {
self.config.max_neurons
}
pub fn get_synapse_capacity(&self) -> usize {
self.config.max_synapses
}
pub fn update_fatigue_index(&self) -> Option<u8> {
let mut last_calc = match self.last_fatigue_calculation.lock() {
Ok(guard) => guard,
Err(_) => return None, };
let now = std::time::Instant::now();
if now.duration_since(*last_calc).as_secs() < 2 {
return None; }
*last_calc = now;
drop(last_calc);
let regular_neuron_count = self.get_neuron_count();
let regular_neuron_capacity = self.get_neuron_capacity();
let regular_neuron_util = if regular_neuron_capacity > 0 {
((regular_neuron_count as f64 / regular_neuron_capacity as f64) * 100.0).round() as u8
} else {
0
};
let memory_neuron_util = match StateManager::instance().try_read() {
Some(state_manager) => state_manager.get_core_state().get_memory_neuron_util(),
None => {
return None;
}
};
let synapse_count = self.get_synapse_count();
let synapse_capacity = self.get_synapse_capacity();
let synapse_util = if synapse_capacity > 0 {
((synapse_count as f64 / synapse_capacity as f64) * 100.0).round() as u8
} else {
0
};
let fatigue_index = regular_neuron_util
.max(memory_neuron_util)
.max(synapse_util);
let current_fatigue_active = {
StateManager::instance()
.try_read()
.map(|m| m.get_core_state().is_fatigue_active())
.unwrap_or(false)
};
let new_fatigue_active = if fatigue_index >= 85 {
true
} else if fatigue_index < 80 {
false
} else {
current_fatigue_active };
if let Some(state_manager) = StateManager::instance().try_write() {
let core_state = state_manager.get_core_state();
core_state.set_fatigue_index(fatigue_index);
core_state.set_fatigue_active(new_fatigue_active);
core_state.set_regular_neuron_util(regular_neuron_util);
core_state.set_memory_neuron_util(memory_neuron_util);
core_state.set_synapse_util(synapse_util);
} else {
trace!(target: "feagi-bdu", "[FATIGUE] StateManager unavailable, skipping update");
}
if let Some(ref npu) = self.npu {
if let Ok(mut npu_lock) = npu.lock() {
npu_lock.set_fatigue_active(new_fatigue_active);
}
}
trace!(
target: "feagi-bdu",
"[FATIGUE] Index={}, Active={}, Regular={}%, Memory={}%, Synapse={}%",
fatigue_index, new_fatigue_active, regular_neuron_util, memory_neuron_util, synapse_util
);
Some(fatigue_index)
}
pub fn create_neurons_for_area(&mut self, cortical_id: &CorticalID) -> BduResult<u32> {
let area = self
.cortical_areas
.get(cortical_id)
.ok_or_else(|| {
BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
})?
.clone();
let cortical_idx = self.cortical_id_to_idx.get(cortical_id).ok_or_else(|| {
BduError::InvalidArea(format!("No index for cortical area {}", cortical_id))
})?;
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
use crate::models::CorticalAreaExt;
let per_voxel_cnt = area.neurons_per_voxel();
let firing_threshold = area.firing_threshold();
let firing_threshold_increment_x = area.firing_threshold_increment_x();
let firing_threshold_increment_y = area.firing_threshold_increment_y();
let firing_threshold_increment_z = area.firing_threshold_increment_z();
let firing_threshold_limit_raw = area.firing_threshold_limit();
let firing_threshold_limit = if firing_threshold_limit_raw == 0.0 {
f32::MAX } else {
firing_threshold_limit_raw
};
if firing_threshold_increment_x != 0.0
|| firing_threshold_increment_y != 0.0
|| firing_threshold_increment_z != 0.0
{
info!(
target: "feagi-bdu",
"🔍 [DEBUG] Area {}: firing_threshold_increment = [{}, {}, {}]",
cortical_id.as_base_64(),
firing_threshold_increment_x,
firing_threshold_increment_y,
firing_threshold_increment_z
);
} else {
if area.properties.contains_key("firing_threshold_increment_x")
|| area.properties.contains_key("firing_threshold_increment_y")
|| area.properties.contains_key("firing_threshold_increment_z")
{
info!(
target: "feagi-bdu",
"🔍 [DEBUG] Area {}: INCREMENT PROPERTIES FOUND: x={:?}, y={:?}, z={:?}",
cortical_id.as_base_64(),
area.properties.get("firing_threshold_increment_x"),
area.properties.get("firing_threshold_increment_y"),
area.properties.get("firing_threshold_increment_z")
);
}
}
let leak_coefficient = area.leak_coefficient();
let excitability = area.neuron_excitability();
let refractory_period = area.refractory_period();
let consecutive_fire_limit_raw = area.consecutive_fire_count() as u16;
let consecutive_fire_limit = if consecutive_fire_limit_raw == 0 {
u16::MAX } else {
consecutive_fire_limit_raw
};
let snooze_length = area.snooze_period();
let mp_charge_accumulation = area.mp_charge_accumulation();
let voxels = area.dimensions.width as usize
* area.dimensions.height as usize
* area.dimensions.depth as usize;
let expected_neurons = voxels * per_voxel_cnt as usize;
trace!(
target: "feagi-bdu",
"Creating neurons for area {}: {}x{}x{} voxels × {} neurons/voxel = {} total neurons",
cortical_id.as_base_64(),
area.dimensions.width,
area.dimensions.height,
area.dimensions.depth,
per_voxel_cnt,
expected_neurons
);
let neuron_count: u32 = {
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
npu_lock
.create_cortical_area_neurons(
*cortical_idx,
area.dimensions.width,
area.dimensions.height,
area.dimensions.depth,
per_voxel_cnt,
firing_threshold,
firing_threshold_increment_x,
firing_threshold_increment_y,
firing_threshold_increment_z,
firing_threshold_limit,
leak_coefficient,
0.0, 0, refractory_period,
excitability,
consecutive_fire_limit,
snooze_length,
mp_charge_accumulation,
)
.map_err(|e| BduError::Internal(format!("NPU neuron creation failed: {}", e)))?
};
trace!(
target: "feagi-bdu",
"Created {} neurons for area {} via NPU",
neuron_count,
cortical_id.as_base_64()
);
{
let mut cache = self.cached_neuron_counts_per_area.write();
cache
.entry(*cortical_id)
.or_insert_with(|| AtomicUsize::new(0))
.store(neuron_count as usize, Ordering::Relaxed);
}
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
state_manager
.set_cortical_area_neuron_count(&cortical_id.as_base_64(), neuron_count as usize);
self.cached_neuron_count
.fetch_add(neuron_count as usize, Ordering::Relaxed);
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.add_neuron_count(neuron_count);
core_state.add_regular_neuron_count(neuron_count);
if let Some(npu) = &self.npu {
if let Ok(mut npl) = npu.lock() {
if let Some(v) = area.properties.get("rate_modulated_leak") {
use crate::models::CorticalAreaExt;
let idxs: Vec<usize> = npl
.get_neurons_in_cortical_area(*cortical_idx)
.into_iter()
.map(|id| id as usize)
.collect();
npl.sync_rate_modulated_leak_from_cortical_property(
*cortical_idx,
v,
area.leak_coefficient(),
idxs,
);
} else {
npl.remove_rate_modulated_leak(*cortical_idx);
}
}
}
Ok(neuron_count)
}
#[allow(clippy::too_many_arguments)]
pub fn add_neuron(
&mut self,
cortical_id: &CorticalID,
x: u32,
y: u32,
z: u32,
firing_threshold: f32,
firing_threshold_limit: f32,
leak_coefficient: f32,
resting_potential: f32,
neuron_type: u8,
refractory_period: u16,
excitability: f32,
consecutive_fire_limit: u16,
snooze_length: u16,
mp_charge_accumulation: bool,
) -> BduResult<u64> {
self.add_neuron_with_area_stats(
cortical_id,
x,
y,
z,
firing_threshold,
firing_threshold_limit,
leak_coefficient,
resting_potential,
neuron_type,
refractory_period,
excitability,
consecutive_fire_limit,
snooze_length,
mp_charge_accumulation,
true,
)
}
#[allow(clippy::too_many_arguments)]
pub fn add_auxiliary_neuron(
&mut self,
cortical_id: &CorticalID,
x: u32,
y: u32,
z: u32,
firing_threshold: f32,
firing_threshold_limit: f32,
leak_coefficient: f32,
resting_potential: f32,
neuron_type: u8,
refractory_period: u16,
excitability: f32,
consecutive_fire_limit: u16,
snooze_length: u16,
mp_charge_accumulation: bool,
) -> BduResult<u64> {
self.add_neuron_with_area_stats(
cortical_id,
x,
y,
z,
firing_threshold,
firing_threshold_limit,
leak_coefficient,
resting_potential,
neuron_type,
refractory_period,
excitability,
consecutive_fire_limit,
snooze_length,
mp_charge_accumulation,
false,
)
}
#[allow(clippy::too_many_arguments)]
fn add_neuron_with_area_stats(
&mut self,
cortical_id: &CorticalID,
x: u32,
y: u32,
z: u32,
firing_threshold: f32,
firing_threshold_limit: f32,
leak_coefficient: f32,
resting_potential: f32,
neuron_type: u8,
refractory_period: u16,
excitability: f32,
consecutive_fire_limit: u16,
snooze_length: u16,
mp_charge_accumulation: bool,
update_area_stats: bool,
) -> BduResult<u64> {
if !self.cortical_areas.contains_key(cortical_id) {
return Err(BduError::InvalidArea(format!(
"Cortical area {} not found",
cortical_id
)));
}
let cortical_idx = *self
.cortical_id_to_idx
.get(cortical_id)
.ok_or_else(|| BduError::InvalidArea(format!("No index for {}", cortical_id)))?;
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let neuron_id = npu_lock
.add_neuron(
firing_threshold,
firing_threshold_limit,
leak_coefficient,
resting_potential,
neuron_type as i32,
refractory_period,
excitability,
consecutive_fire_limit,
snooze_length,
mp_charge_accumulation,
cortical_idx,
x,
y,
z,
)
.map_err(|e| BduError::Internal(format!("Failed to add neuron: {}", e)))?;
trace!(
target: "feagi-bdu",
"Created neuron {} in area {} at ({}, {}, {})",
neuron_id.0,
cortical_id,
x,
y,
z
);
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.add_neuron_count(1);
core_state.add_regular_neuron_count(1);
if update_area_stats {
state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
}
Ok(neuron_id.0 as u64)
}
pub fn delete_neuron(&mut self, neuron_id: u64) -> BduResult<bool> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
let cortical_id = cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
let deleted = npu_lock.delete_neuron(neuron_id as u32);
if deleted {
trace!(target: "feagi-bdu", "Deleted neuron {}", neuron_id);
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.subtract_neuron_count(1);
core_state.subtract_regular_neuron_count(1);
if let Some(cortical_id) = cortical_id {
state_manager.subtract_cortical_area_neuron_count(&cortical_id.as_base_64(), 1);
}
}
Ok(deleted)
}
pub fn apply_cortical_mapping(&mut self, src_cortical_id: &CorticalID) -> BduResult<u32> {
let src_area = self
.cortical_areas
.get(src_cortical_id)
.ok_or_else(|| {
BduError::InvalidArea(format!("Source area {} not found", src_cortical_id))
})?
.clone();
let dstmap = match src_area.properties.get("cortical_mapping_dst") {
Some(serde_json::Value::Object(map)) if !map.is_empty() => map,
_ => return Ok(0), };
let src_cortical_idx = *self
.cortical_id_to_idx
.get(src_cortical_id)
.ok_or_else(|| BduError::InvalidArea(format!("No index for {}", src_cortical_id)))?;
let mut total_synapses = 0u32;
let mut upstream_updates: Vec<(CorticalID, u32)> = Vec::new();
for (dst_cortical_id_str, _rules) in dstmap {
let dst_cortical_id = match CorticalID::try_from_base_64(dst_cortical_id_str) {
Ok(id) => id,
Err(_) => {
warn!(target: "feagi-bdu","Invalid cortical ID format: {}, skipping", dst_cortical_id_str);
continue;
}
};
if !self.cortical_id_to_idx.contains_key(&dst_cortical_id) {
warn!(target: "feagi-bdu","Destination area {} not found, skipping", dst_cortical_id);
continue;
}
let synapse_count =
self.apply_cortical_mapping_for_pair(src_cortical_id, &dst_cortical_id)?;
total_synapses += synapse_count as u32;
upstream_updates.push((dst_cortical_id, src_cortical_idx));
}
for (dst_id, src_idx) in upstream_updates {
self.add_upstream_area(&dst_id, src_idx);
}
trace!(
target: "feagi-bdu",
"Created {} synapses for area {} via NPU",
total_synapses,
src_cortical_id
);
if total_synapses > 0 {
let mut cache = self.cached_synapse_counts_per_area.write();
cache
.entry(*src_cortical_id)
.or_insert_with(|| AtomicUsize::new(0))
.fetch_add(total_synapses as usize, Ordering::Relaxed);
}
self.cached_synapse_count
.fetch_add(total_synapses as usize, Ordering::Relaxed);
if total_synapses > 0 {
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.add_synapse_count(total_synapses);
}
Ok(total_synapses)
}
pub fn has_neuron(&self, neuron_id: u64) -> bool {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.lock() {
npu_lock.is_neuron_valid(neuron_id as u32)
} else {
false
}
} else {
false
}
}
pub fn get_neuron_count(&self) -> usize {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.try_lock() {
let fresh_count = npu_lock.get_neuron_count();
self.cached_neuron_count
.store(fresh_count, Ordering::Relaxed);
}
}
self.cached_neuron_count.load(Ordering::Relaxed)
}
pub fn update_cached_neuron_count(&self) {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.try_lock() {
let count = npu_lock.get_neuron_count();
self.cached_neuron_count.store(count, Ordering::Relaxed);
}
}
}
pub fn refresh_neuron_count_for_area(&self, cortical_id: &CorticalID) -> Option<usize> {
let npu = self.npu.as_ref()?;
let cortical_idx = *self.cortical_id_to_idx.get(cortical_id)?;
let npu_lock = npu.lock().ok()?;
let count = npu_lock.get_neurons_in_cortical_area(cortical_idx).len();
drop(npu_lock);
let mut cache = self.cached_neuron_counts_per_area.write();
cache
.entry(*cortical_id)
.or_insert_with(|| AtomicUsize::new(0))
.store(count, Ordering::Relaxed);
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
state_manager.set_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
self.update_cached_neuron_count();
Some(count)
}
pub fn get_synapse_count(&self) -> usize {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.try_lock() {
let fresh_count = npu_lock.get_synapse_count();
self.cached_synapse_count
.store(fresh_count, Ordering::Relaxed);
}
}
self.cached_synapse_count.load(Ordering::Relaxed)
}
pub fn update_cached_synapse_count(&self) {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.try_lock() {
let count = npu_lock.get_synapse_count();
self.cached_synapse_count.store(count, Ordering::Relaxed);
}
}
}
pub fn update_all_cached_stats(&self) {
self.update_cached_neuron_count();
self.update_cached_synapse_count();
}
pub fn get_neuron_coordinates(&self, neuron_id: u64) -> (u32, u32, u32) {
#[cfg(feature = "plasticity")]
{
if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
return (0, 0, 0);
}
}
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.lock() {
npu_lock
.get_neuron_coordinates(neuron_id as u32)
.unwrap_or((0, 0, 0))
} else {
(0, 0, 0)
}
} else {
(0, 0, 0)
}
}
pub fn get_neuron_cortical_idx(&self, neuron_id: u64) -> u32 {
self.get_neuron_cortical_idx_opt(neuron_id).unwrap_or(0)
}
pub fn get_neuron_cortical_idx_opt(&self, neuron_id: u64) -> Option<u32> {
#[cfg(feature = "plasticity")]
{
if feagi_npu_plasticity::NeuronIdManager::is_memory_neuron_id(neuron_id as u32) {
return self.memory_neuron_cortical_idx_opt(neuron_id as u32);
}
}
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.lock() {
npu_lock.get_neuron_cortical_area(neuron_id as u32)
} else {
None
}
} else {
None
}
}
#[cfg(feature = "plasticity")]
fn memory_neuron_cortical_idx_opt(&self, neuron_id: u32) -> Option<u32> {
let exec = self.get_plasticity_executor()?;
let guard = exec.lock().ok()?;
guard
.memory_neuron_detail(neuron_id)
.map(|d| d.cortical_area_idx)
}
pub fn get_neurons_in_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
let cortical_idx = match self.cortical_id_to_idx.get(cortical_id) {
Some(idx) => *idx,
None => return Vec::new(),
};
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.lock() {
npu_lock
.get_neurons_in_cortical_area(cortical_idx)
.into_iter()
.map(|id| id as u64)
.collect()
} else {
Vec::new()
}
} else {
Vec::new()
}
}
pub fn get_outgoing_synapses(&self, source_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.lock() {
npu_lock.get_outgoing_synapses(source_neuron_id as u32)
} else {
Vec::new()
}
} else {
Vec::new()
}
}
pub fn get_incoming_synapses(&self, target_neuron_id: u64) -> Vec<(u32, f32, f32, u8)> {
if let Some(ref npu) = self.npu {
if let Ok(npu_lock) = npu.lock() {
npu_lock.get_incoming_synapses(target_neuron_id as u32)
} else {
Vec::new()
}
} else {
Vec::new()
}
}
pub fn get_neuron_count_in_area(&self, cortical_id: &CorticalID) -> usize {
let is_memory_area = self
.cortical_areas
.get(cortical_id)
.and_then(|area| feagi_evolutionary::extract_memory_properties(&area.properties))
.is_some();
if is_memory_area {
#[cfg(feature = "plasticity")]
if let Some(count) = self.active_memory_neuron_count_from_plasticity(cortical_id) {
return count;
}
return StateManager::instance()
.try_read()
.and_then(|state_manager| {
state_manager
.get_cortical_area_stats(&cortical_id.as_base_64())
.map(|stats| stats.neuron_count)
})
.unwrap_or(0);
}
let cache = self.cached_neuron_counts_per_area.read();
cache
.get(cortical_id)
.map(|count| count.load(Ordering::Relaxed))
.unwrap_or(0)
}
#[cfg(feature = "plasticity")]
fn active_memory_neuron_count_from_plasticity(
&self,
cortical_id: &CorticalID,
) -> Option<usize> {
let cortical_idx = self.cortical_id_to_idx.get(cortical_id)?;
let exec = self.get_plasticity_executor()?;
let guard = exec.lock().ok()?;
let runtime = guard.memory_cortical_area_runtime_info(*cortical_idx)?;
Some(runtime.active_memory_neuron_count())
}
pub fn get_populated_areas(&self) -> Vec<(String, usize)> {
let mut result = Vec::new();
for cortical_id in self.cortical_areas.keys() {
let count = self.get_neuron_count_in_area(cortical_id);
if count > 0 {
result.push((cortical_id.to_string(), count));
}
}
result
}
pub fn is_area_populated(&self, cortical_id: &CorticalID) -> bool {
self.get_neuron_count_in_area(cortical_id) > 0
}
pub fn get_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
let cache = self.cached_synapse_counts_per_area.read();
cache
.get(cortical_id)
.map(|count| count.load(Ordering::Relaxed))
.unwrap_or(0)
}
pub fn get_incoming_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
if !self.cortical_id_to_idx.contains_key(cortical_id) {
return 0;
}
if let Some(state_manager) = StateManager::instance().try_read() {
if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
return stats.incoming_synapse_count;
}
}
0
}
pub fn get_outgoing_synapse_count_in_area(&self, cortical_id: &CorticalID) -> usize {
if !self.cortical_id_to_idx.contains_key(cortical_id) {
return 0;
}
if let Some(state_manager) = StateManager::instance().try_read() {
if let Some(stats) = state_manager.get_cortical_area_stats(&cortical_id.as_base_64()) {
return stats.outgoing_synapse_count;
}
}
0
}
pub fn are_neurons_connected(&self, source_neuron_id: u64, target_neuron_id: u64) -> bool {
let synapses = self.get_outgoing_synapses(source_neuron_id);
synapses
.iter()
.any(|(target, _, _, _)| *target == target_neuron_id as u32)
}
pub fn get_connection_weight(
&self,
source_neuron_id: u64,
target_neuron_id: u64,
) -> Option<f32> {
let synapses = self.get_outgoing_synapses(source_neuron_id);
synapses
.iter()
.find(|(target, _, _, _)| *target == target_neuron_id as u32)
.map(|(_, weight, _, _)| *weight)
}
pub fn get_area_connectivity_stats(&self, cortical_id: &CorticalID) -> (usize, usize, f32) {
let neurons = self.get_neurons_in_area(cortical_id);
let neuron_count = neurons.len();
if neuron_count == 0 {
return (0, 0, 0.0);
}
let mut total_synapses = 0;
for neuron_id in neurons {
total_synapses += self.get_outgoing_synapses(neuron_id).len();
}
let avg_synapses = total_synapses as f32 / neuron_count as f32;
(neuron_count, total_synapses, avg_synapses)
}
pub fn get_neuron_cortical_id(&self, neuron_id: u64) -> Option<CorticalID> {
let cortical_idx = self.get_neuron_cortical_idx_opt(neuron_id)?;
self.cortical_idx_to_id.get(&cortical_idx).copied()
}
pub fn get_neuron_density(&self, cortical_id: &CorticalID) -> f32 {
let area = match self.cortical_areas.get(cortical_id) {
Some(a) => a,
None => return 0.0,
};
let neuron_count = self.get_neuron_count_in_area(cortical_id);
let volume = area.dimensions.width * area.dimensions.height * area.dimensions.depth;
if volume == 0 {
return 0.0;
}
neuron_count as f32 / volume as f32
}
pub fn get_all_area_stats(&self) -> Vec<(String, usize, usize, f32)> {
let mut stats = Vec::new();
for cortical_id in self.cortical_areas.keys() {
let neuron_count = self.get_neuron_count_in_area(cortical_id);
let synapse_count = self.get_synapse_count_in_area(cortical_id);
let density = self.get_neuron_density(cortical_id);
stats.push((
cortical_id.to_string(),
neuron_count,
synapse_count,
density,
));
}
stats
}
pub fn get_config(&self) -> &ConnectomeConfig {
&self.config
}
pub fn set_config(&mut self, config: ConnectomeConfig) {
self.config = config;
}
pub fn ensure_core_cortical_areas(&mut self) -> BduResult<()> {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Ensuring core cortical areas exist...");
use feagi_structures::genomic::cortical_area::{
CoreCorticalType, CorticalArea, CorticalAreaDimensions, CorticalAreaType,
};
let core_dimensions = CorticalAreaDimensions::new(1, 1, 1).map_err(|e| {
BduError::Internal(format!("Failed to create core area dimensions: {}", e))
})?;
let core_position = (0, 0, 0).into();
let death_id = CoreCorticalType::Death.to_cortical_id();
if !self.cortical_areas.contains_key(&death_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _death area (cortical_idx=0)");
let death_area = CorticalArea::new(
death_id,
0, "_death".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Death),
)
.map_err(|e| BduError::Internal(format!("Failed to create _death area: {}", e)))?;
match self.add_cortical_area(death_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _death area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _death area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _death area already exists");
}
let power_id = CoreCorticalType::Power.to_cortical_id();
if !self.cortical_areas.contains_key(&power_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _power area (cortical_idx=1)");
let power_area = CorticalArea::new(
power_id,
1, "_power".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Power),
)
.map_err(|e| BduError::Internal(format!("Failed to create _power area: {}", e)))?;
match self.add_cortical_area(power_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _power area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _power area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _power area already exists");
}
let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
let pain_id = CoreCorticalType::Pain.to_cortical_id();
let pleasure_id = CoreCorticalType::Pleasure.to_cortical_id();
let fear_id = CoreCorticalType::Fear.to_cortical_id();
let hope_id = CoreCorticalType::Hope.to_cortical_id();
if !self.cortical_areas.contains_key(&fatigue_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fatigue area (cortical_idx=2)");
let fatigue_area = CorticalArea::new(
fatigue_id,
2, "_fatigue".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Fatigue),
)
.map_err(|e| BduError::Internal(format!("Failed to create _fatigue area: {}", e)))?;
match self.add_cortical_area(fatigue_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _fatigue area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _fatigue area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _fatigue area already exists");
}
if !self.cortical_areas.contains_key(&pain_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pain area (cortical_idx=3)");
let pain_area = CorticalArea::new(
pain_id,
3, "_pain".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Pain),
)
.map_err(|e| BduError::Internal(format!("Failed to create _pain area: {}", e)))?;
match self.add_cortical_area(pain_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _pain area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _pain area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _pain area already exists");
}
if !self.cortical_areas.contains_key(&pleasure_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _pleasure area (cortical_idx=4)");
let pleasure_area = CorticalArea::new(
pleasure_id,
4, "_pleasure".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Pleasure),
)
.map_err(|e| BduError::Internal(format!("Failed to create _pleasure area: {}", e)))?;
match self.add_cortical_area(pleasure_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _pleasure area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _pleasure area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _pleasure area already exists");
}
if !self.cortical_areas.contains_key(&fear_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _fear area (cortical_idx=5)");
let fear_area = CorticalArea::new(
fear_id,
5, "_fear".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Fear),
)
.map_err(|e| BduError::Internal(format!("Failed to create _fear area: {}", e)))?;
match self.add_cortical_area(fear_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _fear area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _fear area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _fear area already exists");
}
if !self.cortical_areas.contains_key(&hope_id) {
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Creating missing _hope area (cortical_idx=6)");
let hope_area = CorticalArea::new(
hope_id,
6, "_hope".to_string(),
core_dimensions,
core_position,
CorticalAreaType::Core(CoreCorticalType::Hope),
)
.map_err(|e| BduError::Internal(format!("Failed to create _hope area: {}", e)))?;
match self.add_cortical_area(hope_area) {
Ok(idx) => {
info!(target: "feagi-bdu", " ✅ Created _hope area with cortical_idx={}", idx);
}
Err(e) => {
error!(target: "feagi-bdu", " ❌ Failed to add _hope area: {}", e);
return Err(e);
}
}
} else {
info!(target: "feagi-bdu", " ✓ _hope area already exists");
}
info!(target: "feagi-bdu", "🔧 [CORE-AREA] Core area check complete");
Ok(())
}
#[deprecated(
note = "Use GenomeService::save_genome() instead. This produces incomplete v2.1 format without morphologies/physiology."
)]
#[allow(deprecated)]
pub fn save_genome_to_json(
&self,
genome_id: Option<String>,
genome_title: Option<String>,
) -> BduResult<String> {
let mut brain_regions_with_parents = std::collections::HashMap::new();
for region_id in self.brain_regions.get_all_region_ids() {
if let Some(region) = self.brain_regions.get_region(region_id) {
let parent_id = self
.brain_regions
.get_parent(region_id)
.map(|s| s.to_string());
brain_regions_with_parents
.insert(region_id.to_string(), (region.clone(), parent_id));
}
}
Ok(feagi_evolutionary::GenomeSaver::save_to_json(
&self.cortical_areas,
&brain_regions_with_parents,
genome_id,
genome_title,
)?)
}
pub fn prepare_for_new_genome(&mut self) -> BduResult<()> {
info!(target: "feagi-bdu","Preparing for new genome (clearing existing state)");
self.cortical_areas.clear();
self.cortical_id_to_idx.clear();
self.cortical_idx_to_id.clear();
self.next_cortical_idx = 7;
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)");
self.brain_regions = BrainRegionHierarchy::new();
#[cfg(feature = "plasticity")]
if let Some(executor) = self.plasticity_executor.as_ref() {
executor
.lock()
.map_err(|_| {
BduError::Internal(
"Failed to lock PlasticityExecutor for memory state reset".to_string(),
)
})?
.reset_all_memory_state()
.map_err(BduError::Internal)?;
}
if let Some(ref npu) = self.npu {
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
npu_lock
.reset_for_new_genome()
.map_err(|e| BduError::Internal(format!("Failed to reset NPU: {}", e)))?;
}
info!(target: "feagi-bdu","✅ Connectome cleared and ready for new genome");
Ok(())
}
pub fn resize_for_genome(
&mut self,
genome: &feagi_evolutionary::RuntimeGenome,
) -> BduResult<()> {
self.morphology_registry = genome.morphologies.clone();
info!(target: "feagi-bdu", "Stored {} morphologies from genome", self.morphology_registry.count());
let required_neurons = genome.stats.innate_neuron_count;
let required_synapses = genome.stats.innate_synapse_count;
info!(target: "feagi-bdu",
"Genome requires: {} neurons, {} synapses",
required_neurons,
required_synapses
);
let mut total_voxels = 0;
for area in genome.cortical_areas.values() {
total_voxels += area.dimensions.width * area.dimensions.height * area.dimensions.depth;
}
info!(target: "feagi-bdu",
"Genome has {} cortical areas with {} total voxels",
genome.cortical_areas.len(),
total_voxels
);
Ok(())
}
pub fn create_synapse(
&mut self,
source_neuron_id: u64,
target_neuron_id: u64,
weight: f32,
psp: f32,
synapse_type: u8,
) -> BduResult<()> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let source_exists = (source_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
let target_exists = (target_neuron_id as u32) < npu_lock.get_neuron_count() as u32;
if !source_exists {
return Err(BduError::InvalidNeuron(format!(
"Source neuron {} not found",
source_neuron_id
)));
}
if !target_exists {
return Err(BduError::InvalidNeuron(format!(
"Target neuron {} not found",
target_neuron_id
)));
}
let syn_type = if synapse_type == 0 {
feagi_npu_neural::synapse::SynapseType::Excitatory
} else {
feagi_npu_neural::synapse::SynapseType::Inhibitory
};
let synapse_idx = npu_lock
.add_synapse(
NeuronId(source_neuron_id as u32),
NeuronId(target_neuron_id as u32),
feagi_npu_neural::types::SynapticWeight(weight),
feagi_npu_neural::types::SynapticPsp(psp),
syn_type,
0,
1,
)
.map_err(|e| BduError::Internal(format!("Failed to create synapse: {}", e)))?;
debug!(target: "feagi-bdu", "Created synapse: {} -> {} (weight: {}, psp: {}, type: {}, idx: {})",
source_neuron_id, target_neuron_id, weight, psp, synapse_type, synapse_idx);
let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
let source_cortical_id =
source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
let target_cortical_id =
target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.add_synapse_count(1);
if let Some(cortical_id) = source_cortical_id {
state_manager.add_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
}
if let Some(cortical_id) = target_cortical_id {
state_manager.add_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
}
Ok(())
}
fn sync_cortical_area_flags_to_npu(&mut self) -> BduResult<()> {
if let Some(ref npu) = self.npu {
if let Ok(mut npu_lock) = npu.lock() {
let mut psp_uniform_flags = ahash::AHashMap::new();
let mut mp_driven_psp_flags = ahash::AHashMap::new();
let mut postsynaptic_current_flags = ahash::AHashMap::new();
let mut degeneration_flags = ahash::AHashMap::new();
for (cortical_id, area) in &self.cortical_areas {
let default_psp_uniform = *cortical_id
== CoreCorticalType::Power.to_cortical_id()
|| matches!(area.cortical_type, CorticalAreaType::Memory(_));
let psp_uniform = area
.get_property("psp_uniform_distribution")
.and_then(|v| v.as_bool())
.unwrap_or(default_psp_uniform);
psp_uniform_flags.insert(*cortical_id, psp_uniform);
let mp_driven_psp = area
.get_property("mp_driven_psp")
.and_then(|v| v.as_bool())
.unwrap_or(false);
mp_driven_psp_flags.insert(*cortical_id, mp_driven_psp);
let postsynaptic_current = area
.get_property("postsynaptic_current")
.and_then(|v| v.as_f64())
.unwrap_or(1.0) as f32;
postsynaptic_current_flags.insert(*cortical_id, postsynaptic_current);
let degeneration = area
.get_property("degeneration")
.and_then(|v| v.as_f64())
.unwrap_or(0.0) as f32;
if degeneration > 0.0 {
degeneration_flags.insert(*cortical_id, degeneration);
}
}
npu_lock.set_psp_uniform_distribution_flags(psp_uniform_flags);
npu_lock.set_mp_driven_psp_flags(mp_driven_psp_flags);
npu_lock.set_postsynaptic_current_flags(postsynaptic_current_flags);
npu_lock.set_degeneration_flags(degeneration_flags);
trace!(
target: "feagi-bdu",
"Synchronized cortical area flags to NPU ({} areas)",
self.cortical_areas.len()
);
}
}
Ok(())
}
pub fn get_synapse(
&self,
source_neuron_id: u64,
target_neuron_id: u64,
) -> Option<(f32, f32, u8)> {
let npu = self.npu.as_ref()?;
let npu_lock = npu.lock().ok()?;
let incoming = npu_lock.get_incoming_synapses(target_neuron_id as u32);
for (source_id, weight, psp, synapse_type) in incoming {
if source_id == source_neuron_id as u32 {
return Some((weight, psp, synapse_type));
}
}
None
}
pub fn update_synapse_weight(
&mut self,
source_neuron_id: u64,
target_neuron_id: u64,
new_weight: f32,
) -> BduResult<()> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let updated = npu_lock.update_synapse_weight(
NeuronId(source_neuron_id as u32),
NeuronId(target_neuron_id as u32),
feagi_npu_neural::types::SynapticWeight(new_weight),
);
if updated {
debug!(target: "feagi-bdu","Updated synapse weight: {} -> {} = {}", source_neuron_id, target_neuron_id, new_weight);
Ok(())
} else {
Err(BduError::InvalidSynapse(format!(
"Synapse {} -> {} not found",
source_neuron_id, target_neuron_id
)))
}
}
pub fn remove_synapse(
&mut self,
source_neuron_id: u64,
target_neuron_id: u64,
) -> BduResult<bool> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let source_cortical_idx = npu_lock.get_neuron_cortical_area(source_neuron_id as u32);
let target_cortical_idx = npu_lock.get_neuron_cortical_area(target_neuron_id as u32);
let source_cortical_id =
source_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
let target_cortical_id =
target_cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
let removed = npu_lock.remove_synapse(
NeuronId(source_neuron_id as u32),
NeuronId(target_neuron_id as u32),
);
if removed {
debug!(target: "feagi-bdu","Removed synapse: {} -> {}", source_neuron_id, target_neuron_id);
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.subtract_synapse_count(1);
if let Some(cortical_id) = source_cortical_id {
state_manager
.subtract_cortical_area_outgoing_synapses(&cortical_id.as_base_64(), 1);
}
if let Some(cortical_id) = target_cortical_id {
state_manager
.subtract_cortical_area_incoming_synapses(&cortical_id.as_base_64(), 1);
}
}
Ok(removed)
}
pub fn batch_create_neurons(
&mut self,
cortical_id: &CorticalID,
neurons: Vec<NeuronData>,
) -> BduResult<Vec<u64>> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let area = self.get_cortical_area(cortical_id).ok_or_else(|| {
BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
})?;
let cortical_idx = area.cortical_idx;
let count = neurons.len();
let mut x_coords = Vec::with_capacity(count);
let mut y_coords = Vec::with_capacity(count);
let mut z_coords = Vec::with_capacity(count);
let mut firing_thresholds = Vec::with_capacity(count);
let mut threshold_limits = Vec::with_capacity(count);
let mut leak_coeffs = Vec::with_capacity(count);
let mut resting_potentials = Vec::with_capacity(count);
let mut neuron_types = Vec::with_capacity(count);
let mut refractory_periods = Vec::with_capacity(count);
let mut excitabilities = Vec::with_capacity(count);
let mut consec_fire_limits = Vec::with_capacity(count);
let mut snooze_lengths = Vec::with_capacity(count);
let mut mp_accums = Vec::with_capacity(count);
let mut cortical_areas = Vec::with_capacity(count);
for (
x,
y,
z,
threshold,
threshold_limit,
leak,
resting,
ntype,
refract,
excit,
consec_limit,
snooze,
mp_accum,
) in neurons
{
x_coords.push(x);
y_coords.push(y);
z_coords.push(z);
firing_thresholds.push(threshold);
threshold_limits.push(threshold_limit);
leak_coeffs.push(leak);
resting_potentials.push(resting);
neuron_types.push(ntype);
refractory_periods.push(refract);
excitabilities.push(excit);
consec_fire_limits.push(consec_limit);
snooze_lengths.push(snooze);
mp_accums.push(mp_accum);
cortical_areas.push(cortical_idx);
}
let first_neuron_id = npu_lock.get_neuron_count() as u32;
let firing_thresholds_t = firing_thresholds;
let threshold_limits_t = threshold_limits;
let resting_potentials_t = resting_potentials;
let (neurons_created, _indices) = npu_lock.add_neurons_batch(
firing_thresholds_t,
threshold_limits_t,
leak_coeffs,
resting_potentials_t,
neuron_types,
refractory_periods,
excitabilities,
consec_fire_limits,
snooze_lengths,
mp_accums,
cortical_areas,
x_coords,
y_coords,
z_coords,
);
let mut neuron_ids = Vec::with_capacity(count);
for i in 0..neurons_created {
neuron_ids.push((first_neuron_id + i) as u64);
}
info!(target: "feagi-bdu","Batch created {} neurons in cortical area {}", count, cortical_id);
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.add_neuron_count(neurons_created);
core_state.add_regular_neuron_count(neurons_created);
state_manager.add_cortical_area_neuron_count(&cortical_id.as_base_64(), count);
{
let mut cache = self.cached_neuron_counts_per_area.write();
cache
.entry(*cortical_id)
.or_insert_with(|| AtomicUsize::new(0))
.fetch_add(count, Ordering::Relaxed);
}
Ok(neuron_ids)
}
pub fn delete_neurons_batch(&mut self, neuron_ids: Vec<u64>) -> BduResult<usize> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let mut deleted_count = 0;
let mut per_area_deleted: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for neuron_id in neuron_ids {
let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32);
let cortical_id =
cortical_idx.and_then(|idx| self.cortical_idx_to_id.get(&idx).cloned());
if npu_lock.delete_neuron(neuron_id as u32) {
deleted_count += 1;
if let Some(cortical_id) = cortical_id {
let key = cortical_id.as_base_64();
*per_area_deleted.entry(key).or_insert(0) += 1;
}
}
}
info!(target: "feagi-bdu","Batch deleted {} neurons", deleted_count);
if deleted_count > 0 {
let state_manager = StateManager::instance();
let state_manager = state_manager.read();
let core_state = state_manager.get_core_state();
core_state.subtract_neuron_count(deleted_count as u32);
core_state.subtract_regular_neuron_count(deleted_count as u32);
for (cortical_id, count) in per_area_deleted {
state_manager.subtract_cortical_area_neuron_count(&cortical_id, count);
}
}
Ok(deleted_count)
}
pub fn update_neuron_properties(
&mut self,
neuron_id: u64,
firing_threshold: Option<f32>,
leak_coefficient: Option<f32>,
resting_potential: Option<f32>,
excitability: Option<f32>,
) -> BduResult<()> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
let neuron_id_u32 = neuron_id as u32;
let mut updated = false;
if let Some(threshold) = firing_threshold {
if npu_lock.update_neuron_threshold(neuron_id_u32, threshold) {
updated = true;
debug!(target: "feagi-bdu","Updated neuron {} firing_threshold = {}", neuron_id, threshold);
} else if !updated {
return Err(BduError::InvalidNeuron(format!(
"Neuron {} not found",
neuron_id
)));
}
}
if let Some(leak) = leak_coefficient {
if npu_lock.update_neuron_leak(neuron_id_u32, leak) {
updated = true;
debug!(target: "feagi-bdu","Updated neuron {} leak_coefficient = {}", neuron_id, leak);
} else if !updated {
return Err(BduError::InvalidNeuron(format!(
"Neuron {} not found",
neuron_id
)));
}
}
if let Some(resting) = resting_potential {
if npu_lock.update_neuron_resting_potential(neuron_id_u32, resting) {
updated = true;
debug!(target: "feagi-bdu","Updated neuron {} resting_potential = {}", neuron_id, resting);
} else if !updated {
return Err(BduError::InvalidNeuron(format!(
"Neuron {} not found",
neuron_id
)));
}
}
if let Some(excit) = excitability {
if npu_lock.update_neuron_excitability(neuron_id_u32, excit) {
updated = true;
debug!(target: "feagi-bdu","Updated neuron {} excitability = {}", neuron_id, excit);
} else if !updated {
return Err(BduError::InvalidNeuron(format!(
"Neuron {} not found",
neuron_id
)));
}
}
if !updated {
return Err(BduError::Internal(
"No properties provided for update".to_string(),
));
}
info!(target: "feagi-bdu","Updated properties for neuron {}", neuron_id);
Ok(())
}
pub fn set_neuron_firing_threshold(
&mut self,
neuron_id: u64,
new_threshold: f32,
) -> BduResult<()> {
let npu = self
.npu
.as_ref()
.ok_or_else(|| BduError::Internal("NPU not connected".to_string()))?;
let mut npu_lock = npu
.lock()
.map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
if npu_lock.update_neuron_threshold(neuron_id as u32, new_threshold) {
debug!(target: "feagi-bdu","Set neuron {} firing threshold = {}", neuron_id, new_threshold);
Ok(())
} else {
Err(BduError::InvalidNeuron(format!(
"Neuron {} not found",
neuron_id
)))
}
}
pub fn get_cortical_area_by_name(&self, name: &str) -> Option<CorticalArea> {
self.cortical_areas
.values()
.find(|area| area.name == name)
.cloned()
}
pub fn resize_cortical_area(
&mut self,
cortical_id: &CorticalID,
new_dimensions: CorticalAreaDimensions,
) -> BduResult<()> {
if new_dimensions.width == 0 || new_dimensions.height == 0 || new_dimensions.depth == 0 {
return Err(BduError::InvalidArea(format!(
"Invalid dimensions: {:?} (all must be > 0)",
new_dimensions
)));
}
let area = self.cortical_areas.get_mut(cortical_id).ok_or_else(|| {
BduError::InvalidArea(format!("Cortical area {} not found", cortical_id))
})?;
let old_dimensions = area.dimensions;
area.dimensions = new_dimensions;
info!(target: "feagi-bdu",
"Resized cortical area {} from {:?} to {:?}",
cortical_id,
old_dimensions,
new_dimensions
);
self.refresh_cortical_area_hashes(false, true);
Ok(())
}
pub fn get_areas_in_region(&self, region_id: &str) -> BduResult<Vec<String>> {
let region = self.brain_regions.get_region(region_id).ok_or_else(|| {
BduError::InvalidArea(format!("Brain region {} not found", region_id))
})?;
Ok(region
.cortical_areas
.iter()
.map(|id| id.as_base_64())
.collect())
}
pub fn update_brain_region(
&mut self,
region_id: &str,
new_name: Option<String>,
new_description: Option<String>,
) -> BduResult<()> {
let region = self
.brain_regions
.get_region_mut(region_id)
.ok_or_else(|| {
BduError::InvalidArea(format!("Brain region {} not found", region_id))
})?;
if let Some(name) = new_name {
region.name = name;
debug!(target: "feagi-bdu","Updated brain region {} name", region_id);
}
if let Some(desc) = new_description {
region
.properties
.insert("description".to_string(), serde_json::json!(desc));
debug!(target: "feagi-bdu","Updated brain region {} description", region_id);
}
info!(target: "feagi-bdu","Updated brain region {}", region_id);
self.refresh_brain_regions_hash();
Ok(())
}
pub fn update_brain_region_properties(
&mut self,
region_id: &str,
properties: std::collections::HashMap<String, serde_json::Value>,
) -> BduResult<Option<BrainRegionIoRegistry>> {
use tracing::{debug, info};
let should_recompute_io = properties
.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
|| properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY);
if properties.contains_key(crate::region_io_designation::DESIGNATED_INPUTS_KEY)
|| properties.contains_key(crate::region_io_designation::DESIGNATED_OUTPUTS_KEY)
{
let region_snapshot = self
.brain_regions
.get_region(region_id)
.ok_or_else(|| {
BduError::InvalidArea(format!("Brain region {} not found", region_id))
})?
.clone();
let (merged_in, merged_out) = crate::region_io_designation::merged_designated_lists(
®ion_snapshot,
&properties,
)?;
crate::region_io_designation::validate_merged_designations_against_connectivity(
self,
®ion_snapshot,
&merged_in,
&merged_out,
)?;
}
let region = self
.brain_regions
.get_region_mut(region_id)
.ok_or_else(|| {
BduError::InvalidArea(format!("Brain region {} not found", region_id))
})?;
for (key, value) in properties {
match key.as_str() {
"title" | "name" | "region_title" => {
if let Some(name) = value.as_str() {
region.name = name.to_string();
debug!(target: "feagi-bdu", "Updated brain region {} name = {}", region_id, name);
}
}
"coordinate_3d" | "coordinates_3d" => {
region
.properties
.insert("coordinate_3d".to_string(), value.clone());
debug!(target: "feagi-bdu", "Updated brain region {} coordinate_3d = {:?}", region_id, value);
}
"coordinate_2d" | "coordinates_2d" => {
region
.properties
.insert("coordinate_2d".to_string(), value.clone());
debug!(target: "feagi-bdu", "Updated brain region {} coordinate_2d = {:?}", region_id, value);
}
"description" => {
region
.properties
.insert("description".to_string(), value.clone());
debug!(target: "feagi-bdu", "Updated brain region {} description", region_id);
}
"region_type" => {
if let Some(type_str) = value.as_str() {
region.region_type = feagi_structures::genomic::RegionType::Undefined;
debug!(target: "feagi-bdu", "Updated brain region {} type = {}", region_id, type_str);
}
}
_ => {
region.properties.insert(key.clone(), value.clone());
debug!(target: "feagi-bdu", "Updated brain region {} property {} = {:?}", region_id, key, value);
}
}
}
info!(target: "feagi-bdu", "Updated brain region {} properties", region_id);
if should_recompute_io {
let registry = self.recompute_brain_region_io_registry()?;
return Ok(Some(registry));
}
self.refresh_brain_regions_hash();
Ok(None)
}
pub fn get_neuron_by_coordinates(
&self,
cortical_id: &CorticalID,
x: u32,
y: u32,
z: u32,
) -> Option<u64> {
let area = self.get_cortical_area(cortical_id)?;
let cortical_idx = area.cortical_idx;
let npu = self.npu.as_ref()?;
let npu_lock = npu.lock().ok()?;
npu_lock
.get_neuron_id_at_coordinate(cortical_idx, x, y, z)
.map(|id| id as u64)
}
pub fn get_neuron_position(&self, neuron_id: u64) -> Option<(u32, u32, u32)> {
let npu = self.npu.as_ref()?;
let npu_lock = npu.lock().ok()?;
let neuron_count = npu_lock.get_neuron_count();
if (neuron_id as usize) >= neuron_count {
return None;
}
Some(
npu_lock
.get_neuron_coordinates(neuron_id as u32)
.unwrap_or((0, 0, 0)),
)
}
pub fn get_cortical_area_for_neuron(&self, neuron_id: u64) -> Option<CorticalID> {
let npu = self.npu.as_ref()?;
let npu_lock = npu.lock().ok()?;
let neuron_count = npu_lock.get_neuron_count();
if (neuron_id as usize) >= neuron_count {
return None;
}
let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id as u32)?;
self.cortical_areas
.values()
.find(|area| area.cortical_idx == cortical_idx)
.map(|area| area.cortical_id)
}
pub fn get_neuron_properties(
&self,
neuron_id: u64,
) -> Option<std::collections::HashMap<String, serde_json::Value>> {
let npu = self.npu.as_ref()?;
let npu_lock = npu.lock().ok()?;
let neuron_id_u32 = neuron_id as u32;
let idx = neuron_id as usize;
let neuron_count = npu_lock.get_neuron_count();
if idx >= neuron_count {
return None;
}
let mut properties = std::collections::HashMap::new();
properties.insert("neuron_id".to_string(), serde_json::json!(neuron_id));
let (x, y, z) = npu_lock.get_neuron_coordinates(neuron_id_u32)?;
properties.insert("x".to_string(), serde_json::json!(x));
properties.insert("y".to_string(), serde_json::json!(y));
properties.insert("z".to_string(), serde_json::json!(z));
let cortical_idx = npu_lock.get_neuron_cortical_area(neuron_id_u32)?;
properties.insert("cortical_area".to_string(), serde_json::json!(cortical_idx));
properties.insert(
"mp_charge_accumulation".to_string(),
serde_json::json!(npu_lock.get_mp_charge_accumulation_at(idx).unwrap_or(false)),
);
properties.insert(
"neuron_type".to_string(),
serde_json::json!(npu_lock.get_neuron_type_at(idx).unwrap_or(0)),
);
let (mp_drv, psp_uni) = self
.cortical_idx_to_id
.get(&cortical_idx)
.map(|cid| {
(
npu_lock.get_mp_driven_psp_for_cortical(cid),
npu_lock.get_psp_uniform_distribution_for_cortical(cid),
)
})
.unwrap_or((false, false));
properties.insert("mp_driven_psp".to_string(), serde_json::json!(mp_drv));
properties.insert(
"psp_uniform_distribution".to_string(),
serde_json::json!(psp_uni),
);
let (consec_count, consec_limit, snooze, mp, threshold, refract_countdown) = npu_lock
.get_neuron_state(NeuronId(neuron_id_u32))
.unwrap_or((0u16, 0u16, 0u16, 0.0f32, 0.0f32, 0u16));
properties.insert(
"consecutive_fire_count".to_string(),
serde_json::json!(consec_count),
);
properties.insert(
"consecutive_fire_limit".to_string(),
serde_json::json!(consec_limit),
);
properties.insert("snooze_period".to_string(), serde_json::json!(snooze));
properties.insert("membrane_potential".to_string(), serde_json::json!(mp));
properties.insert("threshold".to_string(), serde_json::json!(threshold));
properties.insert(
"refractory_countdown".to_string(),
serde_json::json!(refract_countdown),
);
properties.insert(
"leak_coefficient".to_string(),
serde_json::json!(npu_lock
.get_neuron_property_by_index(idx, "leak_coefficient")
.unwrap_or(0.0)),
);
properties.insert(
"resting_potential".to_string(),
serde_json::json!(npu_lock
.get_neuron_property_by_index(idx, "resting_potential")
.unwrap_or(0.0)),
);
properties.insert(
"excitability".to_string(),
serde_json::json!(npu_lock
.get_neuron_property_by_index(idx, "excitability")
.unwrap_or(0.0)),
);
properties.insert(
"threshold_limit".to_string(),
serde_json::json!(npu_lock
.get_neuron_property_by_index(idx, "threshold_limit")
.unwrap_or(0.0)),
);
properties.insert(
"refractory_period".to_string(),
serde_json::json!(npu_lock
.get_neuron_property_u16_by_index(idx, "refractory_period")
.unwrap_or(0)),
);
Some(properties)
}
pub fn get_neuron_property(
&self,
neuron_id: u64,
property_name: &str,
) -> Option<serde_json::Value> {
self.get_neuron_properties(neuron_id)?
.get(property_name)
.cloned()
}
pub fn get_all_cortical_ids(&self) -> Vec<CorticalID> {
self.cortical_areas.keys().copied().collect()
}
pub fn get_all_cortical_indices(&self) -> Vec<u32> {
self.cortical_areas
.values()
.map(|area| area.cortical_idx)
.collect()
}
pub fn get_cortical_area_names(&self) -> Vec<String> {
self.cortical_areas
.values()
.map(|area| area.name.clone())
.collect()
}
pub fn list_ipu_areas(&self) -> Vec<CorticalID> {
use crate::models::CorticalAreaExt;
self.cortical_areas
.values()
.filter(|area| area.is_input_area())
.map(|area| area.cortical_id)
.collect()
}
pub fn list_opu_areas(&self) -> Vec<CorticalID> {
use crate::models::CorticalAreaExt;
self.cortical_areas
.values()
.filter(|area| area.is_output_area())
.map(|area| area.cortical_id)
.collect()
}
pub fn get_max_cortical_area_dimensions(&self) -> (usize, usize, usize) {
self.cortical_areas
.values()
.fold((0, 0, 0), |(max_w, max_h, max_d), area| {
(
max_w.max(area.dimensions.width as usize),
max_h.max(area.dimensions.height as usize),
max_d.max(area.dimensions.depth as usize),
)
})
}
pub fn get_cortical_area_properties(
&self,
cortical_id: &CorticalID,
) -> Option<std::collections::HashMap<String, serde_json::Value>> {
let area = self.get_cortical_area(cortical_id)?;
let mut properties = std::collections::HashMap::new();
properties.insert(
"cortical_id".to_string(),
serde_json::json!(area.cortical_id),
);
properties.insert(
"cortical_id_s".to_string(),
serde_json::json!(area.cortical_id.to_string()),
);
properties.insert(
"cortical_idx".to_string(),
serde_json::json!(area.cortical_idx),
);
properties.insert("name".to_string(), serde_json::json!(area.name));
use crate::models::CorticalAreaExt;
properties.insert(
"area_type".to_string(),
serde_json::json!(area.get_cortical_group()),
);
properties.insert(
"dimensions".to_string(),
serde_json::json!({
"width": area.dimensions.width,
"height": area.dimensions.height,
"depth": area.dimensions.depth,
}),
);
properties.insert("position".to_string(), serde_json::json!(area.position));
for (key, value) in &area.properties {
properties.insert(key.clone(), value.clone());
}
properties.extend(area.properties.clone());
Some(properties)
}
pub fn get_all_cortical_area_properties(
&self,
) -> Vec<std::collections::HashMap<String, serde_json::Value>> {
self.cortical_areas
.keys()
.filter_map(|id| self.get_cortical_area_properties(id))
.collect()
}
pub fn get_all_brain_region_ids(&self) -> Vec<String> {
self.brain_regions
.get_all_region_ids()
.into_iter()
.cloned()
.collect()
}
pub fn get_brain_region_names(&self) -> Vec<String> {
self.brain_regions
.get_all_region_ids()
.iter()
.filter_map(|id| {
self.brain_regions
.get_region(id)
.map(|region| region.name.clone())
})
.collect()
}
pub fn get_brain_region_properties(
&self,
region_id: &str,
) -> Option<std::collections::HashMap<String, serde_json::Value>> {
let region = self.brain_regions.get_region(region_id)?;
let mut properties = std::collections::HashMap::new();
properties.insert("region_id".to_string(), serde_json::json!(region.region_id));
properties.insert("name".to_string(), serde_json::json!(region.name));
properties.insert(
"region_type".to_string(),
serde_json::json!(format!("{:?}", region.region_type)),
);
properties.insert(
"cortical_areas".to_string(),
serde_json::json!(region.cortical_areas.iter().collect::<Vec<_>>()),
);
properties.extend(region.properties.clone());
Some(properties)
}
pub fn cortical_area_exists(&self, cortical_id: &CorticalID) -> bool {
self.cortical_areas.contains_key(cortical_id)
}
pub fn brain_region_exists(&self, region_id: &str) -> bool {
self.brain_regions.get_region(region_id).is_some()
}
pub fn get_brain_region_count(&self) -> usize {
self.brain_regions.region_count()
}
pub fn get_neurons_by_cortical_area(&self, cortical_id: &CorticalID) -> Vec<u64> {
self.get_neurons_in_area(cortical_id)
}
}
impl std::fmt::Debug for ConnectomeManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConnectomeManager")
.field("cortical_areas", &self.cortical_areas.len())
.field("next_cortical_idx", &self.next_cortical_idx)
.field("brain_regions", &self.brain_regions)
.field(
"npu",
&if self.npu.is_some() {
"Connected"
} else {
"Not connected"
},
)
.field("initialized", &self.initialized)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use feagi_structures::genomic::cortical_area::CoreCorticalType;
#[cfg(feature = "plasticity")]
#[test]
fn prepare_for_new_genome_discards_memory_neurons_from_previous_brain() {
use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
use feagi_npu_plasticity::executor::PlasticityExecutor;
use feagi_npu_plasticity::{
create_memory_stats_cache, AsyncPlasticityExecutor, MemoryNeuronDetail,
PlasticityConfig,
};
use feagi_npu_runtime::StdRuntime;
let npu = Arc::new(TracingMutex::new(
DynamicNPU::new_f32(
StdRuntime::new(),
feagi_npu_burst_engine::backend::CPUBackend::new(),
16,
16,
8,
)
.unwrap(),
"prepare-for-new-genome-test-npu",
));
let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
PlasticityConfig::default(),
create_memory_stats_cache(),
npu.clone(),
)));
let mut manager = ConnectomeManager::new_for_testing();
manager.set_npu(npu);
manager.set_plasticity_executor(executor.clone());
let previous_brain_memory_idx = 8;
{
let exec = executor.lock().expect("plasticity executor");
PlasticityExecutor::register_memory_area(
&*exec,
previous_brain_memory_idx,
"previous_brain_memory".to_string(),
1,
vec![7],
None,
false,
);
exec.restore_long_term_memory_neurons(&[MemoryNeuronDetail {
neuron_id: 50_000_003,
cortical_area_idx: previous_brain_memory_idx,
pattern_hash: Some(9_631_261_403_772_054_764),
is_longterm_memory: true,
is_active: true,
lifespan_current: 120,
lifespan_initial: 20,
lifespan_growth_rate: 3.0,
creation_burst: 0,
last_activation_burst: 0,
activation_count: 5,
spatial_signature: None,
class_channels: Vec::new(),
}])
.expect("seeded long-term memory neuron");
assert_eq!(
exec.export_long_term_memory_neurons()
.expect("plasticity service is initialized")
.len(),
1
);
}
manager
.prepare_for_new_genome()
.expect("genome preparation must succeed");
let exec = executor.lock().expect("plasticity executor");
assert!(
exec.export_long_term_memory_neurons()
.expect("plasticity service is initialized")
.is_empty(),
"memory neurons from the previous brain must not survive a genome load"
);
assert_eq!(
exec.paginated_memory_neuron_ids_in_area(previous_brain_memory_idx, 0, 10),
Some((Vec::new(), 0)),
"the previous brain's memory area must no longer report any memory neurons"
);
}
#[cfg(feature = "plasticity")]
#[test]
fn remove_cortical_area_unregisters_its_memory() {
use feagi_npu_burst_engine::{DynamicNPU, TracingMutex};
use feagi_npu_plasticity::executor::PlasticityExecutor;
use feagi_npu_plasticity::{
create_memory_stats_cache, AsyncPlasticityExecutor, PlasticityConfig,
};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
};
let npu = Arc::new(TracingMutex::new(
DynamicNPU::new_f32(
StdRuntime::new(),
feagi_npu_burst_engine::backend::CPUBackend::new(),
16,
16,
8,
)
.unwrap(),
"remove-memory-area-test-npu",
));
let executor = Arc::new(std::sync::Mutex::new(AsyncPlasticityExecutor::new(
PlasticityConfig::default(),
create_memory_stats_cache(),
npu.clone(),
)));
let mut manager = ConnectomeManager::new_for_testing();
manager.set_npu(npu);
manager.set_plasticity_executor(executor.clone());
let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
let mut area = CorticalArea::new(
mem_id,
0,
"kernel_mem".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
area.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
let idx = manager.add_cortical_area(area).unwrap();
{
let exec = executor.lock().expect("plasticity executor");
PlasticityExecutor::register_memory_area(
&*exec,
idx,
mem_id.as_base_64(),
1,
vec![7],
None,
false,
);
}
manager
.remove_cortical_area(&mem_id)
.expect("memory area removal");
assert!(manager.get_cortical_id(idx).is_none());
let exec = executor.lock().expect("plasticity executor");
let indexes = exec
.get_service()
.expect("plasticity service")
.registered_memory_area_indexes();
assert!(
!indexes.contains(&idx),
"deleted memory area idx={idx} must leave plasticity, still registered: {indexes:?}"
);
}
#[test]
fn test_singleton_instance() {
let instance1 = ConnectomeManager::instance();
let instance2 = ConnectomeManager::instance();
assert_eq!(Arc::strong_count(&instance1), Arc::strong_count(&instance2));
}
#[test]
fn test_add_cortical_area() {
ConnectomeManager::reset_for_testing();
let instance = ConnectomeManager::instance();
let mut manager = instance.write();
use feagi_structures::genomic::cortical_area::{
CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
let cortical_id = CorticalID::try_from_bytes(b"cst_add_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
let area = CorticalArea::new(
cortical_id,
0,
"Visual Input".to_string(),
CorticalAreaDimensions::new(128, 128, 20).unwrap(),
(0, 0, 0).into(),
cortical_type,
)
.unwrap();
let initial_count = manager.get_cortical_area_count();
let _cortical_idx = manager.add_cortical_area(area).unwrap();
assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
assert!(manager.has_cortical_area(&cortical_id));
assert!(manager.is_initialized());
}
#[test]
fn refresh_all_connectome_hashes_publishes_mappings() {
use feagi_structures::genomic::cortical_area::{
CorticalArea, CorticalAreaDimensions, CorticalAreaType, CustomCorticalType,
};
let src_id = CorticalID::try_from_bytes(b"chashsrc").unwrap();
let dst_id = CorticalID::try_from_bytes(b"chashdst").unwrap();
let mut src = CorticalArea::new(
src_id,
1,
"src".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap();
src.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
dst_id.as_base_64(): [{ "morphology_id": "projector" }]
}),
);
let mut manager = ConnectomeManager::new_for_testing();
manager.add_cortical_area(src).unwrap();
manager.refresh_all_connectome_hashes();
let mappings_hash = feagi_state_manager::StateManager::instance()
.read()
.get_cortical_mappings_hash();
assert_ne!(
mappings_hash, 0,
"refresh_all_connectome_hashes must publish cortical_mappings_hash"
);
manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
classifier_id: "clf-hash".to_string(),
name: "hash_demo".to_string(),
parent_region_id: "root".to_string(),
coordinates_3d: [0, 0, 0],
kernel_area_id: None,
class_area_id: None,
fields: Vec::new(),
kernel_memory_id: "mkmem1".to_string(),
class_memory_id: "mcmem1".to_string(),
properties: HashMap::new(),
});
manager.refresh_all_connectome_hashes();
let classifiers_hash = feagi_state_manager::StateManager::instance()
.read()
.get_classifiers_hash();
assert_ne!(
classifiers_hash, 0,
"refresh_all_connectome_hashes must publish classifiers_hash"
);
}
#[test]
fn test_cortical_area_lookups() {
ConnectomeManager::reset_for_testing();
let instance = ConnectomeManager::instance();
let mut manager = instance.write();
use feagi_structures::genomic::cortical_area::{
CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
let cortical_id = CorticalID::try_from_bytes(b"cst_look").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
let area = CorticalArea::new(
cortical_id,
0,
"Test Area".to_string(),
CorticalAreaDimensions::new(10, 10, 10).unwrap(),
(0, 0, 0).into(),
cortical_type,
)
.unwrap();
let cortical_idx = manager.add_cortical_area(area).unwrap();
assert_eq!(manager.get_cortical_idx(&cortical_id), Some(cortical_idx));
assert_eq!(manager.get_cortical_id(cortical_idx), Some(&cortical_id));
let retrieved_area = manager.get_cortical_area(&cortical_id).unwrap();
assert_eq!(retrieved_area.name, "Test Area");
}
#[test]
fn test_remove_cortical_area() {
ConnectomeManager::reset_for_testing();
let instance = ConnectomeManager::instance();
let mut manager = instance.write();
use feagi_structures::genomic::cortical_area::{
CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
let cortical_id = CoreCorticalType::Power.to_cortical_id();
if manager.has_cortical_area(&cortical_id) {
manager.remove_cortical_area(&cortical_id).unwrap();
}
let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
let area = CorticalArea::new(
cortical_id,
0,
"Test".to_string(),
CorticalAreaDimensions::new(10, 10, 10).unwrap(),
(0, 0, 0).into(),
cortical_type,
)
.unwrap();
let initial_count = manager.get_cortical_area_count();
manager.add_cortical_area(area).unwrap();
assert_eq!(manager.get_cortical_area_count(), initial_count + 1);
let areas_hash_before = feagi_state_manager::StateManager::instance()
.read()
.get_cortical_areas_hash();
let geometry_hash_before = feagi_state_manager::StateManager::instance()
.read()
.get_brain_geometry_hash();
manager.remove_cortical_area(&cortical_id).unwrap();
assert_eq!(manager.get_cortical_area_count(), initial_count);
assert!(!manager.has_cortical_area(&cortical_id));
let areas_hash_after = feagi_state_manager::StateManager::instance()
.read()
.get_cortical_areas_hash();
let geometry_hash_after = feagi_state_manager::StateManager::instance()
.read()
.get_brain_geometry_hash();
assert_ne!(
areas_hash_before, areas_hash_after,
"remove_cortical_area must publish a new cortical_areas_hash"
);
assert_ne!(
geometry_hash_before, geometry_hash_after,
"remove_cortical_area must publish a new brain_geometry_hash"
);
}
#[test]
fn test_duplicate_area_error() {
ConnectomeManager::reset_for_testing();
let instance = ConnectomeManager::instance();
let mut manager = instance.write();
use feagi_structures::genomic::cortical_area::{
CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
let cortical_id = CorticalID::try_from_bytes(b"cst_dup1").unwrap();
let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
let area1 = CorticalArea::new(
cortical_id,
0,
"First".to_string(),
CorticalAreaDimensions::new(10, 10, 10).unwrap(),
(0, 0, 0).into(),
cortical_type,
)
.unwrap();
let area2 = CorticalArea::new(
cortical_id, 1,
"Second".to_string(),
CorticalAreaDimensions::new(10, 10, 10).unwrap(),
(0, 0, 0).into(),
cortical_type,
)
.unwrap();
manager.add_cortical_area(area1).unwrap();
let result = manager.add_cortical_area(area2);
assert!(result.is_err());
}
#[test]
fn test_brain_region_management() {
ConnectomeManager::reset_for_testing();
let instance = ConnectomeManager::instance();
let mut manager = instance.write();
let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
let region_id_str = region_id.to_string();
let root = BrainRegion::new(
region_id,
"Root".to_string(),
feagi_structures::genomic::brain_regions::RegionType::Undefined,
)
.unwrap();
let initial_count = manager.get_brain_region_ids().len();
manager.add_brain_region(root, None).unwrap();
assert_eq!(manager.get_brain_region_ids().len(), initial_count + 1);
assert!(manager.get_brain_region(®ion_id_str).is_some());
}
#[test]
fn test_update_brain_region_description_property() {
ConnectomeManager::reset_for_testing();
let instance = ConnectomeManager::instance();
let mut manager = instance.write();
let region_id = feagi_structures::genomic::brain_regions::RegionID::new();
let region_id_str = region_id.to_string();
let root = BrainRegion::new(
region_id,
"Root".to_string(),
feagi_structures::genomic::brain_regions::RegionType::Undefined,
)
.unwrap();
manager.add_brain_region(root, None).unwrap();
let mut properties = std::collections::HashMap::new();
properties.insert(
"description".to_string(),
serde_json::json!("Holds core physiology"),
);
manager
.update_brain_region_properties(®ion_id_str, properties)
.unwrap();
let updated = manager
.get_brain_region(®ion_id_str)
.expect("region exists after description update");
assert_eq!(
updated.get_property("description"),
Some(&serde_json::json!("Holds core physiology"))
);
}
#[test]
fn test_synapse_operations() {
use feagi_npu_burst_engine::npu::RustNPU;
use feagi_npu_burst_engine::TracingMutex;
use std::sync::Arc;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::DynamicNPU;
use feagi_npu_runtime::StdRuntime;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu_result =
RustNPU::new(runtime, backend, 100, 1000, 10).expect("Failed to create NPU");
let npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu_result), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(npu.clone());
use feagi_structures::genomic::cortical_area::{
CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
let cortical_id = CorticalID::try_from_bytes(b"cst_syn_").unwrap(); let cortical_type = CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean);
let area = CorticalArea::new(
cortical_id,
0, "Test Area".to_string(),
CorticalAreaDimensions::new(10, 10, 1).unwrap(),
(0, 0, 0).into(), cortical_type,
)
.unwrap();
let cortical_idx = manager.add_cortical_area(area).unwrap();
if let Some(npu_arc) = manager.get_npu() {
if let Ok(mut npu_guard) = npu_arc.try_lock() {
if let DynamicNPU::F32(ref mut npu) = *npu_guard {
npu.register_cortical_area(cortical_idx, cortical_id.as_base_64());
}
}
}
let neuron1_id = manager
.add_neuron(
&cortical_id,
0,
0,
0, 100.0, 0.0, 0.1, -60.0, 0, 2, 1.0, 5, 10, false, )
.unwrap();
let neuron2_id = manager
.add_neuron(
&cortical_id,
1,
0,
0, 100.0,
f32::MAX, 0.1,
-60.0,
0,
2,
1.0,
5,
10,
false,
)
.unwrap();
manager
.create_synapse(
neuron1_id, neuron2_id, 128.0, 64.0, 0, )
.unwrap();
println!("✅ Synapse creation test passed");
}
#[test]
fn test_apply_cortical_mapping_missing_rules_is_ok() {
let mut manager = ConnectomeManager::new_for_testing();
use feagi_structures::genomic::cortical_area::{
CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
let src_id = CorticalID::try_from_bytes(b"map_src_").unwrap();
let dst_id = CorticalID::try_from_bytes(b"map_dst_").unwrap();
let src_area = CorticalArea::new(
src_id,
0,
"src".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let dst_area = CorticalArea::new(
dst_id,
1,
"dst".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
manager.add_cortical_area(src_area).unwrap();
manager.add_cortical_area(dst_area).unwrap();
let count = manager
.apply_cortical_mapping_for_pair(&src_id, &dst_id)
.unwrap();
assert_eq!(count, 0);
manager
.update_cortical_mapping(
&src_id,
&dst_id,
vec![serde_json::json!({"morphology_id":"m1"})],
)
.unwrap();
manager
.update_cortical_mapping(&src_id, &dst_id, vec![])
.unwrap();
let count2 = manager
.apply_cortical_mapping_for_pair(&src_id, &dst_id)
.unwrap();
assert_eq!(count2, 0);
}
#[test]
fn test_get_mapping_rules_for_destination_supports_legacy_key() {
let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
let mapping_dst = serde_json::json!({
"csrc0002": [
{"morphology_id": "m1"}
]
});
let mapping_obj = mapping_dst.as_object().expect("mapping must be an object");
let rules = ConnectomeManager::get_mapping_rules_for_destination(mapping_obj, &dst_id)
.expect("legacy destination key should resolve");
assert_eq!(rules.len(), 1);
assert_eq!(
rules[0].get("morphology_id").and_then(|v| v.as_str()),
Some("m1")
);
}
#[test]
fn test_get_neuron_properties_always_includes_neuron_state_keys() {
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::RustNPU;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(
feagi_npu_burst_engine::DynamicNPU::F32(npu),
"TestNPU",
));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
let area_id = CorticalID::try_from_bytes(b"cst_nsp_").unwrap();
let area = CorticalArea::new(
area_id,
0,
"n".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
manager.add_cortical_area(area).unwrap();
let nid = manager
.add_neuron(
&area_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false,
)
.unwrap();
let props = manager
.get_neuron_properties(nid)
.expect("neuron properties");
for key in [
"consecutive_fire_count",
"consecutive_fire_limit",
"snooze_period",
"membrane_potential",
"threshold",
"refractory_countdown",
"mp_charge_accumulation",
"neuron_type",
"mp_driven_psp",
"psp_uniform_distribution",
"leak_coefficient",
"resting_potential",
"excitability",
"threshold_limit",
"refractory_period",
] {
assert!(props.contains_key(key), "missing neuron state key: {key}");
}
}
#[test]
fn test_mapping_deletion_prunes_synapses_between_areas() {
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::RustNPU;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(
feagi_npu_burst_engine::DynamicNPU::F32(npu),
"TestNPU",
));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
let src_id = CorticalID::try_from_bytes(b"cst_src_").unwrap();
let dst_id = CorticalID::try_from_bytes(b"cst_dst_").unwrap();
let src_area = CorticalArea::new(
src_id,
0,
"src".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let dst_area = CorticalArea::new(
dst_id,
1,
"dst".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
manager.add_cortical_area(src_area).unwrap();
manager.add_cortical_area(dst_area).unwrap();
let s0 = manager
.add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let s1 = manager
.add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let t0 = manager
.add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let t1 = manager
.add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
{
let mut npu = dyn_npu.lock().unwrap();
npu.rebuild_synapse_index();
assert_eq!(npu.get_synapse_count(), 2);
}
manager
.update_cortical_mapping(&src_id, &dst_id, vec![])
.unwrap();
let created = manager
.regenerate_synapses_for_mapping(&src_id, &dst_id)
.unwrap();
assert_eq!(created, 0);
{
let mut npu = dyn_npu.lock().unwrap();
npu.rebuild_synapse_index();
assert_eq!(npu.get_synapse_count(), 0);
assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
}
}
#[test]
fn test_mapping_update_prunes_synapses_between_areas() {
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::RustNPU;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, IOCorticalAreaConfigurationFlag,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(
feagi_npu_burst_engine::DynamicNPU::F32(npu),
"TestNPU",
));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let src_id = CorticalID::try_from_bytes(b"cstupds1").unwrap();
let dst_id = CorticalID::try_from_bytes(b"cstupdt1").unwrap();
let src_area = CorticalArea::new(
src_id,
0,
"src".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let dst_area = CorticalArea::new(
dst_id,
0,
"dst".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainOutput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
manager.add_cortical_area(src_area).unwrap();
manager.add_cortical_area(dst_area).unwrap();
let s0 = manager
.add_neuron(&src_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let s1 = manager
.add_neuron(&src_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let t0 = manager
.add_neuron(&dst_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let t1 = manager
.add_neuron(&dst_id, 1, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
manager.create_synapse(s0, t0, 128.0, 200.0, 0).unwrap();
manager.create_synapse(s1, t1, 128.0, 200.0, 0).unwrap();
{
let mut npu = dyn_npu.lock().unwrap();
npu.rebuild_synapse_index();
assert_eq!(npu.get_synapse_count(), 2);
}
manager
.update_cortical_mapping(
&src_id,
&dst_id,
vec![serde_json::json!({
"morphology_id": "episodic_memory",
"morphology_scalar": [1],
"postSynapticCurrent_multiplier": 1,
"plasticity_flag": false,
"plasticity_constant": 0,
"ltp_multiplier": 0,
"ltd_multiplier": 0,
"plasticity_window": 0,
})],
)
.unwrap();
let created = manager
.regenerate_synapses_for_mapping(&src_id, &dst_id)
.unwrap();
assert_eq!(created, 0);
{
let mut npu = dyn_npu.lock().unwrap();
npu.rebuild_synapse_index();
assert_eq!(npu.get_synapse_count(), 0);
assert!(npu.get_outgoing_synapses(s0 as u32).is_empty());
assert!(npu.get_outgoing_synapses(s1 as u32).is_empty());
}
}
#[test]
fn test_upstream_area_tracking() {
use crate::models::cortical_area::CorticalArea;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID,
};
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let src_id = CorticalID::try_from_bytes(b"csrc0000").unwrap();
let src_area = CorticalArea::new(
src_id,
0,
"Source Area".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(
feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
),
)
.unwrap();
let src_idx = manager.add_cortical_area(src_area).unwrap();
let dst_id = CorticalID::try_from_bytes(b"cdst0000").unwrap();
let dst_area = CorticalArea::new(
dst_id,
0,
"Dest Area".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(
feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
),
)
.unwrap();
manager.add_cortical_area(dst_area).unwrap();
{
let dst_area = manager.get_cortical_area(&dst_id).unwrap();
let upstream = dst_area.properties.get("upstream_cortical_areas").unwrap();
assert!(
upstream.as_array().unwrap().is_empty(),
"Upstream areas should be empty initially"
);
}
let mapping_data = vec![serde_json::json!({
"morphology_id": "episodic_memory",
"morphology_scalar": 1,
"postSynapticCurrent_multiplier": 1.0,
})];
manager
.update_cortical_mapping(&src_id, &dst_id, mapping_data)
.unwrap();
manager
.regenerate_synapses_for_mapping(&src_id, &dst_id)
.unwrap();
{
let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
assert_eq!(upstream_areas.len(), 1, "Should have 1 upstream area");
assert_eq!(
upstream_areas[0], src_idx,
"Upstream area should be src_idx"
);
}
manager
.update_cortical_mapping(&src_id, &dst_id, vec![])
.unwrap();
manager
.regenerate_synapses_for_mapping(&src_id, &dst_id)
.unwrap();
{
let upstream_areas = manager.get_upstream_cortical_areas(&dst_id);
assert_eq!(
upstream_areas.len(),
0,
"Should have 0 upstream areas after deletion"
);
}
}
#[test]
fn test_refresh_upstream_areas_for_associative_memory_pairs() {
use crate::models::cortical_area::CorticalArea;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let a1_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
let a2_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
let m1_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
let m2_id = CorticalID::try_from_bytes(b"mmem0003").unwrap();
let a1_area = CorticalArea::new(
a1_id,
0,
"A1".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(
feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
),
)
.unwrap();
let a2_area = CorticalArea::new(
a2_id,
0,
"A2".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(
feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
),
)
.unwrap();
let mut m1_area = CorticalArea::new(
m1_id,
0,
"M1".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
m1_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
m1_area
.properties
.insert("temporal_depth".to_string(), serde_json::json!(1));
let mut m2_area = CorticalArea::new(
m2_id,
0,
"M2".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
m2_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
m2_area
.properties
.insert("temporal_depth".to_string(), serde_json::json!(1));
let a1_idx = manager.add_cortical_area(a1_area).unwrap();
let a2_idx = manager.add_cortical_area(a2_area).unwrap();
let m1_idx = manager.add_cortical_area(m1_area).unwrap();
let m2_idx = manager.add_cortical_area(m2_area).unwrap();
manager
.add_neuron(&a1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
manager
.add_neuron(&a2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let episodic_mapping = vec![serde_json::json!({
"morphology_id": "episodic_memory",
"morphology_scalar": 1,
"postSynapticCurrent_multiplier": 1.0,
})];
manager
.update_cortical_mapping(&a1_id, &m1_id, episodic_mapping.clone())
.unwrap();
manager
.regenerate_synapses_for_mapping(&a1_id, &m1_id)
.unwrap();
manager
.update_cortical_mapping(&a2_id, &m2_id, episodic_mapping)
.unwrap();
manager
.regenerate_synapses_for_mapping(&a2_id, &m2_id)
.unwrap();
let assoc_mapping = vec![serde_json::json!({
"morphology_id": "associative_memory",
"morphology_scalar": 1,
"postSynapticCurrent_multiplier": 1.0,
"plasticity_flag": true,
"plasticity_constant": 1,
"ltp_multiplier": 1,
"ltd_multiplier": 1,
"plasticity_window": 5,
})];
manager
.update_cortical_mapping(&m1_id, &m2_id, assoc_mapping.clone())
.unwrap();
manager
.regenerate_synapses_for_mapping(&m1_id, &m2_id)
.unwrap();
manager
.update_cortical_mapping(&m2_id, &m1_id, assoc_mapping)
.unwrap();
manager
.regenerate_synapses_for_mapping(&m2_id, &m1_id)
.unwrap();
let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
assert_eq!(
upstream_m1.len(),
2,
"M1 should have A1 and M2 as upstreams once both directed associative edges exist"
);
assert_eq!(
upstream_m2.len(),
2,
"M2 should have A2 and M1 as upstreams"
);
manager.refresh_upstream_cortical_areas_from_mappings(&m1_id);
manager.refresh_upstream_cortical_areas_from_mappings(&m2_id);
let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
assert_eq!(upstream_m1.len(), 2, "M1 upstreams unchanged after refresh");
assert_eq!(upstream_m2.len(), 2, "M2 upstreams unchanged after refresh");
assert!(upstream_m1.contains(&a1_idx));
assert!(upstream_m1.contains(&m2_idx));
assert!(upstream_m2.contains(&a2_idx));
assert!(upstream_m2.contains(&m1_idx));
{
let mut npu_lock = dyn_npu.lock().unwrap();
let injected_a1 = npu_lock.inject_sensory_xyzp_by_id(&a1_id, &[(0, 0, 0, 1.0)]);
let injected_a2 = npu_lock.inject_sensory_xyzp_by_id(&a2_id, &[(0, 0, 0, 1.0)]);
assert_eq!(injected_a1, 1, "Expected A1 injection to match one neuron");
assert_eq!(injected_a2, 1, "Expected A2 injection to match one neuron");
npu_lock.process_burst().expect("Burst processing failed");
}
let upstream_m1 = manager.get_upstream_cortical_areas(&m1_id);
let upstream_m2 = manager.get_upstream_cortical_areas(&m2_id);
assert_eq!(
upstream_m1.len(),
2,
"M1 should keep 2 upstreams after firing"
);
assert_eq!(
upstream_m2.len(),
2,
"M2 should keep 2 upstreams after firing"
);
let episodic_upstream_m1 = manager.get_episodic_memory_upstream_cortical_areas(&m1_id);
let episodic_upstream_m2 = manager.get_episodic_memory_upstream_cortical_areas(&m2_id);
assert_eq!(
episodic_upstream_m1,
vec![a1_idx],
"Episodic upstream list for M1 should exclude associative-only memory source M2"
);
assert_eq!(
episodic_upstream_m2,
vec![a2_idx],
"Episodic upstream list for M2 should exclude associative-only memory source M1"
);
}
#[test]
fn test_memory_to_non_memory_requires_associative_morphology() {
use crate::models::cortical_area::CorticalArea;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
MemoryCorticalType,
};
let mut manager = ConnectomeManager::new_for_testing();
let memory_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
let destination_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
let memory_area = CorticalArea::new(
memory_id,
0,
"Memory Area".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
let destination_area = CorticalArea::new(
destination_id,
0,
"Destination Area".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(1, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
manager.add_cortical_area(memory_area).unwrap();
manager.add_cortical_area(destination_area).unwrap();
let invalid = manager.update_cortical_mapping(
&memory_id,
&destination_id,
vec![serde_json::json!({"morphology_id": "episodic_memory"})],
);
assert!(matches!(invalid, Err(BduError::InvalidMorphology(_))));
manager
.update_cortical_mapping(
&memory_id,
&destination_id,
vec![serde_json::json!({"morphology_id": "associative_memory"})],
)
.unwrap();
}
#[test]
fn test_memory_twin_created_for_memory_mapping() {
use crate::models::cortical_area::CorticalArea;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
MemoryCorticalType,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
let dst_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
let src_area = CorticalArea::new(
src_id,
0,
"Source Area".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let mut dst_area = CorticalArea::new(
dst_id,
0,
"Memory Area".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
dst_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
dst_area
.properties
.insert("temporal_depth".to_string(), serde_json::json!(1));
manager.add_cortical_area(src_area).unwrap();
manager.add_cortical_area(dst_area).unwrap();
let mapping_data = vec![serde_json::json!({
"morphology_id": "episodic_memory",
"morphology_scalar": 1,
"postSynapticCurrent_multiplier": 1.0,
})];
manager
.update_cortical_mapping(&src_id, &dst_id, mapping_data)
.unwrap();
manager
.regenerate_synapses_for_mapping(&src_id, &dst_id)
.unwrap();
let memory_area = manager.get_cortical_area(&dst_id).unwrap();
let twin_map = memory_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())
.expect("memory_twin_areas should be set");
let twin_id_str = twin_map
.get(&src_id.as_base_64())
.and_then(|v| v.as_str())
.expect("Missing twin entry for upstream area");
let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
let mapping = memory_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
.and_then(|map| map.get(&twin_id.as_base_64()))
.and_then(|v| v.as_array())
.expect("Missing memory replay mapping for twin area");
let uses_replay = mapping.iter().any(|rule| {
rule.get("morphology_id")
.and_then(|v| v.as_str())
.is_some_and(|id| id == "memory_replay")
});
assert!(uses_replay, "Expected memory_replay mapping for twin area");
let twin_area = manager.get_cortical_area(&twin_id).unwrap();
assert!(matches!(
twin_area.cortical_type,
CorticalAreaType::Custom(_)
));
assert_eq!(
twin_area
.properties
.get("memory_twin_of")
.and_then(|v| v.as_str()),
Some(src_id.as_base_64().as_str())
);
assert_eq!(
twin_area
.properties
.get("memory_twin_for")
.and_then(|v| v.as_str()),
Some(dst_id.as_base_64().as_str())
);
}
#[test]
fn test_associative_memory_between_memory_areas_creates_synapses() {
use crate::models::cortical_area::CorticalArea;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let m1_id = CorticalID::try_from_bytes(b"mmem0402").unwrap();
let m2_id = CorticalID::try_from_bytes(b"mmem0403").unwrap();
let mut m1_area = CorticalArea::new(
m1_id,
0,
"Memory M1".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
m1_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
m1_area
.properties
.insert("temporal_depth".to_string(), serde_json::json!(1));
let mut m2_area = CorticalArea::new(
m2_id,
0,
"Memory M2".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
m2_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
m2_area
.properties
.insert("temporal_depth".to_string(), serde_json::json!(1));
manager.add_cortical_area(m1_area).unwrap();
manager.add_cortical_area(m2_area).unwrap();
manager
.add_neuron(&m1_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
manager
.add_neuron(&m2_id, 0, 0, 0, 1.0, 0.0, 0.1, 0.0, 0, 1, 1.0, 3, 1, false)
.unwrap();
let mapping_data = vec![serde_json::json!({
"morphology_id": "associative_memory",
"morphology_scalar": 1,
"postSynapticCurrent_multiplier": 1.0,
"plasticity_flag": true,
"plasticity_constant": 1,
"ltp_multiplier": 1,
"ltd_multiplier": 1,
"plasticity_window": 5,
})];
manager
.update_cortical_mapping(&m1_id, &m2_id, mapping_data)
.unwrap();
let created = manager
.regenerate_synapses_for_mapping(&m1_id, &m2_id)
.unwrap();
assert!(
created > 0,
"Expected associative memory mapping between memory areas to create synapses"
);
let npu_guard = dyn_npu.lock().unwrap();
let assoc_tagged =
npu_guard.count_synapses_with_edge_flag_bits(SYNAPSE_EDGE_ASSOCIATIVE_MEMORY);
assert!(
assoc_tagged >= 1,
"associative_memory connectome path should stamp SYNAPSE_EDGE_ASSOCIATIVE_MEMORY on created synapses"
);
}
#[test]
fn test_scan_twin_uses_field_xy_and_class_count() {
use crate::models::cortical_area::CorticalArea;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
MemoryCorticalType,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let field_id = CorticalID::try_from_bytes(b"cfld0001").unwrap();
let class_id = CorticalID::try_from_bytes(b"ccls0001").unwrap();
let mem_id = CorticalID::try_from_bytes(b"mmem0001").unwrap();
let field_area = CorticalArea::new(
field_id,
0,
"Field".to_string(),
CorticalAreaDimensions::new(8, 6, 4).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let class_area = CorticalArea::new(
class_id,
0,
"Class".to_string(),
CorticalAreaDimensions::new(1, 1, 5).unwrap(),
(20, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let mut mem_area = CorticalArea::new(
mem_id,
0,
"KernelMem".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(40, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
mem_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
mem_area.properties.insert(
"classifier_class_area_id".to_string(),
serde_json::json!(class_id.as_base_64()),
);
manager.add_cortical_area(field_area).unwrap();
manager.add_cortical_area(class_area).unwrap();
manager.add_cortical_area(mem_area).unwrap();
let mapping_data = vec![serde_json::json!({
"morphology_id": "episodic_scan",
"morphology_scalar": 1,
"postSynapticCurrent_multiplier": 1.0,
})];
manager
.update_cortical_mapping(&field_id, &mem_id, mapping_data)
.unwrap();
manager
.regenerate_synapses_for_mapping(&field_id, &mem_id)
.unwrap();
let memory_area = manager.get_cortical_area(&mem_id).unwrap();
let twin_map = memory_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())
.expect("memory_twin_areas should be set");
let twin_id_str = twin_map
.get(&field_id.as_base_64())
.and_then(|v| v.as_str())
.expect("Missing scan twin entry");
let twin_id = CorticalID::try_from_base_64(twin_id_str).unwrap();
let twin_area = manager.get_cortical_area(&twin_id).unwrap();
assert_eq!(twin_area.dimensions.width, 8);
assert_eq!(twin_area.dimensions.height, 6);
assert_eq!(twin_area.dimensions.depth, 5);
assert_eq!(
twin_area
.properties
.get("scan_twin")
.and_then(|v| v.as_bool()),
Some(true)
);
let has_replay = memory_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
.and_then(|map| map.get(&twin_id.as_base_64()))
.is_some();
assert!(!has_replay, "scan twin must not receive memory_replay");
let episodic_upstreams = manager.get_episodic_memory_upstream_cortical_areas(&mem_id);
let field_idx = manager.get_cortical_idx(&field_id).unwrap();
assert!(
!episodic_upstreams.contains(&field_idx),
"scan source must not enter episodic hash upstreams"
);
}
#[test]
fn test_memory_twin_repair_on_load_preserves_replay_mapping() {
use crate::models::cortical_area::CorticalArea;
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
MemoryCorticalType,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu.clone());
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let src_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
let mem_id = CorticalID::try_from_bytes(b"mmem0002").unwrap();
let src_area = CorticalArea::new(
src_id,
0,
"Source Area".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let mut mem_area = CorticalArea::new(
mem_id,
0,
"Memory Area".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
mem_area
.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
mem_area
.properties
.insert("temporal_depth".to_string(), serde_json::json!(1));
manager.add_cortical_area(src_area).unwrap();
manager.add_cortical_area(mem_area).unwrap();
let twin_id = manager
.build_memory_twin_id(&mem_id, &src_id)
.expect("Failed to build twin id");
let twin_area = CorticalArea::new(
twin_id,
0,
"Source Area_twin".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(
feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
),
)
.unwrap();
manager.add_cortical_area(twin_area).unwrap();
let repaired = manager
.ensure_memory_twin_area(&mem_id, &src_id)
.expect("Failed to repair twin");
assert_eq!(repaired, twin_id);
let mem_area = manager.get_cortical_area(&mem_id).unwrap();
let twin_map = mem_area
.properties
.get("memory_twin_areas")
.and_then(|v| v.as_object())
.expect("memory_twin_areas should be set");
let twin_id_str = twin_map
.get(&src_id.as_base_64())
.and_then(|v| v.as_str())
.expect("Missing twin entry for upstream area");
assert_eq!(twin_id_str, twin_id.as_base_64());
let replay_map = mem_area
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object())
.and_then(|map| map.get(&twin_id.as_base_64()))
.and_then(|v| v.as_array())
.expect("Missing memory replay mapping for twin area");
let uses_replay = replay_map.iter().any(|rule| {
rule.get("morphology_id")
.and_then(|v| v.as_str())
.is_some_and(|id| id == "memory_replay")
});
assert!(uses_replay, "Expected memory_replay mapping for twin area");
let twin_area = manager.get_cortical_area(&twin_id).unwrap();
assert_eq!(
twin_area
.properties
.get("memory_twin_of")
.and_then(|v| v.as_str()),
Some(src_id.as_base_64().as_str())
);
assert_eq!(
twin_area
.properties
.get("memory_twin_for")
.and_then(|v| v.as_str()),
Some(mem_id.as_base_64().as_str())
);
}
#[test]
fn classifier_mapping_change_updates_kernel_slot_not_field() {
let mut manager = ConnectomeManager::new_for_testing();
let mut classifier = feagi_structures::genomic::classifiers::Classifier {
classifier_id: "clf-1".to_string(),
name: "demo".to_string(),
parent_region_id: "root".to_string(),
coordinates_3d: [0, 0, 0],
kernel_area_id: None,
class_area_id: None,
fields: Vec::new(),
kernel_memory_id: "mkmem1".to_string(),
class_memory_id: "mcmem1".to_string(),
properties: HashMap::new(),
};
manager.upsert_classifier(classifier.clone());
let hash_after_upsert = feagi_state_manager::StateManager::instance()
.read()
.get_classifiers_hash();
assert_ne!(hash_after_upsert, 0, "upsert must publish classifiers_hash");
let ignored = manager.apply_classifier_mapping_change(
"cfield",
"mkmem1",
feagi_structures::genomic::classifiers::CLASSIFIER_SCAN_MORPHOLOGY,
false,
);
assert_eq!(ignored, 0);
assert!(manager.get_classifier("clf-1").unwrap().fields.is_empty());
let updated = manager.apply_classifier_mapping_change(
"ckern1",
"mkmem1",
feagi_structures::genomic::classifiers::CLASSIFIER_KERNEL_MORPHOLOGY,
false,
);
assert_eq!(updated, 1);
classifier = manager.get_classifier("clf-1").cloned().unwrap();
assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
let hash_after_kernel_bind = feagi_state_manager::StateManager::instance()
.read()
.get_classifiers_hash();
assert_ne!(
hash_after_kernel_bind, hash_after_upsert,
"binding a classifier input must change classifiers_hash"
);
}
#[test]
fn deleting_referenced_input_clears_classifier_slot() {
let mut manager = ConnectomeManager::new_for_testing();
manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
classifier_id: "clf-1".to_string(),
name: "demo".to_string(),
parent_region_id: "root".to_string(),
coordinates_3d: [0, 0, 0],
kernel_area_id: Some("ckern1".to_string()),
class_area_id: Some("ccls01".to_string()),
fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
field_area_id: "cfield".to_string(),
scan_twin_id: "cscan1".to_string(),
}],
kernel_memory_id: "mkmem1".to_string(),
class_memory_id: "mcmem1".to_string(),
properties: HashMap::new(),
});
assert_eq!(manager.clear_classifier_inputs_for_area("cfield"), 1);
let classifier = manager.get_classifier("clf-1").unwrap();
assert!(classifier.binding_for_field("cfield").is_none());
assert_eq!(classifier.kernel_area_id.as_deref(), Some("ckern1"));
assert!(manager.classifiers_owning_area("cfield").is_empty());
assert_eq!(manager.classifiers_owning_area("mkmem1").len(), 1);
let hash_after_clear = feagi_state_manager::StateManager::instance()
.read()
.get_classifiers_hash();
manager.remove_classifier("clf-1");
let hash_after_remove = feagi_state_manager::StateManager::instance()
.read()
.get_classifiers_hash();
assert_ne!(
hash_after_remove, hash_after_clear,
"removing a classifier must change classifiers_hash"
);
}
#[test]
fn classifier_assembly_mappings_do_not_become_region_io() {
use feagi_structures::genomic::brain_regions::{RegionID, RegionType};
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
MemoryCorticalType,
};
let mut manager = ConnectomeManager::new_for_testing();
let root_id = RegionID::new();
let child_id = RegionID::new();
let root_key = root_id.to_string();
let child_key = child_id.to_string();
manager
.add_brain_region(
BrainRegion::new(root_id, "Root".to_string(), RegionType::Undefined).unwrap(),
None,
)
.unwrap();
manager
.add_brain_region(
BrainRegion::new(child_id, "Child".to_string(), RegionType::Undefined).unwrap(),
Some(root_key.clone()),
)
.unwrap();
let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
let mem_id = CorticalID::try_from_bytes(b"mclfmem1").unwrap();
let regular_field_id = CorticalID::try_from_bytes(b"cregfld1").unwrap();
let regular_mem_id = CorticalID::try_from_bytes(b"mregmem1").unwrap();
let mut field = CorticalArea::new(
field_id,
0,
"field".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap();
field.properties.insert(
"parent_region_id".to_string(),
serde_json::json!(child_key.clone()),
);
field.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
}),
);
let mut classifier_mem = CorticalArea::new(
mem_id,
0,
"clf_kernel_mem".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(10, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
classifier_mem.properties.insert(
"parent_region_id".to_string(),
serde_json::json!(root_key.clone()),
);
classifier_mem
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
classifier_mem.properties.insert(
"classifier_role".to_string(),
serde_json::json!("kernel_memory"),
);
let mut regular_field = CorticalArea::new(
regular_field_id,
0,
"regular_field".to_string(),
CorticalAreaDimensions::new(2, 2, 1).unwrap(),
(20, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap();
regular_field.properties.insert(
"parent_region_id".to_string(),
serde_json::json!(child_key.clone()),
);
regular_field.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
regular_mem_id.as_base_64(): [{ "morphology_id": "episodic_memory" }]
}),
);
let mut regular_mem = CorticalArea::new(
regular_mem_id,
0,
"regular_mem".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(30, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
regular_mem.properties.insert(
"parent_region_id".to_string(),
serde_json::json!(root_key.clone()),
);
manager.add_cortical_area(field).unwrap();
manager.add_cortical_area(classifier_mem).unwrap();
manager.add_cortical_area(regular_field).unwrap();
manager.add_cortical_area(regular_mem).unwrap();
let io = manager.recompute_brain_region_io_registry().unwrap();
let child_outputs = &io.get(&child_key).expect("child region io").1;
assert!(
!child_outputs.contains(&field_id.as_base_64()),
"classifier input must stay inside the circuit; got outputs {child_outputs:?}"
);
assert!(
child_outputs.contains(®ular_field_id.as_base_64()),
"non-classifier cross-region mappings must still become region outputs; got {child_outputs:?}"
);
}
#[test]
fn classifier_assembly_area_is_detected_from_role_or_flag() {
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, MemoryCorticalType,
};
let mut flagged = CorticalArea::new(
CorticalID::try_from_bytes(b"mflagged").unwrap(),
0,
"flagged".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
flagged
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
&flagged
));
let mut role = CorticalArea::new(
CorticalID::try_from_bytes(b"mrole001").unwrap(),
0,
"role".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
role.properties.insert(
"classifier_role".to_string(),
serde_json::json!("scan_twin"),
);
assert!(ConnectomeManager::area_belongs_to_classifier_assembly(
&role
));
let plain = CorticalArea::new(
CorticalID::try_from_bytes(b"mplain01").unwrap(),
0,
"plain".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
assert!(!ConnectomeManager::area_belongs_to_classifier_assembly(
&plain
));
}
#[test]
fn rekey_memory_twin_source_retargets_classifier_stamp() {
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
MemoryCorticalType,
};
let mut manager = ConnectomeManager::new_for_testing();
let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
let mut mem_area = CorticalArea::new(
mem_id,
0,
"kernel_mem".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
mem_area.properties.insert(
"classifier_role".to_string(),
serde_json::json!("kernel_memory"),
);
mem_area.properties.insert(
"memory_twin_areas".to_string(),
serde_json::json!({ "cfield01": stamp_id.as_base_64() }),
);
let mut stamp = CorticalArea::new(
stamp_id,
0,
"stamp".to_string(),
CorticalAreaDimensions::new(4, 4, 2).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap();
stamp.properties.insert(
"classifier_role".to_string(),
serde_json::json!("scan_twin"),
);
stamp
.properties
.insert("memory_twin_of".to_string(), serde_json::json!("cfield01"));
manager.add_cortical_area(mem_area).unwrap();
manager.add_cortical_area(stamp).unwrap();
manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
classifier_id: "clf-1".to_string(),
name: "demo".to_string(),
parent_region_id: "root".to_string(),
coordinates_3d: [0, 0, 0],
kernel_area_id: Some("ckern001".to_string()),
class_area_id: Some("ccls0001".to_string()),
fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
field_area_id: "cfield01".to_string(),
scan_twin_id: stamp_id.as_base_64(),
}],
kernel_memory_id: mem_id.as_base_64(),
class_memory_id: "mcmem001".to_string(),
properties: HashMap::new(),
});
manager
.rekey_memory_twin_source(&mem_id.as_base_64(), "cfield01", "cfield02")
.unwrap();
let memory_area = manager.get_cortical_area(&mem_id).unwrap();
let twins = memory_area
.properties
.get("memory_twin_areas")
.and_then(|value| value.as_object())
.expect("memory_twin_areas");
assert_eq!(
twins.get("cfield02").and_then(|value| value.as_str()),
Some(stamp_id.as_base_64().as_str())
);
assert!(!twins.contains_key("cfield01"));
let stamp = manager.get_cortical_area(&stamp_id).unwrap();
assert_eq!(
stamp
.properties
.get("memory_twin_of")
.and_then(|value| value.as_str()),
Some("cfield02")
);
}
#[test]
fn classifier_stamp_survives_field_mapping_delete() {
use feagi_npu_burst_engine::backend::CPUBackend;
use feagi_npu_burst_engine::TracingMutex;
use feagi_npu_burst_engine::{DynamicNPU, RustNPU};
use feagi_npu_runtime::StdRuntime;
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
IOCorticalAreaConfigurationFlag, MemoryCorticalType,
};
use std::sync::Arc;
let runtime = StdRuntime;
let backend = CPUBackend::new();
let npu = RustNPU::new(runtime, backend, 10_000, 10_000, 10).expect("Failed to create NPU");
let dyn_npu = Arc::new(TracingMutex::new(DynamicNPU::F32(npu), "TestNPU"));
let mut manager = ConnectomeManager::new_for_testing_with_npu(dyn_npu);
feagi_evolutionary::templates::add_core_morphologies(&mut manager.morphology_registry);
let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
let stamp_id = CorticalID::try_from_bytes(b"cstamp01").unwrap();
let field_area = CorticalArea::new(
field_id,
0,
"Field".to_string(),
CorticalAreaDimensions::new(4, 3, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::BrainInput(IOCorticalAreaConfigurationFlag::Boolean),
)
.unwrap();
let mut mem_area = CorticalArea::new(
mem_id,
0,
"kernel_mem".to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(10, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
mem_area.properties.insert(
"classifier_role".to_string(),
serde_json::json!("kernel_memory"),
);
mem_area
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
mem_area.properties.insert(
"memory_twin_areas".to_string(),
serde_json::json!({ field_id.as_base_64(): stamp_id.as_base_64() }),
);
let mut stamp = CorticalArea::new(
stamp_id,
0,
"stamp".to_string(),
CorticalAreaDimensions::new(4, 3, 2).unwrap(),
(20, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap();
stamp.properties.insert(
"classifier_role".to_string(),
serde_json::json!("scan_twin"),
);
stamp
.properties
.insert("classifier_assembly".to_string(), serde_json::json!(true));
stamp.properties.insert(
"memory_twin_of".to_string(),
serde_json::json!(field_id.as_base_64()),
);
manager.add_cortical_area(field_area).unwrap();
manager.add_cortical_area(mem_area).unwrap();
manager.add_cortical_area(stamp).unwrap();
manager.upsert_classifier(feagi_structures::genomic::classifiers::Classifier {
classifier_id: "clf-1".to_string(),
name: "demo".to_string(),
parent_region_id: "root".to_string(),
coordinates_3d: [0, 0, 0],
kernel_area_id: Some("ckern001".to_string()),
class_area_id: Some("ccls0001".to_string()),
fields: vec![feagi_structures::genomic::classifiers::ClassifierField {
field_area_id: field_id.as_base_64(),
scan_twin_id: stamp_id.as_base_64(),
}],
kernel_memory_id: mem_id.as_base_64(),
class_memory_id: "mcmem001".to_string(),
properties: HashMap::new(),
});
let mapping_data = vec![serde_json::json!({
"morphology_id": "episodic_scan",
"morphology_scalar": [1, 1, 1],
"postSynapticCurrent_multiplier": 1,
})];
manager
.update_cortical_mapping(&field_id, &mem_id, mapping_data)
.unwrap();
manager
.update_cortical_mapping(&field_id, &mem_id, Vec::new())
.unwrap();
assert!(
manager.get_cortical_area(&stamp_id).is_some(),
"classifier stamp must survive field mapping delete"
);
assert_eq!(
manager
.get_classifier("clf-1")
.and_then(|classifier| {
classifier
.binding_for_field(&field_id.as_base_64())
.map(|field| field.field_area_id.clone())
}),
Some(field_id.as_base_64()),
"empty mapping delete must not clear the classifier field slot when morphology is omitted"
);
}
#[cfg(feature = "plasticity")]
#[test]
fn classifier_scan_config_targets_each_field_twin() {
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
MemoryCorticalType,
};
let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
let field_a = CorticalID::try_from_bytes(b"cfield01").unwrap();
let field_b = CorticalID::try_from_bytes(b"cfield02").unwrap();
let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
let twin_a = CorticalID::try_from_bytes(b"ctwin001").unwrap();
let twin_b = CorticalID::try_from_bytes(b"ctwin002").unwrap();
let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
CorticalArea::new(
id,
0,
name.to_string(),
CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap()
};
let memory = |id: CorticalID, name: &str| {
let mut area = CorticalArea::new(
id,
0,
name.to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
area.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
area
};
let mut manager = ConnectomeManager::new_for_testing();
let kernel = custom(kernel_id, "kernel", (2, 2, 1));
let class_area = custom(class_id, "class", (1, 1, 10));
let mut field_a_area = custom(field_a, "field_a", (4, 3, 1));
let mut field_b_area = custom(field_b, "field_b", (2, 2, 1));
field_a_area.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
}),
);
field_b_area.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
mem_id.as_base_64(): [{ "morphology_id": "episodic_scan" }]
}),
);
let mut kernel_mem = memory(mem_id, "kernel_mem");
kernel_mem.properties.insert(
"classifier_kernel_area_id".to_string(),
serde_json::json!(kernel_id.as_base_64()),
);
kernel_mem.properties.insert(
"classifier_class_area_id".to_string(),
serde_json::json!(class_id.as_base_64()),
);
kernel_mem.properties.insert(
"classifier_class_memory_id".to_string(),
serde_json::json!(class_mem_id.as_base_64()),
);
kernel_mem.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
class_mem_id.as_base_64(): [{ "morphology_id": "associative_memory" }]
}),
);
kernel_mem.properties.insert(
"memory_twin_areas".to_string(),
serde_json::json!({
field_a.as_base_64(): twin_a.as_base_64(),
field_b.as_base_64(): twin_b.as_base_64(),
}),
);
let class_mem = memory(class_mem_id, "class_mem");
let twin_a_area = custom(twin_a, "twin_a", (4, 3, 10));
let twin_b_area = custom(twin_b, "twin_b", (2, 2, 10));
manager.add_cortical_area(kernel).unwrap();
manager.add_cortical_area(class_area).unwrap();
manager.add_cortical_area(field_a_area).unwrap();
manager.add_cortical_area(field_b_area).unwrap();
manager.add_cortical_area(kernel_mem).unwrap();
manager.add_cortical_area(class_mem).unwrap();
manager.add_cortical_area(twin_a_area).unwrap();
manager.add_cortical_area(twin_b_area).unwrap();
let scan = manager
.build_memory_scan_config(&mem_id)
.expect("a mapped field twin must produce a scan config");
assert_eq!(scan.sources.len(), 2);
assert_eq!(scan.class_channel_count, 10);
assert_eq!(scan.kernel.width, 2);
let source_a = scan
.sources
.iter()
.find(|source| source.field_area_idx == manager.get_cortical_idx(&field_a).unwrap())
.expect("field A scan source");
assert_eq!(
source_a.twin_area_idx,
manager.get_cortical_idx(&twin_a).unwrap()
);
assert_eq!(source_a.field_width, 4);
assert_eq!(source_a.field_height, 3);
let source_b = scan
.sources
.iter()
.find(|source| source.field_area_idx == manager.get_cortical_idx(&field_b).unwrap())
.expect("field B scan source");
assert_eq!(
source_b.twin_area_idx,
manager.get_cortical_idx(&twin_b).unwrap()
);
manager
.get_cortical_area_mut(&mem_id)
.unwrap()
.properties
.insert("memory_twin_areas".to_string(), serde_json::json!({}));
assert!(
manager.build_memory_scan_config(&mem_id).is_none(),
"an empty twin map must not inject, which is why a dark twin stays dark"
);
}
#[cfg(feature = "plasticity")]
#[test]
fn classifier_genome_round_trip_restores_scan_target() {
use feagi_evolutionary::{
convert_hierarchical_to_flat, load_genome_from_json, GenomeMetadata, GenomeSignatures,
GenomeStats, PhysiologyConfig, RuntimeGenome,
};
use feagi_structures::genomic::classifiers::{Classifier, ClassifierField};
use feagi_structures::genomic::cortical_area::{
CorticalAreaDimensions, CorticalAreaType, CorticalID, CustomCorticalType,
MemoryCorticalType,
};
let kernel_id = CorticalID::try_from_bytes(b"ckern001").unwrap();
let class_id = CorticalID::try_from_bytes(b"cclass01").unwrap();
let field_id = CorticalID::try_from_bytes(b"cfield01").unwrap();
let mem_id = CorticalID::try_from_bytes(b"mkmem001").unwrap();
let class_mem_id = CorticalID::try_from_bytes(b"mcmem001").unwrap();
let twin_id = CorticalID::try_from_bytes(b"ctwin001").unwrap();
let custom = |id: CorticalID, name: &str, dims: (u32, u32, u32)| {
CorticalArea::new(
id,
0,
name.to_string(),
CorticalAreaDimensions::new(dims.0, dims.1, dims.2).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire),
)
.unwrap()
};
let memory = |id: CorticalID, name: &str| {
let mut area = CorticalArea::new(
id,
0,
name.to_string(),
CorticalAreaDimensions::new(1, 1, 1).unwrap(),
(0, 0, 0).into(),
CorticalAreaType::Memory(MemoryCorticalType::Memory),
)
.unwrap();
area.properties
.insert("is_mem_type".to_string(), serde_json::json!(true));
area
};
let mut kernel = custom(kernel_id, "kernel", (2, 2, 1));
kernel.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
mem_id.as_base_64(): [{
"morphology_id": "episodic_memory",
"postSynapticCurrent_multiplier": 1.0,
"plasticity_flag": false
}]
}),
);
let class_area = custom(class_id, "class", (1, 1, 4));
let mut field = custom(field_id, "field", (3, 2, 1));
field
.properties
.insert("burst_engine_active".to_string(), serde_json::json!(true));
field.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
mem_id.as_base_64(): [{
"morphology_id": "episodic_scan",
"postSynapticCurrent_multiplier": 1.0,
"plasticity_flag": false
}]
}),
);
let mut kernel_mem = memory(mem_id, "kernel_mem");
kernel_mem.properties.insert(
"cortical_mapping_dst".to_string(),
serde_json::json!({
class_mem_id.as_base_64(): [{
"morphology_id": "associative_memory",
"postSynapticCurrent_multiplier": 1.0,
"plasticity_flag": false
}]
}),
);
let class_mem = memory(class_mem_id, "class_mem");
let mut twin = custom(twin_id, "twin", (3, 2, 4));
twin.properties
.insert("burst_engine_active".to_string(), serde_json::json!(true));
twin.properties.insert(
"memory_twin_of".to_string(),
serde_json::json!(field_id.as_base_64()),
);
let mut genome = RuntimeGenome {
metadata: GenomeMetadata {
genome_id: "clf-round-trip".to_string(),
genome_title: "clf".to_string(),
genome_description: "".to_string(),
version: "3.0".to_string(),
timestamp: 0.0,
brain_regions_root: None,
},
cortical_areas: HashMap::new(),
brain_regions: HashMap::new(),
classifiers: HashMap::new(),
morphologies: feagi_evolutionary::MorphologyRegistry::new(),
physiology: PhysiologyConfig::default(),
signatures: GenomeSignatures {
genome: "0".to_string(),
blueprint: "0".to_string(),
physiology: "0".to_string(),
morphologies: None,
},
stats: GenomeStats::default(),
};
for area in [kernel, class_area, field, kernel_mem, class_mem, twin] {
genome.cortical_areas.insert(area.cortical_id, area);
}
genome.classifiers.insert(
"clf-1".to_string(),
Classifier {
classifier_id: "clf-1".to_string(),
name: "asdf".to_string(),
parent_region_id: "root".to_string(),
coordinates_3d: [0, 0, 0],
kernel_area_id: Some(kernel_id.as_base_64()),
class_area_id: Some(class_id.as_base_64()),
fields: vec![ClassifierField {
field_area_id: field_id.as_base_64(),
scan_twin_id: twin_id.as_base_64(),
}],
kernel_memory_id: mem_id.as_base_64(),
class_memory_id: class_mem_id.as_base_64(),
properties: HashMap::new(),
},
);
let flat = convert_hierarchical_to_flat(&genome).unwrap();
let loaded = load_genome_from_json(&flat.to_string()).unwrap();
let loaded_classifier = loaded.classifiers.get("clf-1").unwrap();
assert_eq!(
loaded_classifier.fields[0].scan_twin_id,
twin_id.as_base_64()
);
assert_eq!(
loaded
.cortical_areas
.get(&twin_id)
.unwrap()
.properties
.get("burst_engine_active")
.and_then(|value| value.as_bool()),
Some(true),
"twin burst must survive save and load"
);
assert!(
!loaded.cortical_areas[&mem_id]
.properties
.contains_key("classifier_kernel_area_id"),
"the blueprint does not store classifier area ids; load must restore them"
);
let mut manager = ConnectomeManager::new_for_testing();
for area in loaded.cortical_areas.values() {
manager.add_cortical_area(area.clone()).unwrap();
}
manager.replace_classifiers(loaded.classifiers);
manager.apply_loaded_classifier_assemblies();
let kernel_mem = manager.get_cortical_area(&mem_id).unwrap();
assert_eq!(
kernel_mem
.properties
.get("classifier_kernel_area_id")
.and_then(|value| value.as_str()),
Some(kernel_id.as_base_64().as_str())
);
assert_eq!(
kernel_mem
.properties
.get("classifier_class_area_id")
.and_then(|value| value.as_str()),
Some(class_id.as_base_64().as_str())
);
assert_eq!(
kernel_mem
.properties
.get("memory_twin_areas")
.and_then(|value| value.as_object())
.and_then(|map| map.get(&field_id.as_base_64()))
.and_then(|value| value.as_str()),
Some(twin_id.as_base_64().as_str())
);
assert_eq!(
kernel_mem
.properties
.get("burst_engine_active")
.and_then(|value| value.as_bool()),
Some(true)
);
let twin_area = manager.get_cortical_area(&twin_id).unwrap();
assert_eq!(
twin_area
.properties
.get("memory_twin_for")
.and_then(|value| value.as_str()),
Some(mem_id.as_base_64().as_str())
);
assert_eq!(
twin_area
.properties
.get("scan_twin")
.and_then(|value| value.as_bool()),
Some(true)
);
assert!(
manager.mapping_from_src_to_dst_has_associative(&mem_id, &class_mem_id),
"kernel memory to class memory associative mapping must survive save and load, got {:?}",
manager
.get_cortical_area(&mem_id)
.unwrap()
.properties
.get("cortical_mapping_dst")
);
assert!(
!manager
.get_episodic_scan_upstream_cortical_areas(&mem_id)
.is_empty(),
"field episodic_scan must survive save and load, got {:?}",
manager
.get_cortical_area(&field_id)
.unwrap()
.properties
.get("cortical_mapping_dst")
);
let scan = manager
.build_memory_scan_config(&mem_id)
.expect("a reloaded classifier must scan into its twin");
assert_eq!(scan.sources.len(), 1);
assert_eq!(
scan.sources[0].twin_area_idx,
manager.get_cortical_idx(&twin_id).unwrap()
);
assert_eq!(
scan.sources[0].field_area_idx,
manager.get_cortical_idx(&field_id).unwrap()
);
}
}