evo_rl/
lib.rs

1//! Evo RL is a machine learning library built on the concept of Neuroevolution -- evolving an
2//! architecture for neural networks as opposed to pre-specifying it. This library is best suited
3//! for Reinforcement Learning tasks in which a reward (or fitness) score can be assigned to
4//! agents.
5//! 
6//! Evo RL is a WIP and is in the pre-alpha state. 
7
8pub mod neuron;
9pub mod enecode;
10pub mod graph;
11pub mod doctest;
12pub mod population;
13pub mod api;
14pub mod agent_wrapper;
15
16use rand::prelude::*;
17use enecode::topology::TopologyGene;
18use enecode::NeuronType;
19use log::*;
20use std::collections::HashMap;
21use std::sync::Arc;
22use std::f32::consts::E;
23
24///Utility function for logging unit tests
25pub fn setup_logger() {
26    pretty_env_logger::try_init().ok();
27}
28
29
30/// Returns an rng based on Option<seed>
31pub fn rng_box(rng_seed: Option<u8>) -> Box<dyn RngCore> {
32        match rng_seed {
33            Some(seedu8) => {
34                let seed = [seedu8; 32];
35                Box::new(StdRng::from_seed(seed))
36            }
37            None => Box::new(rand::thread_rng())
38        }
39}
40
41/// Convenience function to easily create maps when specifying gene strings. 
42pub fn hash_em(names: Vec<&str>, weights: Vec<f32>) -> HashMap<String, f32> {
43    let mut hm: HashMap<String, f32> = HashMap::new();
44    for (inn_number, weight) in names.iter().zip(weights.iter()) {
45        hm.insert(String::from(*inn_number), *weight);
46    }
47
48    hm
49}
50
51/// Convenience function to easily create a vector of owned Strings. 
52pub fn ez_input(names: Vec<&str>) -> Vec<String> {
53    names.iter().map(|&n| String::from(n)).collect()
54}
55
56/// Sorts topology gene in the order of Input, Hidden, Output and then by innovation number
57pub fn sort_genes_by_neuron_type(input_topology_vector: Vec<TopologyGene>) -> Vec<TopologyGene> {
58        let mut topology_s: Vec<TopologyGene> = Vec::new();
59        let mut topology_hidden: Vec<TopologyGene> = Vec::new();
60        let mut topology_outputs: Vec<TopologyGene> = Vec::new();
61
62        for tg in input_topology_vector.into_iter() {
63
64            match tg.pin {
65                NeuronType::In => topology_s.push(tg),
66                NeuronType::Hidden => topology_hidden.push(tg),
67                NeuronType::Out => topology_outputs.push(tg),
68            };
69
70        }
71
72        topology_s.sort_by_key(|tg| tg.innovation_number.clone() );
73        topology_hidden.sort_by_key(|tg| tg.innovation_number.clone() );
74        topology_outputs.sort_by_key(|tg| tg.innovation_number.clone() );
75
76        topology_s.extend(topology_hidden.drain(..));
77        topology_s.extend(topology_outputs.drain(..));
78        topology_s
79    
80}
81
82/// Returns progenitor code base for an innnovation number
83pub fn progenitor_code(innovation_number: &str) -> &str {
84    match innovation_number.find("-") {
85        Some(idx) => {
86            let(prog, _tail) = innovation_number.split_at(idx);
87            prog
88        }
89        None => innovation_number
90    }
91
92}
93
94/// Increments the ID of a neuron when creating a daughter
95pub fn increment_innovation_number(neuron_id: &str, daughter_ids: Vec<&str>) -> Arc<str> {
96    //innovation numbers will be of the form alphanumeric string (progenitor code) followed by
97    //numeric (lineage code)
98    //First, identify the progenitor code
99    
100    let progenitor_code: &str = progenitor_code(neuron_id);
101
102    let daughter_ids_progenitor: Vec<&str> = daughter_ids.iter().map(|x| *x)
103                                                                .filter(|id| id.starts_with(progenitor_code))
104                                                                .filter(|&id| id != progenitor_code).collect();
105
106    //If it is the first daughter, add -1 to the end of the string
107    if daughter_ids_progenitor.len() == 0 {
108        format!("{}-0001", progenitor_code).into()
109    } else {
110        //else increment the largest daughter
111        let largest_daughter_id = daughter_ids_progenitor.iter().max().unwrap();
112
113        if let Some(idx) = largest_daughter_id.rfind("-") {
114            let (previous_lineage, largest_daughter_number) = largest_daughter_id.split_at(idx);
115
116            let ldn: i32 = match largest_daughter_number[1..].parse() {
117                Ok(n) => n,
118                Err(_e) => panic!("Failed to parse string daughter number"),
119            };
120
121            let mut daughter_id = String::from(previous_lineage);
122            //daughter_id.push('-');
123
124            let daughter_innovation = format!("-{:0>4}", ldn + 1);
125            daughter_id.push_str(&daughter_innovation);
126
127            Arc::from(daughter_id)
128
129        } else {
130            debug!("Problem with parsing string largest_daughter_id {} while duplicating {}", largest_daughter_id, neuron_id);
131            panic!("Attempted to parse daughter innovation number but found invalid code");
132        }
133    }
134}
135
136//Non-linearity functions. Thank yourAkshay Ballal for sigmoid and relu
137pub fn sigmoid(z: &f32) -> f32 {
138    1.0 / (1.0 + E.powf(-z))
139}
140
141//Non-linearity functions. Thank yourAkshay Ballal for sigmoid and relu
142pub fn relu(z: &f32) -> f32 {
143    match *z > 0.0 {
144        true => *z,
145        false => 0.0,
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_progenitor_code() {
155        assert_eq!("a0", progenitor_code("a0"));
156        assert_eq!("a0", progenitor_code("a0-12345"));
157        assert_eq!("b00", progenitor_code("b00-12345"));
158    }
159
160    #[test]
161    fn test_increment_innovation_number() {
162        let innovation_number = String::from("a0");
163        let daughters = Vec::new();
164
165        let d1 = increment_innovation_number(&innovation_number, daughters);
166        assert_eq!(&*d1, "a0-0001");
167
168        let daughters2 = vec!["a0-0001", "a0-0002"];
169        let d2 = increment_innovation_number(&innovation_number, daughters2);
170        assert_eq!(&*d2, "a0-0003");
171
172        let innovation_number2 = String::from("a0-0001");
173
174        let daughters3 = vec!["a0-0002", "a0-0005", "B0-10000"];
175        let d3 = increment_innovation_number(&innovation_number2, daughters3);
176
177        assert_eq!(&*d3, "a0-0006");
178    }
179}