├── .gitignore ├── README.md ├── examples ├── pendulum │ ├── champion.gif │ ├── config.toml │ └── main.rs ├── cart_pole │ ├── champion.gif │ ├── config.toml │ └── main.rs └── mountain_car │ ├── champion.gif │ ├── config.toml │ └── main.rs ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EVOLUTIONARY SELF-REPLICATION 2 | 3 | Inspired by: https://arxiv.org/pdf/2109.08057.pdf 4 | -------------------------------------------------------------------------------- /examples/pendulum/champion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilvanCodes/evolutionary-self-replication/main/examples/pendulum/champion.gif -------------------------------------------------------------------------------- /examples/cart_pole/champion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilvanCodes/evolutionary-self-replication/main/examples/cart_pole/champion.gif -------------------------------------------------------------------------------- /examples/mountain_car/champion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilvanCodes/evolutionary-self-replication/main/examples/mountain_car/champion.gif -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "evolutionary-self-replication" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | set_genome = "0.5.5" 10 | favannat = "0.5.1" 11 | rand = { version = "0.8.5", features = ["small_rng"] } 12 | gym-rs = "0.2.1" -------------------------------------------------------------------------------- /examples/cart_pole/config.toml: -------------------------------------------------------------------------------- 1 | [structure] 2 | number_of_inputs = 5 3 | number_of_outputs = 1 4 | percent_of_connected_inputs = 1.0 5 | outputs_activation = "Tanh" 6 | weight_std_dev = 0.1 7 | weight_cap = 1.0 8 | 9 | [[mutations]] 10 | type = "add_connection" 11 | chance = 0.1 12 | 13 | [[mutations]] 14 | type = "add_node" 15 | chance = 0.01 16 | activation_pool = [ 17 | "Sigmoid", 18 | "Tanh", 19 | "Relu", 20 | "Linear", 21 | "Gaussian", 22 | "Step", 23 | "Sine", 24 | "Cosine", 25 | "Inverse", 26 | "Absolute", 27 | ] 28 | 29 | [[mutations]] 30 | type = "change_weights" 31 | chance = 0.95 32 | percent_perturbed = 0.5 33 | -------------------------------------------------------------------------------- /examples/pendulum/config.toml: -------------------------------------------------------------------------------- 1 | [structure] 2 | number_of_inputs = 4 3 | number_of_outputs = 1 4 | percent_of_connected_inputs = 1.0 5 | outputs_activation = "Tanh" 6 | weight_std_dev = 0.1 7 | weight_cap = 1.0 8 | 9 | [[mutations]] 10 | type = "add_connection" 11 | chance = 0.1 12 | 13 | [[mutations]] 14 | type = "add_node" 15 | chance = 0.01 16 | activation_pool = [ 17 | "Sigmoid", 18 | "Tanh", 19 | "Relu", 20 | "Linear", 21 | "Gaussian", 22 | "Step", 23 | "Sine", 24 | "Cosine", 25 | "Inverse", 26 | "Absolute", 27 | ] 28 | 29 | [[mutations]] 30 | type = "change_weights" 31 | chance = 0.95 32 | percent_perturbed = 0.5 33 | -------------------------------------------------------------------------------- /examples/mountain_car/config.toml: -------------------------------------------------------------------------------- 1 | [structure] 2 | number_of_inputs = 3 3 | number_of_outputs = 1 4 | percent_of_connected_inputs = 1.0 5 | outputs_activation = "Tanh" 6 | weight_std_dev = 0.1 7 | weight_cap = 1.0 8 | 9 | [[mutations]] 10 | type = "add_connection" 11 | chance = 0.1 12 | 13 | [[mutations]] 14 | type = "add_node" 15 | chance = 0.01 16 | activation_pool = [ 17 | "Sigmoid", 18 | "Tanh", 19 | "Relu", 20 | "Linear", 21 | "Gaussian", 22 | "Step", 23 | "Sine", 24 | "Cosine", 25 | "Inverse", 26 | "Absolute", 27 | ] 28 | 29 | [[mutations]] 30 | type = "change_weights" 31 | chance = 0.95 32 | percent_perturbed = 0.5 33 | -------------------------------------------------------------------------------- /examples/pendulum/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | use evolutionary_self_replication::{Environment, Habitat}; 4 | use favannat::{matrix::feedforward::evaluator::MatrixFeedforwardEvaluator, network::Evaluator}; 5 | use gym_rs::{ActionType, GifRender, GymEnv, PendulumEnv}; 6 | use set_genome::Parameters; 7 | 8 | fn main() { 9 | let mut habitat = Habitat::new( 10 | 100, 11 | create_environment, 12 | Parameters::new("examples/pendulum/config.toml").expect("Could not parse config.toml"), 13 | ); 14 | 15 | loop { 16 | habitat.step(); 17 | habitat.reproduce(); 18 | } 19 | } 20 | 21 | fn create_environment() -> Box { 22 | let mut env = PendulumEnv::default(); 23 | let state: Vec = env.reset(); 24 | Box::new(PendulumEnvironment(env, state, 0)) 25 | } 26 | 27 | struct PendulumEnvironment(PendulumEnv, Vec, usize); 28 | 29 | impl Environment for PendulumEnvironment { 30 | fn step( 31 | &mut self, 32 | evaluator: &MatrixFeedforwardEvaluator, 33 | ) -> evolutionary_self_replication::Status { 34 | let mut input = self.1.clone(); 35 | input.push(1.0); 36 | 37 | let output = evaluator.evaluate(input); 38 | 39 | let action = ActionType::Continuous(vec![output[0] * 4.0 - 2.0]); 40 | 41 | let (state, _reward, done, _info) = self.0.step(action); 42 | 43 | self.1 = state; 44 | self.2 += 1; 45 | 46 | if self.2 > 300 { 47 | println!("evaluator over 300 steps: {:?}", evaluator); 48 | render_champion(evaluator); 49 | exit(0); 50 | } 51 | 52 | if done { 53 | println!("organism lived for {} steps", self.2); 54 | } 55 | 56 | (!done).into() 57 | } 58 | } 59 | 60 | fn render_champion(evaluator: &MatrixFeedforwardEvaluator) { 61 | println!("rendering champion..."); 62 | 63 | let mut env = PendulumEnv::default(); 64 | env.seed(0); 65 | 66 | let mut render = GifRender::new(540, 540, "examples/pendulum/champion.gif", 50).unwrap(); 67 | 68 | let mut state: Vec = env.reset(); 69 | 70 | let mut end: bool = false; 71 | let mut steps: usize = 0; 72 | while !end { 73 | if steps > 300 { 74 | break; 75 | } 76 | let mut input = state.clone(); 77 | input.push(1.0); 78 | 79 | let output = evaluator.evaluate(input); 80 | 81 | let action = ActionType::Continuous(vec![output[0] * 4.0 - 2.0]); 82 | let (s, _reward, done, _info) = env.step(action); 83 | end = done; 84 | state = s; 85 | steps += 1; 86 | 87 | env.render(&mut render); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/cart_pole/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | use evolutionary_self_replication::{Environment, Habitat}; 4 | use favannat::{matrix::feedforward::evaluator::MatrixFeedforwardEvaluator, network::Evaluator}; 5 | use gym_rs::{ActionType, CartPoleEnv, GifRender, GymEnv}; 6 | use set_genome::Parameters; 7 | 8 | fn main() { 9 | let mut habitat = Habitat::new( 10 | 100, 11 | create_environment, 12 | Parameters::new("examples/cart_pole/config.toml").expect("Could not parse config.toml"), 13 | ); 14 | 15 | loop { 16 | habitat.step(); 17 | habitat.reproduce(); 18 | } 19 | } 20 | 21 | fn create_environment() -> Box { 22 | let mut env = CartPoleEnv::default(); 23 | let state: Vec = env.reset(); 24 | Box::new(CartPoleEnvironment(env, state, 0)) 25 | } 26 | 27 | struct CartPoleEnvironment(CartPoleEnv, Vec, usize); 28 | 29 | impl Environment for CartPoleEnvironment { 30 | fn step( 31 | &mut self, 32 | evaluator: &MatrixFeedforwardEvaluator, 33 | ) -> evolutionary_self_replication::Status { 34 | let mut input = self.1.clone(); 35 | input.push(1.0); 36 | let output = evaluator.evaluate(input); 37 | 38 | let action: ActionType = if output[0] < 0.0 { 39 | ActionType::Discrete(0) 40 | } else { 41 | ActionType::Discrete(1) 42 | }; 43 | 44 | let (state, _reward, done, _info) = self.0.step(action); 45 | 46 | self.1 = state; 47 | self.2 += 1; 48 | 49 | if self.2 > 1000 { 50 | println!("evaluator over 1000 steps: {:?}", evaluator); 51 | render_champion(evaluator); 52 | exit(0); 53 | } 54 | 55 | if done { 56 | println!("organism lived for {} steps", self.2); 57 | } 58 | 59 | (!done).into() 60 | } 61 | } 62 | 63 | fn render_champion(evaluator: &MatrixFeedforwardEvaluator) { 64 | println!("rendering champion..."); 65 | 66 | let mut env = CartPoleEnv::default(); 67 | 68 | let mut render = GifRender::new(540, 540, "examples/cart_pole/champion.gif", 20).unwrap(); 69 | 70 | let mut state: Vec = env.reset(); 71 | 72 | let mut end: bool = false; 73 | let mut steps: usize = 0; 74 | while !end { 75 | if steps > 1000 { 76 | break; 77 | } 78 | let mut input = state; 79 | input.push(1.0); 80 | let output = evaluator.evaluate(input); 81 | 82 | let action: ActionType = if output[0] < 0.0 { 83 | ActionType::Discrete(0) 84 | } else { 85 | ActionType::Discrete(1) 86 | }; 87 | let (s, _reward, done, _info) = env.step(action); 88 | end = done; 89 | state = s; 90 | steps += 1; 91 | 92 | env.render(&mut render); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/mountain_car/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | use evolutionary_self_replication::{Environment, Habitat}; 4 | use favannat::{matrix::feedforward::evaluator::MatrixFeedforwardEvaluator, network::Evaluator}; 5 | use gym_rs::{ActionType, GifRender, GymEnv, MountainCarEnv}; 6 | use set_genome::Parameters; 7 | 8 | // TODO: change to also enable MatrixRecurrentEvaluator 9 | 10 | fn main() { 11 | let mut habitat = Habitat::new( 12 | 100, 13 | create_environment, 14 | Parameters::new("examples/mountain_car/config.toml").expect("Could not parse config.toml"), 15 | ); 16 | 17 | loop { 18 | habitat.step(); 19 | habitat.reproduce(); 20 | } 21 | } 22 | 23 | fn create_environment() -> Box { 24 | let mut env = MountainCarEnv::default(); 25 | let state: Vec = env.reset(); 26 | Box::new(MountainCarEnvironment(env, state, 0)) 27 | } 28 | 29 | struct MountainCarEnvironment(MountainCarEnv, Vec, usize); 30 | 31 | impl Environment for MountainCarEnvironment { 32 | fn step( 33 | &mut self, 34 | evaluator: &MatrixFeedforwardEvaluator, 35 | ) -> evolutionary_self_replication::Status { 36 | let mut input = self.1.clone(); 37 | input.push(1.0); 38 | 39 | let output = evaluator.evaluate(input); 40 | 41 | let action = ActionType::Continuous(output); 42 | 43 | let (state, _reward, mut done, _info) = self.0.step(action); 44 | 45 | if done { 46 | println!("organism lived for {} steps", self.2); 47 | println!("evaluator: {:?}", evaluator); 48 | render_champion(evaluator); 49 | exit(0); 50 | } 51 | 52 | if (self.1[0] - state[0]).abs() < 0.0005 { 53 | done = true; 54 | } 55 | 56 | self.1 = state; 57 | self.2 += 1; 58 | 59 | if done { 60 | println!("organism lived for {} steps", self.2); 61 | } 62 | 63 | (!done).into() 64 | } 65 | } 66 | 67 | fn render_champion(evaluator: &MatrixFeedforwardEvaluator) { 68 | println!("rendering champion..."); 69 | 70 | let mut env = MountainCarEnv::default(); 71 | env.seed(0); 72 | 73 | let mut render = GifRender::new(540, 540, "examples/mountain_car/champion.gif", 50).unwrap(); 74 | 75 | let mut state: Vec = env.reset(); 76 | 77 | let mut end: bool = false; 78 | let mut steps: usize = 0; 79 | while !end { 80 | if steps > 300 { 81 | break; 82 | } 83 | let mut input = state.clone(); 84 | input.push(1.0); 85 | 86 | let output = evaluator.evaluate(input); 87 | 88 | let action = ActionType::Continuous(output); 89 | let (s, _reward, done, _info) = env.step(action); 90 | end = done; 91 | state = s; 92 | steps += 1; 93 | 94 | env.render(&mut render); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use std::hash::Hash; 4 | 5 | use favannat::{ 6 | matrix::feedforward::{ 7 | evaluator::MatrixFeedforwardEvaluator, fabricator::MatrixFeedForwardFabricator, 8 | }, 9 | network::{Evaluator, Fabricator}, 10 | }; 11 | use rand::Rng; 12 | use set_genome::{Genome, GenomeContext, Parameters}; 13 | 14 | pub struct Habitat { 15 | capacity: usize, 16 | organisms: HashSet, 17 | create_environment: fn() -> Box, 18 | genome_context: GenomeContext, 19 | } 20 | 21 | impl Habitat { 22 | pub fn new( 23 | capacity: usize, 24 | create_environment: fn() -> Box, 25 | parameters: Parameters, 26 | ) -> Self { 27 | Self { 28 | capacity, 29 | create_environment, 30 | genome_context: GenomeContext::new(parameters), 31 | organisms: HashSet::new(), 32 | } 33 | } 34 | 35 | pub fn step(&mut self) { 36 | if self.organisms.is_empty() { 37 | for _ in 0..(self.capacity as f64 * 0.1).ceil() as usize { 38 | let mut genome = self.genome_context.uninitialized_genome(); 39 | genome.init_with_context(&mut self.genome_context); 40 | self.organisms 41 | .insert(Organism::new(genome, (self.create_environment)())); 42 | } 43 | self.organisms.insert(Organism::new( 44 | self.genome_context.initialized_genome(), 45 | (self.create_environment)(), 46 | )); 47 | } 48 | 49 | self.organisms = self 50 | .organisms 51 | .drain() 52 | .filter_map(|mut organism| (organism.step() == Status::Alive).then(|| organism)) 53 | .collect(); 54 | } 55 | 56 | pub fn reproduce(&mut self) { 57 | let mut new_organisms = Vec::new(); 58 | for organism in &self.organisms { 59 | if self.organisms.len() < self.capacity && rand::thread_rng().gen::() < 0.1 { 60 | // println!("REPRODUCTION"); 61 | let mut genome = organism.genome.clone(); 62 | genome.mutate_with_context(&mut self.genome_context); 63 | new_organisms.push(Organism::new(genome, (self.create_environment)())) 64 | } 65 | } 66 | } 67 | } 68 | 69 | pub struct Organism { 70 | genome: Genome, 71 | phenotype: MatrixFeedforwardEvaluator, 72 | environment: Box, 73 | } 74 | 75 | impl Organism { 76 | fn new(genome: Genome, environment: Box) -> Self { 77 | Self { 78 | phenotype: MatrixFeedForwardFabricator::fabricate(&genome).expect("fabricatio failed"), 79 | genome, 80 | environment, 81 | } 82 | } 83 | 84 | fn step(&mut self) -> Status { 85 | self.environment.step(&self.phenotype) 86 | } 87 | } 88 | 89 | impl Hash for Organism { 90 | fn hash(&self, state: &mut H) { 91 | self.genome.hash(state); 92 | } 93 | } 94 | 95 | impl PartialEq for Organism { 96 | fn eq(&self, other: &Self) -> bool { 97 | self.genome == other.genome 98 | } 99 | } 100 | 101 | impl Eq for Organism {} 102 | 103 | #[derive(Debug, Eq, PartialEq)] 104 | pub enum Status { 105 | Dead, 106 | Alive, 107 | } 108 | 109 | impl From for Status { 110 | fn from(status: bool) -> Self { 111 | if status { 112 | Status::Alive 113 | } else { 114 | Status::Dead 115 | } 116 | } 117 | } 118 | 119 | pub trait Environment { 120 | fn step(&mut self, evaluator: &MatrixFeedforwardEvaluator) -> Status; 121 | } 122 | --------------------------------------------------------------------------------