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