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