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