Skip to main content

feagi_evolutionary/
runtime.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Runtime genome representation for FEAGI.
6
7This module defines the in-memory Rust objects that represent a loaded genome.
8These objects are created by the genome parser and consumed by neuroembryogenesis.
9
10Copyright 2025 Neuraville Inc.
11Licensed under the Apache License, Version 2.0
12*/
13
14use 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/// Complete runtime genome representation
22#[derive(Debug, Clone)]
23pub struct RuntimeGenome {
24    /// Genome metadata
25    pub metadata: GenomeMetadata,
26
27    /// Cortical areas (by cortical_id as CorticalID)
28    pub cortical_areas: HashMap<CorticalID, CorticalArea>,
29
30    /// Brain regions (by region_id)
31    pub brain_regions: HashMap<String, BrainRegion>,
32
33    /// Classifier assemblies (by classifier_id). Parallel to `brain_regions`;
34    /// not a region and not exportable as a circuit.
35    pub classifiers: HashMap<String, Classifier>,
36
37    /// Morphology registry
38    pub morphologies: MorphologyRegistry,
39
40    /// Physiology configuration
41    pub physiology: PhysiologyConfig,
42
43    /// Genome signatures
44    pub signatures: GenomeSignatures,
45
46    /// Statistics
47    pub stats: GenomeStats,
48}
49
50/// Genome metadata
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct GenomeMetadata {
53    pub genome_id: String,
54    pub genome_title: String,
55    pub genome_description: String,
56    pub version: String,
57    pub timestamp: f64, // Unix timestamp
58
59    /// Root brain region ID (UUID string) - explicit identification for O(1) lookup
60    /// This eliminates the need to search through all regions to find which has no parent
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub brain_regions_root: Option<String>,
63}
64
65/// Neuron morphology registry
66#[derive(Debug, Clone, Default)]
67pub struct MorphologyRegistry {
68    /// All morphologies by morphology_id
69    morphologies: HashMap<String, Morphology>,
70}
71
72impl MorphologyRegistry {
73    /// Create empty registry
74    pub fn new() -> Self {
75        Self::default()
76    }
77
78    /// Add a morphology
79    pub fn add_morphology(&mut self, id: String, morphology: Morphology) {
80        self.morphologies.insert(id, morphology);
81    }
82
83    /// Get a morphology by ID
84    pub fn get(&self, id: &str) -> Option<&Morphology> {
85        self.morphologies.get(id)
86    }
87
88    /// Check if morphology exists
89    pub fn contains(&self, id: &str) -> bool {
90        self.morphologies.contains_key(id)
91    }
92
93    /// Get all morphology IDs
94    pub fn morphology_ids(&self) -> Vec<String> {
95        self.morphologies.keys().cloned().collect()
96    }
97
98    /// Remove a morphology by ID.
99    ///
100    /// Returns true if the morphology existed and was removed.
101    pub fn remove_morphology(&mut self, id: &str) -> bool {
102        self.morphologies.remove(id).is_some()
103    }
104
105    /// Get count of morphologies
106    pub fn count(&self) -> usize {
107        self.morphologies.len()
108    }
109
110    /// Iterate over all morphologies
111    pub fn iter(&self) -> impl Iterator<Item = (&String, &Morphology)> {
112        self.morphologies.iter()
113    }
114}
115
116/// Neuron morphology definition
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct Morphology {
119    /// Morphology type: "vectors", "patterns", "functions", or "composite"
120    pub morphology_type: MorphologyType,
121
122    /// Morphology parameters
123    pub parameters: MorphologyParameters,
124
125    /// Morphology class: "core", "custom", etc.
126    pub class: String,
127}
128
129/// Morphology type enum
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
131#[serde(rename_all = "lowercase")]
132pub enum MorphologyType {
133    /// Vector-based morphology (3D offset vectors)
134    Vectors,
135
136    /// Pattern-based morphology (source → destination patterns)
137    Patterns,
138
139    /// Function-based morphology (built-in algorithms)
140    Functions,
141
142    /// Composite morphology (combines multiple morphologies)
143    Composite,
144}
145
146/// Morphology parameters (type-specific)
147#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(untagged)]
149pub enum MorphologyParameters {
150    /// Vector parameters: list of [x, y, z] offsets
151    Vectors { vectors: Vec<[i32; 3]> },
152
153    /// Pattern parameters: list of [source_pattern, dest_pattern] pairs
154    Patterns {
155        patterns: Vec<[Vec<PatternElement>; 2]>,
156    },
157
158    /// Function parameters: empty for built-in functions
159    Functions {},
160
161    /// Composite parameters: combines seed + pattern + mapper
162    Composite {
163        src_seed: [u32; 3],
164        src_pattern: Vec<[i32; 2]>,
165        mapper_morphology: String,
166    },
167}
168
169/// Pattern element: exact value, wildcard (*), skip (?), exclude (!), relative, or `N..M`
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub enum PatternElement {
172    /// Exact coordinate value
173    Value(i32),
174    /// Wildcard - matches any value
175    Wildcard, // "*"
176    /// Skip - don't check this coordinate
177    Skip, // "?"
178    /// Exclude - exclude this coordinate
179    Exclude, // "!"
180    /// All coordinates strictly above source on this axis
181    DirectionPositive, // "?+"
182    /// All coordinates strictly below source on this axis
183    DirectionNegative, // "?-"
184    /// All coordinates at or above source on this axis
185    DirectionPositiveInclusive, // "?+="
186    /// All coordinates at or below source on this axis
187    DirectionNegativeInclusive, // "?-="
188    /// Single coordinate at offset from source
189    Offset(i32), // "?+N" or "?-N"
190    /// Inclusive range relative to source [src+lo, src+hi]
191    Range(i32, i32), // "?-A:?+B"
192    /// Inclusive absolute range [N, M]
193    AbsoluteRange(i32, i32), // "N..M"
194}
195
196// Custom serialization to convert PatternElement back to JSON properly
197impl Serialize for PatternElement {
198    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
199    where
200        S: serde::Serializer,
201    {
202        match self {
203            PatternElement::Value(v) => serializer.serialize_i32(*v),
204            PatternElement::Wildcard => serializer.serialize_str("*"),
205            PatternElement::Skip => serializer.serialize_str("?"),
206            PatternElement::Exclude => serializer.serialize_str("!"),
207            PatternElement::DirectionPositive => serializer.serialize_str("?+"),
208            PatternElement::DirectionNegative => serializer.serialize_str("?-"),
209            PatternElement::DirectionPositiveInclusive => serializer.serialize_str("?+="),
210            PatternElement::DirectionNegativeInclusive => serializer.serialize_str("?-="),
211            PatternElement::Offset(off) => {
212                if *off >= 0 {
213                    serializer.serialize_str(&format!("?+{}", off))
214                } else {
215                    serializer.serialize_str(&format!("?{}", off))
216                }
217            }
218            PatternElement::Range(lo, hi) => {
219                let lo_str = if *lo >= 0 {
220                    format!("?+{}", lo)
221                } else {
222                    format!("?{}", lo)
223                };
224                let hi_str = if *hi >= 0 {
225                    format!("?+{}", hi)
226                } else {
227                    format!("?{}", hi)
228                };
229                serializer.serialize_str(&format!("{}:{}", lo_str, hi_str))
230            }
231            PatternElement::AbsoluteRange(lo, hi) => {
232                serializer.serialize_str(&format!("{}..{}", lo, hi))
233            }
234        }
235    }
236}
237
238// Custom deserialization to parse JSON into PatternElement
239impl<'de> Deserialize<'de> for PatternElement {
240    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
241    where
242        D: serde::Deserializer<'de>,
243    {
244        let value = serde_json::Value::deserialize(deserializer)?;
245        match value {
246            serde_json::Value::Number(n) => {
247                if let Some(i) = n.as_i64() {
248                    Ok(PatternElement::Value(i as i32))
249                } else {
250                    Err(serde::de::Error::custom(
251                        "Pattern element must be an integer",
252                    ))
253                }
254            }
255            serde_json::Value::String(s) => Self::parse_string(&s)
256                .ok_or_else(|| serde::de::Error::custom(format!("Unknown pattern element: {}", s))),
257            _ => Err(serde::de::Error::custom(
258                "Pattern element must be number or string",
259            )),
260        }
261    }
262}
263
264impl PatternElement {
265    /// Parse a pattern element from its string representation.
266    pub fn parse_string(s: &str) -> Option<Self> {
267        match s {
268            "*" => Some(PatternElement::Wildcard),
269            "?" => Some(PatternElement::Skip),
270            "!" => Some(PatternElement::Exclude),
271            "?+" => Some(PatternElement::DirectionPositive),
272            "?-" => Some(PatternElement::DirectionNegative),
273            "?+=" => Some(PatternElement::DirectionPositiveInclusive),
274            "?-=" => Some(PatternElement::DirectionNegativeInclusive),
275            _ => {
276                if let Some(range) = Self::try_parse_range(s) {
277                    return Some(range);
278                }
279                if let Some(abs_range) = Self::try_parse_absolute_range(s) {
280                    return Some(abs_range);
281                }
282                if let Some(offset) = Self::try_parse_offset(s) {
283                    return Some(offset);
284                }
285                None
286            }
287        }
288    }
289
290    fn try_parse_range(s: &str) -> Option<Self> {
291        let parts: Vec<&str> = s.split(':').collect();
292        if parts.len() != 2 {
293            return None;
294        }
295        let lo = Self::extract_relative_offset(parts[0])?;
296        let hi = Self::extract_relative_offset(parts[1])?;
297        Some(PatternElement::Range(lo, hi))
298    }
299
300    fn try_parse_absolute_range(s: &str) -> Option<Self> {
301        let idx = s.find("..")?;
302        if s[idx + 2..].contains("..") {
303            return None;
304        }
305        let lo = s[..idx].parse::<i32>().ok()?;
306        let hi = s[idx + 2..].parse::<i32>().ok()?;
307        Some(PatternElement::AbsoluteRange(lo, hi))
308    }
309
310    fn try_parse_offset(s: &str) -> Option<Self> {
311        let offset = Self::extract_relative_offset(s)?;
312        Some(PatternElement::Offset(offset))
313    }
314
315    fn extract_relative_offset(s: &str) -> Option<i32> {
316        if !s.starts_with('?') {
317            return None;
318        }
319        let rest = &s[1..];
320        if rest.is_empty() || rest == "+" || rest == "-" || rest == "+=" || rest == "-=" {
321            return None;
322        }
323        rest.parse::<i32>().ok()
324    }
325}
326
327/// Physiology configuration (runtime parameters)
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct PhysiologyConfig {
330    /// Simulation timestep in seconds (formerly burst_delay)
331    pub simulation_timestep: f64,
332
333    /// Maximum neuron age
334    pub max_age: u64,
335
336    /// Evolution burst count
337    pub evolution_burst_count: u64,
338
339    /// IPU idle threshold
340    pub ipu_idle_threshold: u64,
341
342    /// Plasticity queue depth
343    pub plasticity_queue_depth: usize,
344
345    /// Lifespan management interval
346    pub lifespan_mgmt_interval: u64,
347
348    /// Quantization precision for numeric values
349    /// Options: "fp32" (default), "fp16", "int8"
350    #[serde(default = "default_quantization_precision")]
351    pub quantization_precision: String,
352}
353
354pub fn default_quantization_precision() -> String {
355    "int8".to_string() // Default to INT8 for memory efficiency
356}
357
358impl Default for PhysiologyConfig {
359    fn default() -> Self {
360        Self {
361            simulation_timestep: 0.025,
362            max_age: 10_000_000,
363            evolution_burst_count: 50,
364            ipu_idle_threshold: 1000,
365            plasticity_queue_depth: 3,
366            lifespan_mgmt_interval: 10,
367            quantization_precision: default_quantization_precision(),
368        }
369    }
370}
371
372/// Genome signatures for comparison
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct GenomeSignatures {
375    /// Full genome signature
376    pub genome: String,
377
378    /// Blueprint signature
379    pub blueprint: String,
380
381    /// Physiology signature
382    pub physiology: String,
383
384    /// Morphologies signature (optional, for future extension)
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub morphologies: Option<String>,
387}
388
389/// Genome statistics
390#[derive(Debug, Clone, Serialize, Deserialize, Default)]
391pub struct GenomeStats {
392    /// Innate cortical area count
393    pub innate_cortical_area_count: usize,
394
395    /// Innate neuron count
396    pub innate_neuron_count: usize,
397
398    /// Innate synapse count
399    pub innate_synapse_count: usize,
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn test_morphology_registry_creation() {
408        let registry = MorphologyRegistry::new();
409        assert_eq!(registry.count(), 0);
410    }
411
412    #[test]
413    fn test_morphology_registry_add_and_get() {
414        let mut registry = MorphologyRegistry::new();
415
416        let morphology = Morphology {
417            morphology_type: MorphologyType::Vectors,
418            parameters: MorphologyParameters::Vectors {
419                vectors: vec![[1, 0, 0], [0, 1, 0]],
420            },
421            class: "test".to_string(),
422        };
423
424        registry.add_morphology("test_morph".to_string(), morphology);
425
426        assert_eq!(registry.count(), 1);
427        assert!(registry.contains("test_morph"));
428        assert!(registry.get("test_morph").is_some());
429    }
430
431    #[test]
432    fn test_physiology_config_default() {
433        let config = PhysiologyConfig::default();
434        assert_eq!(config.simulation_timestep, 0.025);
435        assert_eq!(config.max_age, 10_000_000);
436    }
437}