1use feagi_structures::genomic::classifiers::Classifier;
15use feagi_structures::genomic::cortical_area::CorticalArea;
16use feagi_structures::genomic::cortical_area::CorticalID;
17use feagi_structures::genomic::BrainRegion;
18use serde::{Deserialize, Serialize};
19use std::collections::HashMap;
20
21#[derive(Debug, Clone)]
23pub struct RuntimeGenome {
24 pub metadata: GenomeMetadata,
26
27 pub cortical_areas: HashMap<CorticalID, CorticalArea>,
29
30 pub brain_regions: HashMap<String, BrainRegion>,
32
33 pub classifiers: HashMap<String, Classifier>,
36
37 pub morphologies: MorphologyRegistry,
39
40 pub physiology: PhysiologyConfig,
42
43 pub signatures: GenomeSignatures,
45
46 pub stats: GenomeStats,
48
49 pub change_history: Vec<serde_json::Value>,
54}
55
56impl RuntimeGenome {
57 pub fn apply_classifier_required_mappings(&mut self) -> usize {
64 let mut added = 0usize;
65 for classifier in self.classifiers.values() {
66 let Some(associative_window) =
67 CorticalID::try_from_base_64(&classifier.kernel_memory_id)
68 .ok()
69 .and_then(|id| self.cortical_areas.get(&id))
70 .and_then(|area| crate::extract_memory_properties(&area.properties))
71 .map(|props| props.temporal_depth)
72 else {
73 continue;
74 };
75 for mapping in classifier.required_mappings() {
76 let Ok(dst_id) = CorticalID::try_from_base_64(&mapping.dst_area_id) else {
77 continue;
78 };
79 if !self.cortical_areas.contains_key(&dst_id) {
80 continue;
81 }
82 let Ok(src_id) = CorticalID::try_from_base_64(&mapping.src_area_id) else {
83 continue;
84 };
85 let Some(src_area) = self.cortical_areas.get_mut(&src_id) else {
86 continue;
87 };
88 let Some(mapping_dst) = src_area
89 .properties
90 .entry("cortical_mapping_dst".to_string())
91 .or_insert_with(|| serde_json::json!({}))
92 .as_object_mut()
93 else {
94 continue;
95 };
96 let Some(rules) = mapping_dst
97 .entry(mapping.dst_area_id.clone())
98 .or_insert_with(|| serde_json::json!([]))
99 .as_array_mut()
100 else {
101 continue;
102 };
103 let present = rules.iter().any(|rule| {
104 rule.get("morphology_id").and_then(|v| v.as_str())
105 == Some(mapping.morphology_id.as_str())
106 });
107 if present {
108 continue;
109 }
110 rules.push(
111 feagi_structures::genomic::classifiers::classifier_mapping_rule(
112 &mapping.morphology_id,
113 associative_window,
114 ),
115 );
116 added += 1;
117 }
118 }
119 added
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct GenomeMetadata {
126 pub genome_id: String,
127 pub genome_title: String,
128 pub genome_description: String,
129 pub version: String,
130 pub timestamp: f64, #[serde(skip_serializing_if = "Option::is_none")]
135 pub brain_regions_root: Option<String>,
136}
137
138#[derive(Debug, Clone, Default)]
140pub struct MorphologyRegistry {
141 morphologies: HashMap<String, Morphology>,
143}
144
145impl MorphologyRegistry {
146 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn add_morphology(&mut self, id: String, morphology: Morphology) {
153 self.morphologies.insert(id, morphology);
154 }
155
156 pub fn get(&self, id: &str) -> Option<&Morphology> {
158 self.morphologies.get(id)
159 }
160
161 pub fn contains(&self, id: &str) -> bool {
163 self.morphologies.contains_key(id)
164 }
165
166 pub fn morphology_ids(&self) -> Vec<String> {
168 self.morphologies.keys().cloned().collect()
169 }
170
171 pub fn remove_morphology(&mut self, id: &str) -> bool {
175 self.morphologies.remove(id).is_some()
176 }
177
178 pub fn count(&self) -> usize {
180 self.morphologies.len()
181 }
182
183 pub fn iter(&self) -> impl Iterator<Item = (&String, &Morphology)> {
185 self.morphologies.iter()
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct Morphology {
192 pub morphology_type: MorphologyType,
194
195 pub parameters: MorphologyParameters,
197
198 pub class: String,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
204#[serde(rename_all = "lowercase")]
205pub enum MorphologyType {
206 Vectors,
208
209 Patterns,
211
212 Functions,
214
215 Composite,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(untagged)]
222pub enum MorphologyParameters {
223 Vectors { vectors: Vec<[i32; 3]> },
225
226 Patterns {
228 patterns: Vec<[Vec<PatternElement>; 2]>,
229 },
230
231 Functions {},
233
234 Composite {
236 src_seed: [u32; 3],
237 src_pattern: Vec<[i32; 2]>,
238 mapper_morphology: String,
239 },
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum PatternElement {
245 Value(i32),
247 Wildcard, Skip, Exclude, DirectionPositive, DirectionNegative, DirectionPositiveInclusive, DirectionNegativeInclusive, Offset(i32), Range(i32, i32), AbsoluteRange(i32, i32), }
268
269impl Serialize for PatternElement {
271 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
272 where
273 S: serde::Serializer,
274 {
275 match self {
276 PatternElement::Value(v) => serializer.serialize_i32(*v),
277 PatternElement::Wildcard => serializer.serialize_str("*"),
278 PatternElement::Skip => serializer.serialize_str("?"),
279 PatternElement::Exclude => serializer.serialize_str("!"),
280 PatternElement::DirectionPositive => serializer.serialize_str("?+"),
281 PatternElement::DirectionNegative => serializer.serialize_str("?-"),
282 PatternElement::DirectionPositiveInclusive => serializer.serialize_str("?+="),
283 PatternElement::DirectionNegativeInclusive => serializer.serialize_str("?-="),
284 PatternElement::Offset(off) => {
285 if *off >= 0 {
286 serializer.serialize_str(&format!("?+{}", off))
287 } else {
288 serializer.serialize_str(&format!("?{}", off))
289 }
290 }
291 PatternElement::Range(lo, hi) => {
292 let lo_str = if *lo >= 0 {
293 format!("?+{}", lo)
294 } else {
295 format!("?{}", lo)
296 };
297 let hi_str = if *hi >= 0 {
298 format!("?+{}", hi)
299 } else {
300 format!("?{}", hi)
301 };
302 serializer.serialize_str(&format!("{}:{}", lo_str, hi_str))
303 }
304 PatternElement::AbsoluteRange(lo, hi) => {
305 serializer.serialize_str(&format!("{}..{}", lo, hi))
306 }
307 }
308 }
309}
310
311impl<'de> Deserialize<'de> for PatternElement {
313 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
314 where
315 D: serde::Deserializer<'de>,
316 {
317 let value = serde_json::Value::deserialize(deserializer)?;
318 match value {
319 serde_json::Value::Number(n) => {
320 if let Some(i) = n.as_i64() {
321 Ok(PatternElement::Value(i as i32))
322 } else {
323 Err(serde::de::Error::custom(
324 "Pattern element must be an integer",
325 ))
326 }
327 }
328 serde_json::Value::String(s) => Self::parse_string(&s)
329 .ok_or_else(|| serde::de::Error::custom(format!("Unknown pattern element: {}", s))),
330 _ => Err(serde::de::Error::custom(
331 "Pattern element must be number or string",
332 )),
333 }
334 }
335}
336
337impl PatternElement {
338 pub fn parse_string(s: &str) -> Option<Self> {
340 match s {
341 "*" => Some(PatternElement::Wildcard),
342 "?" => Some(PatternElement::Skip),
343 "!" => Some(PatternElement::Exclude),
344 "?+" => Some(PatternElement::DirectionPositive),
345 "?-" => Some(PatternElement::DirectionNegative),
346 "?+=" => Some(PatternElement::DirectionPositiveInclusive),
347 "?-=" => Some(PatternElement::DirectionNegativeInclusive),
348 _ => {
349 if let Some(range) = Self::try_parse_range(s) {
350 return Some(range);
351 }
352 if let Some(abs_range) = Self::try_parse_absolute_range(s) {
353 return Some(abs_range);
354 }
355 if let Some(offset) = Self::try_parse_offset(s) {
356 return Some(offset);
357 }
358 None
359 }
360 }
361 }
362
363 fn try_parse_range(s: &str) -> Option<Self> {
364 let parts: Vec<&str> = s.split(':').collect();
365 if parts.len() != 2 {
366 return None;
367 }
368 let lo = Self::extract_relative_offset(parts[0])?;
369 let hi = Self::extract_relative_offset(parts[1])?;
370 Some(PatternElement::Range(lo, hi))
371 }
372
373 fn try_parse_absolute_range(s: &str) -> Option<Self> {
374 let idx = s.find("..")?;
375 if s[idx + 2..].contains("..") {
376 return None;
377 }
378 let lo = s[..idx].parse::<i32>().ok()?;
379 let hi = s[idx + 2..].parse::<i32>().ok()?;
380 Some(PatternElement::AbsoluteRange(lo, hi))
381 }
382
383 fn try_parse_offset(s: &str) -> Option<Self> {
384 let offset = Self::extract_relative_offset(s)?;
385 Some(PatternElement::Offset(offset))
386 }
387
388 fn extract_relative_offset(s: &str) -> Option<i32> {
389 if !s.starts_with('?') {
390 return None;
391 }
392 let rest = &s[1..];
393 if rest.is_empty() || rest == "+" || rest == "-" || rest == "+=" || rest == "-=" {
394 return None;
395 }
396 rest.parse::<i32>().ok()
397 }
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct PhysiologyConfig {
403 pub simulation_timestep: f64,
405
406 pub max_age: u64,
408
409 pub evolution_burst_count: u64,
411
412 pub ipu_idle_threshold: u64,
414
415 pub plasticity_queue_depth: usize,
417
418 pub lifespan_mgmt_interval: u64,
420
421 #[serde(default = "default_quantization_precision")]
424 pub quantization_precision: String,
425}
426
427pub fn default_quantization_precision() -> String {
428 "int8".to_string() }
430
431impl Default for PhysiologyConfig {
432 fn default() -> Self {
433 Self {
434 simulation_timestep: 0.025,
435 max_age: 10_000_000,
436 evolution_burst_count: 50,
437 ipu_idle_threshold: 1000,
438 plasticity_queue_depth: 3,
439 lifespan_mgmt_interval: 10,
440 quantization_precision: default_quantization_precision(),
441 }
442 }
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct GenomeSignatures {
448 pub genome: String,
450
451 pub blueprint: String,
453
454 pub physiology: String,
456
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub morphologies: Option<String>,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize, Default)]
464pub struct GenomeStats {
465 pub innate_cortical_area_count: usize,
467
468 pub innate_neuron_count: usize,
470
471 pub innate_synapse_count: usize,
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_morphology_registry_creation() {
481 let registry = MorphologyRegistry::new();
482 assert_eq!(registry.count(), 0);
483 }
484
485 #[test]
486 fn test_morphology_registry_add_and_get() {
487 let mut registry = MorphologyRegistry::new();
488
489 let morphology = Morphology {
490 morphology_type: MorphologyType::Vectors,
491 parameters: MorphologyParameters::Vectors {
492 vectors: vec![[1, 0, 0], [0, 1, 0]],
493 },
494 class: "test".to_string(),
495 };
496
497 registry.add_morphology("test_morph".to_string(), morphology);
498
499 assert_eq!(registry.count(), 1);
500 assert!(registry.contains("test_morph"));
501 assert!(registry.get("test_morph").is_some());
502 }
503
504 fn classifier_genome(kernel_to_kmem_rules: Vec<serde_json::Value>) -> RuntimeGenome {
505 use feagi_structures::genomic::classifiers::{
506 Classifier, ClassifierField, ClassifierTrainingMode,
507 };
508 use feagi_structures::genomic::cortical_area::{
509 CorticalAreaDimensions, CorticalAreaType, CustomCorticalType, MemoryCorticalType,
510 };
511
512 let area = |id: &str, is_memory: bool| {
513 let kind = if is_memory {
514 CorticalAreaType::Memory(MemoryCorticalType::Memory)
515 } else {
516 CorticalAreaType::Custom(CustomCorticalType::LeakyIntegrateFire)
517 };
518 let mut area = CorticalArea::new(
519 CorticalID::try_from_base_64(id).expect("id"),
520 0,
521 id.to_string(),
522 CorticalAreaDimensions::new(1, 1, 1).expect("dims"),
523 (0, 0, 0).into(),
524 kind,
525 )
526 .expect("area");
527 if is_memory {
528 area.properties
529 .insert("is_mem_type".to_string(), serde_json::json!(true));
530 area.properties
531 .insert("temporal_depth".to_string(), serde_json::json!(2));
532 }
533 area
534 };
535 let mut kernel = area("Y01OSVNUX9w=", false);
536 kernel.properties.insert(
537 "cortical_mapping_dst".to_string(),
538 serde_json::json!({ "bU1OSVNUXx8=": kernel_to_kmem_rules }),
539 );
540 let mut cortical_areas = HashMap::new();
541 for a in [
542 kernel,
543 area("Y01OSVNUX+E=", false),
544 area("Y01OSVNUX8Y=", false),
545 area("bU1OSVNUXx8=", true),
546 area("bU1OSVNUXyA=", true),
547 ] {
548 cortical_areas.insert(a.cortical_id, a);
549 }
550 let classifier = Classifier {
551 classifier_id: "clf".to_string(),
552 name: "clf".to_string(),
553 parent_region_id: "region".to_string(),
554 coordinates_3d: [0, 0, 0],
555 training_mode: ClassifierTrainingMode::Kernel,
556 kernel_area_id: Some("Y01OSVNUX9w=".to_string()),
557 class_area_id: Some("Y01OSVNUX+E=".to_string()),
558 mask_area_id: None,
559 class_count: None,
560 kernel_size: None,
561 fields: vec![ClassifierField {
562 field_area_id: "Y01OSVNUX9w=".to_string(),
563 scan_twin_id: "Y01OSVNUX8Y=".to_string(),
564 }],
565 kernel_memory_id: "bU1OSVNUXx8=".to_string(),
566 class_memory_id: "bU1OSVNUXyA=".to_string(),
567 reward_training: false,
568 answer_feedback_area_id: None,
569 pain_area_id: None,
570 pleasure_area_id: None,
571 answer_latency_bursts: 0,
572 learn_area_id: None,
573 confidence_area_id: None,
574 properties: HashMap::new(),
575 };
576 RuntimeGenome {
577 metadata: GenomeMetadata {
578 genome_id: "t".to_string(),
579 genome_title: "t".to_string(),
580 genome_description: String::new(),
581 version: "3.0".to_string(),
582 timestamp: 0.0,
583 brain_regions_root: None,
584 },
585 cortical_areas,
586 brain_regions: HashMap::new(),
587 classifiers: HashMap::from([("clf".to_string(), classifier)]),
588 morphologies: MorphologyRegistry::new(),
589 physiology: PhysiologyConfig::default(),
590 signatures: GenomeSignatures {
591 genome: "0".to_string(),
592 blueprint: "0".to_string(),
593 physiology: "0".to_string(),
594 morphologies: None,
595 },
596 stats: GenomeStats::default(),
597 change_history: Vec::new(),
598 }
599 }
600
601 fn morphologies(genome: &RuntimeGenome, src: &str, dst: &str) -> Vec<String> {
602 genome.cortical_areas[&CorticalID::try_from_base_64(src).unwrap()]
603 .properties
604 .get("cortical_mapping_dst")
605 .and_then(|m| m.get(dst))
606 .and_then(|r| r.as_array())
607 .map(|rules| {
608 rules
609 .iter()
610 .filter_map(|r| r["morphology_id"].as_str().map(str::to_string))
611 .collect()
612 })
613 .unwrap_or_default()
614 }
615
616 #[test]
617 fn scan_only_kernel_edge_regains_episodic_memory() {
618 use feagi_structures::genomic::classifiers::classifier_mapping_rule;
619 let mut genome = classifier_genome(vec![classifier_mapping_rule("episodic_scan", 2)]);
620
621 let added = genome.apply_classifier_required_mappings();
622
623 let kernel_edge = morphologies(&genome, "Y01OSVNUX9w=", "bU1OSVNUXx8=");
624 assert!(kernel_edge.contains(&"episodic_scan".to_string()));
625 assert!(kernel_edge.contains(&"episodic_memory".to_string()));
626 assert_eq!(
627 morphologies(&genome, "Y01OSVNUX+E=", "bU1OSVNUXyA="),
628 vec!["episodic_memory".to_string()]
629 );
630 let assoc = morphologies(&genome, "bU1OSVNUXx8=", "bU1OSVNUXyA=");
631 assert_eq!(assoc, vec!["associative_memory".to_string()]);
632 let assoc_rule = &genome.cortical_areas
633 [&CorticalID::try_from_base_64("bU1OSVNUXx8=").unwrap()]
634 .properties["cortical_mapping_dst"]["bU1OSVNUXyA="][0];
635 assert_eq!(assoc_rule["plasticity_window"], serde_json::json!(2));
636 assert_eq!(added, 3);
637 }
638
639 #[test]
640 fn complete_classifier_edges_are_left_unchanged() {
641 use feagi_structures::genomic::classifiers::classifier_mapping_rule;
642 let mut genome = classifier_genome(vec![
643 classifier_mapping_rule("episodic_memory", 2),
644 classifier_mapping_rule("episodic_scan", 2),
645 ]);
646 genome.apply_classifier_required_mappings();
647 let before = genome.cortical_areas.clone();
648
649 assert_eq!(genome.apply_classifier_required_mappings(), 0);
650 for (id, area) in &before {
651 assert_eq!(
652 area.properties.get("cortical_mapping_dst"),
653 genome.cortical_areas[id]
654 .properties
655 .get("cortical_mapping_dst")
656 );
657 }
658 }
659
660 #[test]
661 fn test_physiology_config_default() {
662 let config = PhysiologyConfig::default();
663 assert_eq!(config.simulation_timestep, 0.025);
664 assert_eq!(config.max_age, 10_000_000);
665 }
666}