feagi_evolutionary/
templates.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Genome templates for FEAGI.
6
7Provides templates for creating genomes from scratch, including:
8- Minimal genome template
9- Cortical area templates (IPU, OPU, CORE)
10- Default neural parameters
11- Embedded default genomes
12
13Copyright 2025 Neuraville Inc.
14Licensed under the Apache License, Version 2.0
15*/
16
17use crate::{
18    GenomeMetadata, GenomeSignatures, GenomeStats, MorphologyRegistry, PhysiologyConfig,
19    RuntimeGenome,
20};
21use feagi_structures::genomic::cortical_area::CoreCorticalType;
22use feagi_structures::genomic::cortical_area::{CorticalArea, CorticalAreaDimensions};
23use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
24use serde_json::Value;
25use std::collections::HashMap;
26
27/// Embedded essential genome (loaded at compile time)
28pub const ESSENTIAL_GENOME_JSON: &str = include_str!("../genomes/essential_genome.json");
29
30/// Embedded barebones genome (loaded at compile time)
31pub const BAREBONES_GENOME_JSON: &str = include_str!("../genomes/barebones_genome.json");
32
33/// Embedded test genome (loaded at compile time)
34pub const TEST_GENOME_JSON: &str = include_str!("../genomes/test_genome.json");
35
36/// Embedded vision genome (loaded at compile time)
37pub const VISION_GENOME_JSON: &str = include_str!("../genomes/vision_genome.json");
38
39/// Default neural properties for all cortical areas
40pub fn get_default_neural_properties() -> HashMap<String, Value> {
41    let mut props = HashMap::new();
42    props.insert("per_voxel_neuron_cnt".to_string(), Value::from(1));
43    props.insert("synapse_attractivity".to_string(), Value::from(100.0));
44    props.insert("degeneration".to_string(), Value::from(0.0));
45    props.insert("psp_uniform_distribution".to_string(), Value::from(true));
46    props.insert("postsynaptic_current_max".to_string(), Value::from(10000.0));
47    props.insert("postsynaptic_current".to_string(), Value::from(500.0));
48    props.insert("firing_threshold".to_string(), Value::from(0.1));
49    props.insert("refractory_period".to_string(), Value::from(0));
50    props.insert("leak_coefficient".to_string(), Value::from(0.0));
51    props.insert("leak_variability".to_string(), Value::from(0.0));
52    props.insert("consecutive_fire_cnt_max".to_string(), Value::from(0));
53    props.insert("snooze_length".to_string(), Value::from(0));
54    props.insert("mp_charge_accumulation".to_string(), Value::from(false));
55    props.insert("mp_driven_psp".to_string(), Value::from(false));
56    props.insert("neuron_excitability".to_string(), Value::from(1.0));
57    props.insert("visualization".to_string(), Value::from(true));
58    props.insert(
59        "cortical_mapping_dst".to_string(),
60        Value::Object(serde_json::Map::new()),
61    );
62    props
63}
64
65/// Create _death cortical area (cortical_idx = 0) from template
66pub fn create_death_area() -> CorticalArea {
67    let cortical_id = CoreCorticalType::Death.to_cortical_id();
68    let cortical_type = cortical_id
69        .as_cortical_type()
70        .expect("Death cortical ID should map to Core type");
71
72    let mut area = CorticalArea::new(
73        cortical_id,
74        0, // cortical_idx = 0 (reserved)
75        "Death".to_string(),
76        CorticalAreaDimensions::new(1, 1, 1).expect("Failed to create dimensions"),
77        GenomeCoordinate3D::new(0, 0, -20),
78        cortical_type,
79    )
80    .expect("Failed to create _death area");
81
82    let mut props = get_default_neural_properties();
83    props.insert("cortical_group".to_string(), Value::from("CORE"));
84    props.insert("2d_coordinate".to_string(), Value::from(vec![-10, -20]));
85    area.properties = props;
86    area
87}
88
89/// Create _power cortical area (cortical_idx = 1) from template
90pub fn create_power_area() -> CorticalArea {
91    let cortical_id = CoreCorticalType::Power.to_cortical_id();
92    let cortical_type = cortical_id
93        .as_cortical_type()
94        .expect("Power cortical ID should map to Core type");
95
96    let mut area = CorticalArea::new(
97        cortical_id,
98        1, // cortical_idx = 1 (reserved)
99        "Brain_Power".to_string(),
100        CorticalAreaDimensions::new(1, 1, 1).expect("Failed to create dimensions"),
101        GenomeCoordinate3D::new(0, 0, -20),
102        cortical_type,
103    )
104    .expect("Failed to create _power area");
105
106    let mut props = get_default_neural_properties();
107    props.insert("cortical_group".to_string(), Value::from("CORE"));
108    props.insert("2d_coordinate".to_string(), Value::from(vec![-10, -10]));
109    props.insert("firing_threshold".to_string(), Value::from(0.1));
110    props.insert("postsynaptic_current".to_string(), Value::from(500.0));
111    props.insert("neuron_excitability".to_string(), Value::from(100.0));
112    area.properties = props;
113    area
114}
115
116/// Create _fatigue cortical area (cortical_idx = 2) from template
117pub fn create_fatigue_area() -> CorticalArea {
118    let cortical_id = CoreCorticalType::Fatigue.to_cortical_id();
119    let cortical_type = cortical_id
120        .as_cortical_type()
121        .expect("Fatigue cortical ID should map to Core type");
122
123    let mut area = CorticalArea::new(
124        cortical_id,
125        2, // cortical_idx = 2 (reserved)
126        "Fatigue".to_string(),
127        CorticalAreaDimensions::new(1, 1, 1).expect("Failed to create dimensions"),
128        GenomeCoordinate3D::new(0, 0, -30),
129        cortical_type,
130    )
131    .expect("Failed to create _fatigue area");
132
133    let mut props = get_default_neural_properties();
134    props.insert("cortical_group".to_string(), Value::from("CORE"));
135    props.insert("2d_coordinate".to_string(), Value::from(vec![-10, 0]));
136    area.properties = props;
137    area
138}
139
140/// Create a minimal empty genome
141pub fn create_minimal_genome(genome_id: String, genome_title: String) -> RuntimeGenome {
142    RuntimeGenome {
143        metadata: GenomeMetadata {
144            genome_id,
145            genome_title,
146            genome_description: "Minimal genome template".to_string(),
147            version: "2.0".to_string(),
148            timestamp: std::time::SystemTime::now()
149                .duration_since(std::time::UNIX_EPOCH)
150                .unwrap()
151                .as_secs_f64(),
152            brain_regions_root: None, // Will be set after neuroembryogenesis
153        },
154        cortical_areas: HashMap::new(),
155        brain_regions: HashMap::new(),
156        morphologies: MorphologyRegistry::new(),
157        physiology: PhysiologyConfig::default(),
158        signatures: GenomeSignatures {
159            genome: String::new(),
160            blueprint: String::new(),
161            physiology: String::new(),
162            morphologies: None,
163        },
164        stats: GenomeStats::default(),
165    }
166}
167
168/// Create a genome with core areas (_death, _power)
169pub fn create_genome_with_core_areas(genome_id: String, genome_title: String) -> RuntimeGenome {
170    let mut genome = create_minimal_genome(genome_id, genome_title);
171
172    // Add core areas (convert 6-char strings to CorticalID)
173    let death_id =
174        crate::genome::parser::string_to_cortical_id("_death").expect("Valid cortical ID");
175    let power_id =
176        crate::genome::parser::string_to_cortical_id("_power").expect("Valid cortical ID");
177    let fatigue_id =
178        crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid cortical ID");
179
180    genome.cortical_areas.insert(death_id, create_death_area());
181    genome.cortical_areas.insert(power_id, create_power_area());
182    genome
183        .cortical_areas
184        .insert(fatigue_id, create_fatigue_area());
185
186    genome
187}
188
189/// Create a genome with core morphologies
190pub fn create_genome_with_core_morphologies(
191    genome_id: String,
192    genome_title: String,
193) -> RuntimeGenome {
194    let mut genome = create_minimal_genome(genome_id, genome_title);
195
196    // Add core morphologies
197    add_core_morphologies(&mut genome.morphologies);
198
199    genome
200}
201
202/// CRITICAL: Ensure a genome has all required core components
203///
204/// This function checks if a genome has:
205/// 1. Core cortical areas (_death, _power)
206/// 2. Core morphologies (block_to_block, projector, etc.)
207///
208/// If any are missing, they are automatically added. This ensures every genome
209/// can function properly regardless of its source.
210///
211/// # Arguments
212/// * `genome` - The genome to validate and fix
213///
214/// # Returns
215/// A tuple of (areas_added, morphologies_added) indicating what was added
216pub fn ensure_core_components(genome: &mut RuntimeGenome) -> (usize, usize) {
217    let mut areas_added = 0;
218    let mut morphologies_added = 0;
219
220    // Convert core area IDs
221    let death_id =
222        crate::genome::parser::string_to_cortical_id("_death").expect("Valid cortical ID");
223    let power_id =
224        crate::genome::parser::string_to_cortical_id("_power").expect("Valid cortical ID");
225    let fatigue_id =
226        crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid cortical ID");
227
228    // 1. Ensure core cortical areas exist
229    if let std::collections::hash_map::Entry::Vacant(e) = genome.cortical_areas.entry(death_id) {
230        let death_area = create_death_area();
231        e.insert(death_area);
232        areas_added += 1;
233        tracing::info!("Added missing core area: _death (cortical_idx=0)");
234    }
235
236    if let std::collections::hash_map::Entry::Vacant(e) = genome.cortical_areas.entry(power_id) {
237        let power_area = create_power_area();
238        e.insert(power_area);
239        areas_added += 1;
240        tracing::info!("Added missing core area: _power (cortical_idx=1)");
241    }
242
243    if let std::collections::hash_map::Entry::Vacant(e) = genome.cortical_areas.entry(fatigue_id) {
244        let fatigue_area = create_fatigue_area();
245        e.insert(fatigue_area);
246        areas_added += 1;
247        tracing::info!("Added missing core area: _fatigue (cortical_idx=2)");
248    }
249
250    // 2. Ensure core morphologies exist
251    let required_morphologies = vec![
252        "block_to_block",
253        "projector",
254        "memory",
255        "all_to_0-0-0",
256        "0-0-0_to_all",
257        "lateral_+x",
258        "lateral_-x",
259        "lateral_+y",
260        "lateral_-y",
261        "lateral_+z",
262        "lateral_-z",
263    ];
264
265    for morph_name in required_morphologies {
266        if !genome.morphologies.contains(morph_name) {
267            morphologies_added += 1;
268        }
269    }
270
271    // Add all missing core morphologies in one call
272    if morphologies_added > 0 {
273        add_core_morphologies(&mut genome.morphologies);
274        tracing::info!("Added {} missing core morphologies", morphologies_added);
275    }
276
277    (areas_added, morphologies_added)
278}
279
280/// Add core morphologies to a registry
281pub fn add_core_morphologies(registry: &mut MorphologyRegistry) {
282    use crate::{Morphology, MorphologyParameters, MorphologyType};
283
284    // block_to_block - Connect neurons in same position
285    registry.add_morphology(
286        "block_to_block".to_string(),
287        Morphology {
288            morphology_type: MorphologyType::Vectors,
289            parameters: MorphologyParameters::Vectors {
290                vectors: vec![[0, 0, 0]],
291            },
292            class: "core".to_string(),
293        },
294    );
295
296    // projector - Function-based morphology
297    registry.add_morphology(
298        "projector".to_string(),
299        Morphology {
300            morphology_type: MorphologyType::Functions,
301            parameters: MorphologyParameters::Functions {},
302            class: "core".to_string(),
303        },
304    );
305
306    // memory - Function-based morphology
307    registry.add_morphology(
308        "memory".to_string(),
309        Morphology {
310            morphology_type: MorphologyType::Functions,
311            parameters: MorphologyParameters::Functions {},
312            class: "core".to_string(),
313        },
314    );
315
316    // all_to_0-0-0 - Connect all neurons to origin
317    registry.add_morphology(
318        "all_to_0-0-0".to_string(),
319        Morphology {
320            morphology_type: MorphologyType::Patterns,
321            parameters: MorphologyParameters::Patterns {
322                patterns: vec![[
323                    vec![
324                        crate::PatternElement::Wildcard,
325                        crate::PatternElement::Wildcard,
326                        crate::PatternElement::Wildcard,
327                    ],
328                    vec![
329                        crate::PatternElement::Value(0),
330                        crate::PatternElement::Value(0),
331                        crate::PatternElement::Value(0),
332                    ],
333                ]],
334            },
335            class: "core".to_string(),
336        },
337    );
338
339    // 0-0-0_to_all - Connect origin to all neurons
340    registry.add_morphology(
341        "0-0-0_to_all".to_string(),
342        Morphology {
343            morphology_type: MorphologyType::Patterns,
344            parameters: MorphologyParameters::Patterns {
345                patterns: vec![[
346                    vec![
347                        crate::PatternElement::Value(0),
348                        crate::PatternElement::Value(0),
349                        crate::PatternElement::Value(0),
350                    ],
351                    vec![
352                        crate::PatternElement::Wildcard,
353                        crate::PatternElement::Wildcard,
354                        crate::PatternElement::Wildcard,
355                    ],
356                ]],
357            },
358            class: "core".to_string(),
359        },
360    );
361
362    // lateral_+x - Connect along +X axis
363    registry.add_morphology(
364        "lateral_+x".to_string(),
365        Morphology {
366            morphology_type: MorphologyType::Vectors,
367            parameters: MorphologyParameters::Vectors {
368                vectors: vec![[1, 0, 0]],
369            },
370            class: "core".to_string(),
371        },
372    );
373
374    // lateral_-x - Connect along -X axis
375    registry.add_morphology(
376        "lateral_-x".to_string(),
377        Morphology {
378            morphology_type: MorphologyType::Vectors,
379            parameters: MorphologyParameters::Vectors {
380                vectors: vec![[-1, 0, 0]],
381            },
382            class: "core".to_string(),
383        },
384    );
385
386    // lateral_+y - Connect along +Y axis
387    registry.add_morphology(
388        "lateral_+y".to_string(),
389        Morphology {
390            morphology_type: MorphologyType::Vectors,
391            parameters: MorphologyParameters::Vectors {
392                vectors: vec![[0, 1, 0]],
393            },
394            class: "core".to_string(),
395        },
396    );
397
398    // lateral_-y - Connect along -Y axis
399    registry.add_morphology(
400        "lateral_-y".to_string(),
401        Morphology {
402            morphology_type: MorphologyType::Vectors,
403            parameters: MorphologyParameters::Vectors {
404                vectors: vec![[0, -1, 0]],
405            },
406            class: "core".to_string(),
407        },
408    );
409
410    // lateral_+z - Connect along +Z axis
411    registry.add_morphology(
412        "lateral_+z".to_string(),
413        Morphology {
414            morphology_type: MorphologyType::Vectors,
415            parameters: MorphologyParameters::Vectors {
416                vectors: vec![[0, 0, 1]],
417            },
418            class: "core".to_string(),
419        },
420    );
421
422    // lateral_-z - Connect along -Z axis
423    registry.add_morphology(
424        "lateral_-z".to_string(),
425        Morphology {
426            morphology_type: MorphologyType::Vectors,
427            parameters: MorphologyParameters::Vectors {
428                vectors: vec![[0, 0, -1]],
429            },
430            class: "core".to_string(),
431        },
432    );
433}
434
435/// Load essential genome from embedded JSON
436///
437/// Automatically ensures core components (_death, _power, core morphologies) are present
438pub fn load_essential_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
439    use crate::genome::loader::load_genome_from_json;
440    let mut genome = load_genome_from_json(ESSENTIAL_GENOME_JSON)?;
441    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
442    if areas_added > 0 || morphs_added > 0 {
443        tracing::info!(
444            "Essential genome: added {} core areas, {} core morphologies",
445            areas_added,
446            morphs_added
447        );
448    }
449    Ok(genome)
450}
451
452/// Load barebones genome from embedded JSON
453///
454/// Automatically ensures core components (_death, _power, core morphologies) are present
455pub fn load_barebones_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
456    use crate::genome::loader::load_genome_from_json;
457    let mut genome = load_genome_from_json(BAREBONES_GENOME_JSON)?;
458    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
459    if areas_added > 0 || morphs_added > 0 {
460        tracing::info!(
461            "Barebones genome: added {} core areas, {} core morphologies",
462            areas_added,
463            morphs_added
464        );
465    }
466    Ok(genome)
467}
468
469/// Load test genome from embedded JSON
470///
471/// Automatically ensures core components (_death, _power, core morphologies) are present
472pub fn load_test_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
473    use crate::genome::loader::load_genome_from_json;
474    let mut genome = load_genome_from_json(TEST_GENOME_JSON)?;
475    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
476    if areas_added > 0 || morphs_added > 0 {
477        tracing::info!(
478            "Test genome: added {} core areas, {} core morphologies",
479            areas_added,
480            morphs_added
481        );
482    }
483    Ok(genome)
484}
485
486/// Load vision genome from embedded JSON
487///
488/// Automatically ensures core components (_death, _power, core morphologies) are present
489pub fn load_vision_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
490    use crate::genome::loader::load_genome_from_json;
491    let mut genome = load_genome_from_json(VISION_GENOME_JSON)?;
492    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
493    if areas_added > 0 || morphs_added > 0 {
494        tracing::info!(
495            "Vision genome: added {} core areas, {} core morphologies",
496            areas_added,
497            morphs_added
498        );
499    }
500    Ok(genome)
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[test]
508    fn test_create_minimal_genome() {
509        let genome = create_minimal_genome("test_genome".to_string(), "Test Genome".to_string());
510
511        assert_eq!(genome.metadata.genome_id, "test_genome");
512        assert_eq!(genome.metadata.version, "2.0");
513        assert_eq!(genome.cortical_areas.len(), 0);
514        assert_eq!(genome.morphologies.count(), 0);
515    }
516
517    #[test]
518    fn test_create_genome_with_core_areas() {
519        let genome =
520            create_genome_with_core_areas("test_genome".to_string(), "Test Genome".to_string());
521
522        assert_eq!(genome.metadata.genome_id, "test_genome");
523        assert_eq!(genome.cortical_areas.len(), 3);
524
525        let death_id = crate::genome::parser::string_to_cortical_id("_death").expect("Valid ID");
526        let power_id = crate::genome::parser::string_to_cortical_id("_power").expect("Valid ID");
527        let fatigue_id =
528            crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid ID");
529        assert!(genome.cortical_areas.contains_key(&death_id));
530        assert!(genome.cortical_areas.contains_key(&power_id));
531        assert!(genome.cortical_areas.contains_key(&fatigue_id));
532
533        // Verify _power has correct properties
534        let power = genome.cortical_areas.get(&power_id).unwrap();
535        assert_eq!(power.cortical_id.as_base_64(), power_id.as_base_64());
536        assert_eq!(power.cortical_idx, 1);
537        assert_eq!(power.dimensions.width, 1);
538        assert_eq!(power.dimensions.height, 1);
539        assert_eq!(power.dimensions.depth, 1);
540    }
541
542    #[test]
543    fn test_create_genome_with_core_morphologies() {
544        let genome = create_genome_with_core_morphologies(
545            "test_genome".to_string(),
546            "Test Genome".to_string(),
547        );
548
549        assert_eq!(genome.metadata.genome_id, "test_genome");
550        assert!(genome.morphologies.count() > 0);
551        assert!(genome.morphologies.contains("block_to_block"));
552        assert!(genome.morphologies.contains("projector"));
553        assert!(genome.morphologies.contains("lateral_+x"));
554    }
555
556    #[test]
557    fn test_add_core_morphologies() {
558        let mut registry = MorphologyRegistry::new();
559        add_core_morphologies(&mut registry);
560
561        // Should have at least 11 core morphologies
562        assert!(registry.count() >= 11);
563        assert!(registry.contains("block_to_block"));
564        assert!(registry.contains("projector"));
565        assert!(registry.contains("all_to_0-0-0"));
566        assert!(registry.contains("lateral_+x"));
567        assert!(registry.contains("lateral_-z"));
568    }
569
570    #[test]
571    fn test_embedded_genomes_exist() {
572        // Test that embedded genome strings are not empty
573        // These are compile-time constants, so they're always non-empty
574        // The assertions verify the constants are defined correctly
575        #[allow(clippy::const_is_empty)]
576        {
577            assert!(!ESSENTIAL_GENOME_JSON.is_empty());
578            assert!(!BAREBONES_GENOME_JSON.is_empty());
579            assert!(!TEST_GENOME_JSON.is_empty());
580            assert!(!VISION_GENOME_JSON.is_empty());
581        }
582    }
583
584    #[test]
585    fn test_load_essential_genome() {
586        let genome = load_essential_genome().expect("Failed to load essential genome");
587        assert!(!genome.cortical_areas.is_empty());
588        // Essential genome should have _power
589        let power_id = crate::genome::parser::string_to_cortical_id("_power").expect("Valid ID");
590        assert!(genome.cortical_areas.contains_key(&power_id));
591    }
592
593    #[test]
594    fn test_ensure_core_components_adds_missing_areas() {
595        // Create a minimal genome without core areas
596        let mut genome = create_minimal_genome("test".to_string(), "Test".to_string());
597
598        assert_eq!(genome.cortical_areas.len(), 0);
599
600        // Ensure core components
601        let (areas_added, _) = ensure_core_components(&mut genome);
602
603        // Should have added _death, _power, and _fatigue
604        assert_eq!(areas_added, 3);
605
606        let death_id = crate::genome::parser::string_to_cortical_id("_death").expect("Valid ID");
607        let power_id = crate::genome::parser::string_to_cortical_id("_power").expect("Valid ID");
608        let fatigue_id =
609            crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid ID");
610        assert!(genome.cortical_areas.contains_key(&death_id));
611        assert!(genome.cortical_areas.contains_key(&power_id));
612        assert!(genome.cortical_areas.contains_key(&fatigue_id));
613
614        // Verify cortical_idx assignments
615        assert_eq!(
616            genome.cortical_areas.get(&death_id).unwrap().cortical_idx,
617            0
618        );
619        assert_eq!(
620            genome.cortical_areas.get(&power_id).unwrap().cortical_idx,
621            1
622        );
623        assert_eq!(
624            genome.cortical_areas.get(&fatigue_id).unwrap().cortical_idx,
625            2
626        );
627    }
628
629    #[test]
630    fn test_ensure_core_components_adds_missing_morphologies() {
631        // Create a genome with core areas but no morphologies
632        let mut genome = create_genome_with_core_areas("test".to_string(), "Test".to_string());
633
634        assert_eq!(genome.morphologies.count(), 0);
635
636        // Ensure core components
637        let (_, morphs_added) = ensure_core_components(&mut genome);
638
639        // Should have added core morphologies
640        assert!(morphs_added > 0);
641        assert!(genome.morphologies.contains("block_to_block"));
642        assert!(genome.morphologies.contains("projector"));
643        assert!(genome.morphologies.contains("memory"));
644        assert!(genome.morphologies.contains("lateral_+x"));
645    }
646
647    #[test]
648    fn test_ensure_core_components_idempotent() {
649        // Create a genome with all core components
650        let mut genome = create_genome_with_core_areas("test".to_string(), "Test".to_string());
651        add_core_morphologies(&mut genome.morphologies);
652
653        // Run ensure_core_components
654        let (areas_added, morphs_added) = ensure_core_components(&mut genome);
655
656        // Should not add anything (already present)
657        assert_eq!(areas_added, 0);
658        assert_eq!(morphs_added, 0);
659    }
660}