├── .gitignore ├── src ├── lib.rs ├── parameters.rs ├── statistics.rs ├── individual │ └── mod.rs ├── runtime.rs └── elites_map.rs ├── Cargo.toml └── examples ├── MountainCarContinuous-v0 ├── log.yaml ├── config.toml └── main.rs ├── xor ├── config.toml └── main.rs └── BipedalWalker-v3 ├── config.toml └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | /examples/**/*.log 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod elites_map; 2 | mod individual; 3 | mod parameters; 4 | mod runtime; 5 | mod statistics; 6 | 7 | pub use crate::individual::Individual; 8 | pub use crate::runtime::Runtime; 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "map-elites" 3 | version = "0.1.0" 4 | authors = ["Silvan Buedenbender "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | serde = { version = "1.0", features = ["derive"] } 11 | rand = { version = "0.8", features = [ "small_rng" ] } 12 | config = "0.9" 13 | rayon = "1.3" 14 | set_genome = { git = "https://github.com/SilvanCodes/set-genome", branch = "main" } 15 | tracing = "0.1" 16 | 17 | [dev-dependencies] 18 | gym = { git = "https://github.com/SilvanCodes/gym-rs", tag = "thesis" } 19 | favannat = { git = "https://github.com/SilvanCodes/favannat", tag = "thesis" } 20 | tracing-subscriber = "0.2" 21 | tracing-appender = "0.1" 22 | serde_json = "1.0" 23 | ndarray = { version = "0.13.0", features = ["serde"] } 24 | 25 | -------------------------------------------------------------------------------- /src/parameters.rs: -------------------------------------------------------------------------------- 1 | use config::{Config, ConfigError, File}; 2 | use serde::{Deserialize, Serialize}; 3 | use set_genome::Parameters as GenomeParameters; 4 | 5 | #[derive(Deserialize, Serialize, Default, Debug)] 6 | pub struct Parameters { 7 | pub map_elites: MapElitesParameters, 8 | pub genome: GenomeParameters, 9 | } 10 | 11 | #[derive(Deserialize, Serialize, Default, Debug)] 12 | pub struct MapElitesParameters { 13 | pub map_resolution: usize, 14 | pub feature_ranges: Vec<(f64, f64)>, 15 | pub initial_runs: usize, 16 | pub batch_size: usize, 17 | } 18 | 19 | impl Parameters { 20 | pub fn new(path: &str) -> Result { 21 | let mut s = Config::new(); 22 | 23 | // Start off by merging in the "default" configuration file 24 | s.merge(File::with_name(path))?; 25 | 26 | // You can deserialize (and thus freeze) the entire configuration as 27 | s.try_into() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/MountainCarContinuous-v0/log.yaml: -------------------------------------------------------------------------------- 1 | refresh_rate: 30 seconds 2 | appenders: 3 | stdout: 4 | kind: console 5 | progress: 6 | kind: file 7 | path: "examples/MountainCarContinuous-v0/progress.log" 8 | encoder: 9 | pattern: "{m},{n}" 10 | parameters: 11 | kind: file 12 | path: "examples/MountainCarContinuous-v0/parameters.log" 13 | encoder: 14 | pattern: "{d} - {m}{n}" 15 | solutions: 16 | kind: file 17 | path: "examples/MountainCarContinuous-v0/solutions.log" 18 | encoder: 19 | pattern: "{d} - {m}{n}" 20 | root: 21 | level: info 22 | appenders: 23 | - stdout 24 | 25 | loggers: 26 | # Route log events sent to the "app::requests" logger to the "requests" appender, 27 | app::progress: 28 | level: info 29 | appenders: 30 | - progress 31 | app::parameters: 32 | level: info 33 | appenders: 34 | - parameters 35 | app::solutions: 36 | level: info 37 | appenders: 38 | - solutions -------------------------------------------------------------------------------- /examples/xor/config.toml: -------------------------------------------------------------------------------- 1 | [map_elites] 2 | map_resolution = 16 3 | feature_ranges = [ 4 | [0, 10], 5 | [0, 10] 6 | ] 7 | initial_runs = 1000 8 | batch_size = 100 9 | 10 | [genome.structure] 11 | inputs = 3 12 | outputs = 1 13 | inputs_connected_percent = 1.0 14 | outputs_activation = "Tanh" 15 | weight_std_dev = 3.0 16 | weight_cap = 9.0 17 | 18 | [[genome.mutations]] 19 | type = "add_connection" 20 | chance = 0.1 21 | 22 | [[genome.mutations]] 23 | type = "add_node" 24 | chance = 0.05 25 | activation_pool = [ 26 | "Sigmoid", 27 | "Tanh", 28 | "Relu", 29 | "Linear", 30 | "Gaussian", 31 | "Step", 32 | "Sine", 33 | "Cosine", 34 | "Inverse", 35 | "Absolute", 36 | ] 37 | 38 | [[genome.mutations]] 39 | type = "change_weights" 40 | chance = 1.0 41 | percent_perturbed = 0.5 42 | 43 | [[genome.mutations]] 44 | type = "change_activation" 45 | chance = 0.05 46 | activation_pool = [ 47 | "Sigmoid", 48 | "Tanh", 49 | "Relu", 50 | "Linear", 51 | "Gaussian", 52 | "Step", 53 | "Sine", 54 | "Cosine", 55 | "Inverse", 56 | "Absolute", 57 | ] -------------------------------------------------------------------------------- /examples/MountainCarContinuous-v0/config.toml: -------------------------------------------------------------------------------- 1 | [map_elites] 2 | map_resolution = 8 3 | feature_ranges = [ 4 | # [-1.2, 0.6], # x position 5 | # [-0.07, 0.07] # velocity 6 | [0, 10], # num nodes 7 | [0, 10], # num feed-forward connections 8 | [0, 10], # num recurrent connections 9 | ] 10 | initial_runs = 1000 11 | batch_size = 100 12 | 13 | [genome.structure] 14 | inputs = 3 15 | outputs = 1 16 | inputs_connected_percent = 1.0 17 | outputs_activation = "Tanh" 18 | weight_std_dev = 0.1 19 | weight_cap = 1.0 20 | 21 | [[genome.mutations]] 22 | type = "add_connection" 23 | chance = 0.1 24 | 25 | [[genome.mutations]] 26 | type = "add_node" 27 | chance = 0.05 28 | activation_pool = [ 29 | "Sigmoid", 30 | "Tanh", 31 | "Relu", 32 | "Linear", 33 | "Gaussian", 34 | "Step", 35 | "Sine", 36 | "Cosine", 37 | "Inverse", 38 | "Absolute", 39 | ] 40 | 41 | [[genome.mutations]] 42 | type = "change_weights" 43 | chance = 1.0 44 | percent_perturbed = 0.5 45 | 46 | [[genome.mutations]] 47 | type = "change_activation" 48 | chance = 0.05 49 | activation_pool = [ 50 | "Sigmoid", 51 | "Tanh", 52 | "Relu", 53 | "Linear", 54 | "Gaussian", 55 | "Step", 56 | "Sine", 57 | "Cosine", 58 | "Inverse", 59 | "Absolute", 60 | ] 61 | -------------------------------------------------------------------------------- /src/statistics.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use crate::individual::Individual; 4 | 5 | #[derive(Debug, Clone, Default, Serialize)] 6 | pub struct Statistics { 7 | pub population: PopulationStatistics, 8 | pub num_generation: usize, 9 | pub milliseconds_elapsed_evaluation: u128, 10 | pub time_stamp: u64, 11 | } 12 | 13 | #[derive(Debug, Clone, Default, Serialize)] 14 | pub struct FitnessStatisitcs { 15 | pub raw_maximum: f64, 16 | pub raw_minimum: f64, 17 | pub raw_average: f64, 18 | pub raw_std_dev: f64, 19 | pub shifted_maximum: f64, 20 | pub shifted_minimum: f64, 21 | pub shifted_average: f64, 22 | pub normalized_maximum: f64, 23 | pub normalized_minimum: f64, 24 | pub normalized_average: f64, 25 | } 26 | 27 | #[derive(Debug, Clone, Default, Serialize)] 28 | pub struct NoveltyStatisitcs { 29 | pub raw_maximum: f64, 30 | pub raw_minimum: f64, 31 | pub raw_average: f64, 32 | pub shifted_maximum: f64, 33 | pub shifted_minimum: f64, 34 | pub shifted_average: f64, 35 | pub normalized_maximum: f64, 36 | pub normalized_minimum: f64, 37 | pub normalized_average: f64, 38 | } 39 | #[derive(Debug, Clone, Default, Serialize)] 40 | pub struct PopulationStatistics { 41 | pub milliseconds_elapsed_reproducing: u128, 42 | pub top_performer: Individual, 43 | pub age_maximum: usize, 44 | pub age_average: f64, 45 | pub fitness: FitnessStatisitcs, 46 | pub novelty: NoveltyStatisitcs, 47 | } 48 | -------------------------------------------------------------------------------- /src/individual/mod.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use rand::Rng; 4 | use serde::{Deserialize, Serialize}; 5 | use set_genome::Genome; 6 | 7 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 8 | pub struct Individual { 9 | pub genome: Genome, 10 | pub behavior: Vec, 11 | pub fitness: f64, 12 | } 13 | 14 | impl Deref for Individual { 15 | type Target = Genome; 16 | 17 | fn deref(&self) -> &Self::Target { 18 | &self.genome 19 | } 20 | } 21 | 22 | impl DerefMut for Individual { 23 | fn deref_mut(&mut self) -> &mut Self::Target { 24 | &mut self.genome 25 | } 26 | } 27 | 28 | impl Individual { 29 | pub fn from_genome(genome: Genome) -> Self { 30 | Self { 31 | genome, 32 | behavior: Vec::new(), 33 | fitness: 0.0, 34 | } 35 | } 36 | 37 | // self is fitter if it has higher score or in case of equal score has fewer genes, i.e. less complexity 38 | pub fn is_fitter_than(&self, other: &Self) -> bool { 39 | self.fitness > other.fitness 40 | || ((self.fitness - other.fitness).abs() < f64::EPSILON 41 | && self.genome.len() < other.genome.len()) 42 | } 43 | 44 | pub fn crossover(&self, other: &Self, rng: &mut impl Rng) -> Self { 45 | let (fitter, weaker) = if self.is_fitter_than(other) { 46 | (&self.genome, &other.genome) 47 | } else { 48 | (&other.genome, &self.genome) 49 | }; 50 | 51 | Individual { 52 | genome: fitter.cross_in(weaker, rng), 53 | behavior: Vec::new(), 54 | fitness: 0.0, 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/BipedalWalker-v3/config.toml: -------------------------------------------------------------------------------- 1 | [map_elites] 2 | map_resolution = 8 3 | feature_ranges = [ 4 | # [-1.5, 0.5], # hull angle 5 | # [-1.0, 1.0], # hull angular velocity 6 | 7 | # [-1.0, 1.0], # x velocity kk 8 | # [-1.0, 1.0], # y velocity kk 9 | # leg one 10 | [-0.8, 1.1], # hip angle kk 11 | # [-1.0, 1.0], # hip speed 12 | [-0.6, 0.95], # knee angle kk 13 | # [-1.0, 1.0], # knee speed 14 | [0.0, 1.0], # ground contact boolean kk 15 | # leg one 16 | [-0.8, 1.1], # hip angle kk 17 | # [-1.0, 1.0], # hip speed 18 | [-0.6, 0.95], # knee angle kk 19 | # [-1.0, 1.0], # knee speed 20 | [0.0, 1.0], # ground contact boolean kk 21 | 22 | # standard deviation of previous 14 values 23 | # [-1.0, 1.0], 24 | # [-1.0, 1.0], 25 | # [-1.0, 1.0], 26 | # [-1.0, 1.0], 27 | # [-1.0, 1.0], 28 | # [-1.0, 1.0], 29 | # [-1.0, 1.0], 30 | # [-1.0, 1.0], 31 | # [-1.0, 1.0], 32 | # [-1.0, 1.0], 33 | # [-1.0, 1.0], 34 | # [-1.0, 1.0], 35 | # [-1.0, 1.0], 36 | # [-1.0, 1.0], 37 | ] 38 | initial_runs = 10_000 39 | batch_size = 1000 40 | 41 | [genome.structure] 42 | inputs = 25 43 | outputs = 4 44 | inputs_connected_percent = 1.0 45 | outputs_activation = "Tanh" 46 | weight_std_dev = 0.1 47 | weight_cap = 1.0 48 | 49 | [[genome.mutations]] 50 | type = "add_connection" 51 | chance = 0.1 52 | 53 | [[genome.mutations]] 54 | type = "add_recurrent_connection" 55 | chance = 0.01 56 | 57 | [[genome.mutations]] 58 | type = "add_node" 59 | chance = 0.005 60 | activation_pool = [ 61 | "Sigmoid", 62 | "Tanh", 63 | "Relu", 64 | "Linear", 65 | "Gaussian", 66 | "Step", 67 | "Sine", 68 | "Cosine", 69 | "Inverse", 70 | "Absolute", 71 | ] 72 | 73 | [[genome.mutations]] 74 | type = "remove_node" 75 | chance = 0.001 76 | 77 | [[genome.mutations]] 78 | type = "change_weights" 79 | chance = 1.0 80 | percent_perturbed = 0.5 81 | 82 | [[genome.mutations]] 83 | type = "change_activation" 84 | chance = 0.05 85 | activation_pool = [ 86 | "Sigmoid", 87 | "Tanh", 88 | "Relu", 89 | "Linear", 90 | "Gaussian", 91 | "Step", 92 | "Sine", 93 | "Cosine", 94 | "Inverse", 95 | "Absolute", 96 | ] 97 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; 2 | use set_genome::GenomeContext; 3 | use tracing::{debug, info}; 4 | 5 | use crate::{elites_map::ElitesMap, parameters::Parameters, Individual}; 6 | 7 | pub struct Runtime { 8 | fitness_function: Box (f64, Vec) + Send + Sync>, 9 | pub parameters: Parameters, 10 | } 11 | 12 | pub struct RuntimeIterator<'a> { 13 | runtime: &'a Runtime, 14 | elites_map: ElitesMap, 15 | genome_context: GenomeContext, 16 | } 17 | 18 | impl Runtime { 19 | pub fn new( 20 | path: &str, 21 | fitness_function: Box (f64, Vec) + Send + Sync>, 22 | ) -> Self { 23 | let parameters = Parameters::new(path).unwrap(); 24 | Self { 25 | parameters, 26 | fitness_function, 27 | } 28 | } 29 | 30 | /* fn evaluate(&self, individual: &mut Individual) { 31 | let (fitness, behavior) = (self.fitness_function)(individual); 32 | individual.fitness = fitness; 33 | individual.behavior = behavior; 34 | } */ 35 | 36 | fn evaluate_parallel(&self, individuals: &mut Vec) { 37 | info!("evaluating {} individuals in parallel", individuals.len()); 38 | 39 | individuals 40 | .par_iter_mut() 41 | .enumerate() 42 | .map(|(index, individual)| { 43 | let (fitness, behavior) = (self.fitness_function)(individual); 44 | individual.fitness = fitness; 45 | individual.behavior = behavior; 46 | debug!("evaluated {}th individual", index); 47 | }) 48 | .collect::<()>() 49 | } 50 | 51 | pub fn initilize(&self) -> RuntimeIterator { 52 | info!("starting runtime initialization"); 53 | 54 | let mut genome_context = GenomeContext::new(self.parameters.genome.clone()); 55 | 56 | // generate individual with initial ids for genome 57 | let initial_individual = Individual::from_genome(genome_context.uninitialized_genome()); 58 | 59 | let mut elites_map = ElitesMap::new( 60 | self.parameters.map_elites.map_resolution, 61 | self.parameters.map_elites.feature_ranges.clone(), 62 | ); 63 | 64 | let mut initial_individuals: Vec = (0..self.parameters.map_elites.initial_runs) 65 | .map(|_| { 66 | let mut other_individual = initial_individual.clone(); 67 | other_individual.init_with_context(&mut genome_context); 68 | other_individual.mutate_with_context(&mut genome_context); 69 | other_individual 70 | }) 71 | .collect(); 72 | 73 | self.evaluate_parallel(&mut initial_individuals); 74 | 75 | for individual in initial_individuals { 76 | elites_map.place_individual(individual); 77 | } 78 | 79 | RuntimeIterator { 80 | genome_context, 81 | elites_map, 82 | runtime: self, 83 | } 84 | } 85 | } 86 | 87 | impl<'a> Iterator for RuntimeIterator<'a> { 88 | type Item = ElitesMap; 89 | 90 | fn next(&mut self) -> Option { 91 | info!("selecting next individual batch"); 92 | 93 | let mut random_individuals: Vec = 94 | (0..self.runtime.parameters.map_elites.batch_size) 95 | .map(|_| { 96 | let mut random_individual = self 97 | .elites_map 98 | .get_random_individual(&mut self.genome_context.rng); 99 | random_individual.mutate_with_context(&mut self.genome_context); 100 | random_individual 101 | }) 102 | .collect(); 103 | 104 | info!("evaluating selected individual batch"); 105 | 106 | self.runtime.evaluate_parallel(&mut random_individuals); 107 | 108 | info!("placing evaluated individual batch"); 109 | 110 | for individual in random_individuals { 111 | self.elites_map.place_individual(individual); 112 | } 113 | 114 | info!("finished batch"); 115 | 116 | Some(self.elites_map.clone()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /examples/xor/main.rs: -------------------------------------------------------------------------------- 1 | use favannat::matrix::fabricator::FeedForwardMatrixFabricator; 2 | use favannat::network::{Evaluator, Fabricator}; 3 | use ndarray::array; 4 | use std::{ops::Deref, time::Instant}; 5 | 6 | use map_elites::{Individual, Runtime}; 7 | 8 | fn main() { 9 | let fitness_function = |individual: &Individual| -> (f64, Vec) { 10 | let result_0; 11 | let result_1; 12 | let result_2; 13 | let result_3; 14 | 15 | /* match LoopingFabricator::fabricate(individual) { 16 | Ok(mut evaluator) => { */ 17 | match FeedForwardMatrixFabricator::fabricate(individual.deref()) { 18 | Ok(evaluator) => { 19 | result_0 = evaluator.evaluate(array![1.0, 1.0, 0.0]); 20 | result_1 = evaluator.evaluate(array![1.0, 1.0, 1.0]); 21 | result_2 = evaluator.evaluate(array![1.0, 0.0, 1.0]); 22 | result_3 = evaluator.evaluate(array![1.0, 0.0, 0.0]); 23 | } 24 | Err(e) => { 25 | println!("error fabricating individual: {:?} {:?}", individual, e); 26 | panic!("") 27 | } 28 | } 29 | 30 | // calculate fitness 31 | 32 | let fitness = (4.0 33 | - ((1.0 - result_0[0]) 34 | + (0.0 - result_1[0]).abs() 35 | + (1.0 - result_2[0]) 36 | + (0.0 - result_3[0]).abs())) 37 | .powi(2); 38 | 39 | ( 40 | fitness, 41 | vec![ 42 | individual.feed_forward.len() as f64, 43 | individual.hidden.len() as f64, 44 | ], 45 | ) 46 | }; 47 | 48 | let runtime = Runtime::new("examples/xor/config.toml", Box::new(fitness_function)); 49 | 50 | let mut millis_elapsed_in_run = Vec::new(); 51 | let mut connections_in_winner_in_run = Vec::new(); 52 | let mut nodes_in_winner_in_run = Vec::new(); 53 | let mut generations_till_winner_in_run = Vec::new(); 54 | 55 | for i in 0..100 { 56 | let now = Instant::now(); 57 | 58 | if let Some((generations, winner_map)) = 59 | runtime 60 | .initilize() 61 | .enumerate() 62 | .find(|(iteration, elites_map)| { 63 | dbg!(iteration); 64 | dbg!(elites_map.top_individual().fitness); 65 | 66 | elites_map.top_individual().fitness > 15.9 67 | }) 68 | { 69 | millis_elapsed_in_run.push(now.elapsed().as_millis() as f64); 70 | connections_in_winner_in_run.push(winner_map.top_individual().feed_forward.len()); 71 | nodes_in_winner_in_run.push(winner_map.top_individual().nodes().count()); 72 | generations_till_winner_in_run.push(generations); 73 | println!( 74 | "finished run {} in {} seconds ({}, {}) {}", 75 | i, 76 | millis_elapsed_in_run.last().unwrap() / 1000.0, 77 | winner_map.top_individual().nodes().count(), 78 | winner_map.top_individual().feed_forward.len(), 79 | generations 80 | ); 81 | } 82 | } 83 | 84 | let num_runs = millis_elapsed_in_run.len() as f64; 85 | 86 | let total_millis: f64 = millis_elapsed_in_run.iter().sum(); 87 | let total_connections: usize = connections_in_winner_in_run.iter().sum(); 88 | let total_nodes: usize = nodes_in_winner_in_run.iter().sum(); 89 | let total_generations: usize = generations_till_winner_in_run.iter().sum(); 90 | 91 | println!( 92 | "did {} runs in {} seconds / {} nodes average / {} connections / {} batches per run", 93 | num_runs, 94 | total_millis / num_runs / 1000.0, 95 | total_nodes as f64 / num_runs, 96 | total_connections as f64 / num_runs, 97 | total_generations as f64 / num_runs 98 | ); 99 | 100 | /* let now = Instant::now(); 101 | 102 | if let Some(winner) = neat 103 | .run() 104 | .filter_map(|evaluation| match evaluation { 105 | Evaluation::Progress(report) => { 106 | println!("{:#?}", report); 107 | None 108 | } 109 | Evaluation::Solution(genome) => Some(genome), 110 | }) 111 | .next() 112 | { 113 | let secs = now.elapsed().as_millis(); 114 | println!( 115 | "winning genome ({},{}) after {} seconds: {:?}", 116 | winner.nodes().count(), 117 | winner.feed_forward.len(), 118 | secs as f64 / 1000.0, 119 | winner 120 | ); 121 | let evaluator = LoopingFabricator::fabricate(&winner).unwrap(); 122 | println!("as evaluator {:#?}", evaluator); 123 | } */ 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | #[test] 129 | fn fitness_function_good_result() { 130 | let result_0: Vec = vec![1.0]; 131 | let result_1: Vec = vec![0.0]; 132 | let result_2: Vec = vec![1.0]; 133 | let result_3: Vec = vec![0.0]; 134 | 135 | let result = (4.0 136 | - ((1.0 - result_0[0]) 137 | + (0.0 - result_1[0]).abs() 138 | + (1.0 - result_2[0]) 139 | + (0.0 - result_3[0]).abs())) 140 | .powi(2); 141 | 142 | println!("result {:?}", res/* */ult); 143 | 144 | assert_eq!(result, 16.0); 145 | } 146 | 147 | #[test] 148 | fn fitness_function_bad_result() { 149 | let result_0: Vec = vec![0.0]; 150 | let result_1: Vec = vec![1.0]; 151 | let result_2: Vec = vec![0.0]; 152 | let result_3: Vec = vec![1.0]; 153 | 154 | let result = (4.0 155 | - ((1.0 - result_0[0]) 156 | + (0.0 - result_1[0]).abs() 157 | + (1.0 - result_2[0]) 158 | + (0.0 - result_3[0]).abs())) 159 | .powi(2); 160 | 161 | println!("result {:?}", result); 162 | 163 | assert_eq!(result, 0.0); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /examples/BipedalWalker-v3/main.rs: -------------------------------------------------------------------------------- 1 | use favannat::matrix::fabricator::RecurrentMatrixFabricator; 2 | use favannat::network::{StatefulEvaluator, StatefulFabricator}; 3 | use gym::{utility::StandardScaler, SpaceData, SpaceTemplate, State}; 4 | use map_elites::{Individual, Runtime}; 5 | use ndarray::{stack, Array2, Axis}; 6 | 7 | use std::time::SystemTime; 8 | use std::{env, fs}; 9 | use std::{ops::Deref, time::Instant}; 10 | use tracing::{error, info}; 11 | 12 | pub const TRAINING_RUNS: usize = 1; 13 | pub const VALIDATION_RUNS: usize = 100; 14 | pub const SIMULATION_STEPS: usize = 1600; 15 | pub const ENV: &str = "BipedalWalker-v3"; 16 | pub const REQUIRED_FITNESS: f64 = 300.0; 17 | 18 | static mut TIMESTAMP: u64 = 0; 19 | 20 | fn timestamp() -> u64 { 21 | unsafe { TIMESTAMP } 22 | } 23 | 24 | fn main() { 25 | let args: Vec = env::args().collect(); 26 | 27 | unsafe { 28 | TIMESTAMP = SystemTime::now() 29 | .duration_since(SystemTime::UNIX_EPOCH) 30 | .unwrap() 31 | .as_secs(); 32 | } 33 | 34 | let file_appender = tracing_appender::rolling::never( 35 | format!("./examples/{}/logs", ENV), 36 | format!("{}_stats.log", timestamp()), 37 | ); 38 | let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 39 | tracing_subscriber::fmt() 40 | .with_writer(non_blocking) 41 | .json() 42 | .init(); 43 | 44 | if args.get(1).is_some() { 45 | let winner_json = fs::read_to_string(format!("examples/{}/winner_1601592694.json", ENV)) 46 | .expect("cant read file"); 47 | let winner: Individual = serde_json::from_str(&winner_json).unwrap(); 48 | let standard_scaler: StandardScaler = serde_json::from_str(&winner_json).unwrap(); 49 | run(&standard_scaler, &winner, 1, SIMULATION_STEPS, true, false); 50 | } else { 51 | train(StandardScaler::for_environment(ENV)); 52 | } 53 | } 54 | 55 | fn train(standard_scaler: StandardScaler) { 56 | let other_standard_scaler = standard_scaler.clone(); 57 | 58 | let fitness_function = move |individual: &Individual| -> (f64, Vec) { 59 | let (training_fitness, training_observations) = run( 60 | &standard_scaler, 61 | individual, 62 | TRAINING_RUNS, 63 | SIMULATION_STEPS, 64 | false, 65 | false, 66 | ); 67 | 68 | let mut observations = training_observations; 69 | let mut fitness = training_fitness; 70 | let behavior_characterization; 71 | 72 | if fitness > 0.0 { 73 | dbg!(fitness); 74 | } 75 | 76 | if fitness >= REQUIRED_FITNESS { 77 | info!("hit task theshold, starting validation runs..."); 78 | let (validation_fitness, validation_observations) = run( 79 | &standard_scaler, 80 | individual, 81 | VALIDATION_RUNS, 82 | SIMULATION_STEPS, 83 | false, 84 | false, 85 | ); 86 | 87 | // log possible solutions to file 88 | info!( 89 | "finished validation runs with {} average fitness", 90 | validation_fitness 91 | ); 92 | if validation_fitness > REQUIRED_FITNESS { 93 | fitness = validation_fitness; 94 | observations = validation_observations; 95 | } else { 96 | fitness = REQUIRED_FITNESS - 1.0; 97 | } 98 | } 99 | 100 | let observation_means = observations.mean_axis(Axis(0)).unwrap(); 101 | // let observation_std_dev = observations.std_axis(Axis(0), 0.0); 102 | 103 | behavior_characterization = vec![ 104 | observation_means[[4]], 105 | observation_means[[6]], 106 | observation_means[[8]], 107 | observation_means[[9]], 108 | observation_means[[11]], 109 | observation_means[[13]], 110 | ]; 111 | 112 | // behavior_characterization = observation_means.iter().take(14).cloned().collect(); 113 | /* observation_means 114 | .iter() 115 | .take(14) 116 | .cloned() 117 | .chain(observation_std_dev.iter().take(14).cloned()) 118 | .collect(), */ 119 | 120 | (fitness, behavior_characterization) 121 | }; 122 | 123 | let neat = Runtime::new( 124 | &format!("examples/{}/config.toml", ENV), 125 | Box::new(fitness_function), 126 | ); 127 | 128 | let now = Instant::now(); 129 | 130 | info!(target: "app::parameters", "starting training: {:#?}", neat.parameters); 131 | 132 | if let Some(winner_map) = neat.initilize().find(|elites_map| { 133 | println!( 134 | "map cells: {}, map capacity: {}", 135 | elites_map.len(), 136 | elites_map.capacity() 137 | ); 138 | info!(target: "training::top-fitness", fitness = ?elites_map.top_individual().fitness); 139 | for top in &elites_map.sorted_individuals()[0..5] { 140 | run( 141 | &other_standard_scaler, 142 | top, 143 | 1, 144 | SIMULATION_STEPS, 145 | true, 146 | false, 147 | ); 148 | } 149 | elites_map.top_individual().fitness > REQUIRED_FITNESS 150 | }) { 151 | fs::write( 152 | format!("examples/{}/{}_winner.json", ENV, timestamp()), 153 | serde_json::to_string(&winner_map.top_individual()).unwrap(), 154 | ) 155 | .expect("Unable to write file"); 156 | fs::write( 157 | format!("examples/{}/{}_winner_map.json", ENV, timestamp()), 158 | serde_json::to_string(&winner_map).unwrap(), 159 | ) 160 | .expect("Unable to write file"); 161 | fs::write( 162 | format!("examples/{}/{}_winner_parameters.json", ENV, timestamp()), 163 | serde_json::to_string(&neat.parameters).unwrap(), 164 | ) 165 | .expect("Unable to write file"); 166 | fs::write( 167 | format!( 168 | "examples/{}/{}_winner_standard_scaler.json", 169 | ENV, 170 | timestamp() 171 | ), 172 | serde_json::to_string(&other_standard_scaler).unwrap(), 173 | ) 174 | .expect("Unable to write file"); 175 | 176 | info!( 177 | "winning individual ({},{}) after {} seconds: {:?}", 178 | winner_map.top_individual().nodes().count(), 179 | winner_map.top_individual().feed_forward.len(), 180 | now.elapsed().as_secs(), 181 | winner_map.top_individual() 182 | ); 183 | } 184 | } 185 | 186 | fn run( 187 | standard_scaler: &StandardScaler, 188 | net: &Individual, 189 | runs: usize, 190 | steps: usize, 191 | render: bool, 192 | debug: bool, 193 | ) -> (f64, Array2) { 194 | let gym = gym::GymClient::default(); 195 | let env = gym.make(ENV); 196 | 197 | let mut evaluator = RecurrentMatrixFabricator::fabricate(net.deref()).unwrap(); 198 | // let mut evaluator = LoopingFabricator::fabricate(net).unwrap(); 199 | let mut fitness = 0.0; 200 | let mut all_observations; 201 | 202 | if let SpaceTemplate::BOX { shape, .. } = env.observation_space() { 203 | all_observations = Array2::zeros((1, shape[0])); 204 | } else { 205 | panic!("is no box observation space") 206 | } 207 | 208 | if debug { 209 | dbg!(net); 210 | dbg!(&evaluator); 211 | } 212 | 213 | for run in 0..runs { 214 | evaluator.reset_internal_state(); 215 | let mut recent_observation = env.reset().expect("Unable to reset"); 216 | let mut total_reward = 0.0; 217 | 218 | if debug { 219 | dbg!(run); 220 | } 221 | 222 | for step in 0..steps { 223 | if render { 224 | env.render(); 225 | } 226 | 227 | let mut observations = recent_observation.get_box().unwrap(); 228 | 229 | all_observations = stack![ 230 | Axis(0), 231 | all_observations, 232 | observations.clone().insert_axis(Axis(0)) 233 | ]; 234 | 235 | standard_scaler.scale_inplace(observations.view_mut()); 236 | 237 | // add bias input 238 | let input = stack![Axis(0), observations, [1.0]]; 239 | let output = evaluator.evaluate(input.clone()); 240 | 241 | if debug { 242 | dbg!(&input); 243 | dbg!(&output); 244 | } 245 | 246 | let (observation, reward, is_done) = match env.step(&SpaceData::BOX(output.clone())) { 247 | Ok(State { 248 | observation, 249 | reward, 250 | is_done, 251 | }) => (observation, reward, is_done), 252 | Err(err) => { 253 | error!("evaluation error: {}", err); 254 | dbg!(run); 255 | dbg!(input); 256 | dbg!(output); 257 | dbg!(evaluator); 258 | dbg!(net); 259 | dbg!(all_observations); 260 | panic!("evaluation error"); 261 | } 262 | }; 263 | 264 | recent_observation = observation; 265 | total_reward += reward; 266 | 267 | if is_done { 268 | if render { 269 | println!("finished with reward {} after {} steps", total_reward, step); 270 | } 271 | break; 272 | } 273 | } 274 | fitness += total_reward; 275 | } 276 | 277 | if debug { 278 | dbg!(&all_observations); 279 | } 280 | 281 | (fitness / runs as f64, all_observations) 282 | } 283 | -------------------------------------------------------------------------------- /examples/MountainCarContinuous-v0/main.rs: -------------------------------------------------------------------------------- 1 | use favannat::{ 2 | matrix::fabricator::RecurrentMatrixFabricator, 3 | network::{StatefulEvaluator, StatefulFabricator}, 4 | }; 5 | use gym::{SpaceData, State}; 6 | use map_elites::{Individual, Runtime}; 7 | use ndarray::{stack, Array1, Array2, Axis}; 8 | use tracing::{error, info}; 9 | 10 | use std::time::SystemTime; 11 | use std::{env, fs}; 12 | use std::{ops::Deref, time::Instant}; 13 | 14 | pub const RUNS: usize = 1; 15 | pub const STEPS: usize = 100; 16 | pub const VALIDATION_RUNS: usize = 100; 17 | pub const ENV: &str = "MountainCarContinuous-v0"; 18 | pub const REQUIRED_FITNESS: f64 = 90.0; 19 | 20 | fn main() { 21 | tracing_subscriber::fmt::init(); 22 | let args: Vec = env::args().collect(); 23 | 24 | if let Some(timestamp) = args.get(1) { 25 | let winner_json = 26 | fs::read_to_string(format!("examples/{}/winner.json", ENV)).expect("cant read file"); 27 | let winner: Individual = serde_json::from_str(&winner_json).unwrap(); 28 | let scaler_json = 29 | fs::read_to_string(format!("examples/{}/winner_standard_scaler.json", ENV)) 30 | .expect("cant read file"); 31 | let standard_scaler: (Array1, Array1) = 32 | serde_json::from_str(&scaler_json).unwrap(); 33 | // showcase(standard_scaler, winner); 34 | run(&standard_scaler, &winner, 1, STEPS, true, false); 35 | } else { 36 | train(standard_scaler()); 37 | } 38 | } 39 | 40 | fn standard_scaler() -> (Array1, Array1) { 41 | let gym = gym::GymClient::default(); 42 | let env = gym.make(ENV); 43 | 44 | // collect samples for standard scaler 45 | let samples = env.reset().unwrap().get_box().unwrap(); 46 | 47 | let mut samples = samples.insert_axis(Axis(0)); 48 | 49 | println!("sampling for scaler"); 50 | for i in 0..1000 { 51 | println!("sampling {}", i); 52 | let State { observation, .. } = env.step(&env.action_space().sample()).unwrap(); 53 | 54 | samples = stack![ 55 | Axis(0), 56 | samples, 57 | observation.get_box().unwrap().insert_axis(Axis(0)) 58 | ]; 59 | } 60 | println!("done sampling"); 61 | 62 | let std_dev = samples 63 | .var_axis(Axis(0), 0.0) 64 | .mapv_into(|x| (x + f64::EPSILON).sqrt()); 65 | 66 | dbg!(samples.mean_axis(Axis(0)).unwrap(), std_dev) 67 | } 68 | 69 | fn train(standard_scaler: (Array1, Array1)) { 70 | // log4rs::init_file(format!("examples/{}/log.yaml", ENV), Default::default()).unwrap(); 71 | 72 | info!(target: "app::parameters", "standard scaler: {:?}", &standard_scaler); 73 | 74 | let other_standard_scaler = standard_scaler.clone(); 75 | 76 | let fitness_function = move |individual: &Individual| -> (f64, Vec) { 77 | let standard_scaler = &standard_scaler; 78 | 79 | let (fitness, all_observations) = 80 | run(standard_scaler, individual, RUNS, STEPS, false, false); 81 | 82 | if fitness > 0.0 { 83 | dbg!(fitness); 84 | } 85 | 86 | if fitness >= REQUIRED_FITNESS { 87 | info!("hit task theshold, starting validation runs..."); 88 | 89 | let (validation_fitness, all_observations) = run( 90 | &standard_scaler, 91 | individual, 92 | VALIDATION_RUNS, 93 | STEPS, 94 | false, 95 | false, 96 | ); 97 | 98 | // log possible solutions to file 99 | let individual = individual.clone(); 100 | info!(target: "app::solutions", "{}", serde_json::to_string(&individual).unwrap()); 101 | info!( 102 | "finished validation runs with {} average fitness", 103 | validation_fitness 104 | ); 105 | if validation_fitness > REQUIRED_FITNESS { 106 | // let observation_means = all_observations.mean_axis(Axis(0)).unwrap(); 107 | // let observation_std_dev = all_observations.std_axis(Axis(0), 0.0); 108 | return ( 109 | validation_fitness, 110 | all_observations 111 | .row(all_observations.shape()[0] - 1) 112 | .to_vec()[0..2] 113 | .to_vec(), 114 | ); 115 | } 116 | } 117 | // let observation_means = all_observations.mean_axis(Axis(0)).unwrap(); 118 | // let observation_std_dev = all_observations.std_axis(Axis(0), 0.0); 119 | 120 | /* ( 121 | fitness, 122 | all_observations 123 | .row(all_observations.shape()[0] - 1) 124 | .to_vec()[0..2] 125 | .to_vec(), 126 | ) */ 127 | 128 | ( 129 | fitness, 130 | vec![ 131 | individual.hidden.len() as f64, 132 | individual.feed_forward.len() as f64, 133 | individual.recurrent.len() as f64, 134 | ], 135 | ) 136 | }; 137 | 138 | let runtime = Runtime::new( 139 | &format!("examples/{}/config.toml", ENV), 140 | Box::new(fitness_function), 141 | ); 142 | 143 | let now = Instant::now(); 144 | 145 | info!(target: "app::parameters", "starting training...\nRUNS:{:#?}\nVALIDATION_RUNS:{:#?}\nSTEPS: {:#?}\nREQUIRED_FITNESS:{:#?}\nPARAMETERS: {:#?}", RUNS, VALIDATION_RUNS, STEPS, REQUIRED_FITNESS, runtime.parameters); 146 | 147 | if let Some((_, winner_map)) = runtime.initilize().enumerate().find(|(batch, elites_map)| { 148 | run( 149 | &other_standard_scaler, 150 | &elites_map.top_individual(), 151 | 1, 152 | STEPS, 153 | false, 154 | false, 155 | ); 156 | dbg!("batch {} status: {:?}", batch, elites_map.top_individual()); 157 | elites_map.top_individual().fitness > REQUIRED_FITNESS 158 | }) { 159 | let time_stamp = SystemTime::now() 160 | .duration_since(SystemTime::UNIX_EPOCH) 161 | .unwrap() 162 | .as_secs(); 163 | 164 | fs::write( 165 | format!("examples/{}/winner_map.json", ENV), 166 | serde_json::to_string(&winner_map).unwrap(), 167 | ) 168 | .expect("Unable to write file"); 169 | fs::write( 170 | format!("examples/{}/winner.json", ENV), 171 | serde_json::to_string(&winner_map.top_individual()).unwrap(), 172 | ) 173 | .expect("Unable to write file"); 174 | fs::write( 175 | format!("examples/{}/{}_winner.json", ENV, time_stamp), 176 | serde_json::to_string(&winner_map.top_individual()).unwrap(), 177 | ) 178 | .expect("Unable to write file"); 179 | fs::write( 180 | format!("examples/{}/{}_winner_parameters.json", ENV, time_stamp), 181 | serde_json::to_string(&runtime.parameters).unwrap(), 182 | ) 183 | .expect("Unable to write file"); 184 | fs::write( 185 | format!( 186 | "examples/{}/{}_winner_standard_scaler.json", 187 | ENV, time_stamp 188 | ), 189 | serde_json::to_string(&other_standard_scaler).unwrap(), 190 | ) 191 | .expect("Unable to write file"); 192 | fs::write( 193 | format!("examples/{}/winner_standard_scaler.json", ENV), 194 | serde_json::to_string(&other_standard_scaler).unwrap(), 195 | ) 196 | .expect("Unable to write file"); 197 | 198 | let secs = now.elapsed().as_millis(); 199 | info!( 200 | "winning individual ({},{}) after {} seconds: {:?}", 201 | winner_map.top_individual().nodes().count(), 202 | winner_map.top_individual().feed_forward.len(), 203 | secs as f64 / 1000.0, 204 | winner_map.top_individual() 205 | ); 206 | } 207 | } 208 | 209 | fn run( 210 | standard_scaler: &(Array1, Array1), 211 | net: &Individual, 212 | runs: usize, 213 | steps: usize, 214 | render: bool, 215 | debug: bool, 216 | ) -> (f64, Array2) { 217 | let gym = gym::GymClient::default(); 218 | let env = gym.make(ENV); 219 | 220 | let (means, std_dev) = standard_scaler.clone(); 221 | 222 | let mut evaluator = RecurrentMatrixFabricator::fabricate(net.deref()).unwrap(); 223 | let mut fitness = 0.0; 224 | let mut all_observations = Array2::zeros((1, 2)); 225 | 226 | if debug { 227 | dbg!(net); 228 | dbg!(&evaluator); 229 | } 230 | 231 | for run in 0..runs { 232 | evaluator.reset_internal_state(); 233 | let mut recent_observation = env.reset().expect("Unable to reset"); 234 | let mut total_reward = 0.0; 235 | 236 | if debug { 237 | dbg!(run); 238 | } 239 | 240 | for step in 0..steps { 241 | if render { 242 | env.render(); 243 | } 244 | 245 | let mut observations = recent_observation.get_box().unwrap(); 246 | 247 | all_observations = stack![ 248 | Axis(0), 249 | all_observations, 250 | observations.clone().insert_axis(Axis(0)) 251 | ]; 252 | 253 | observations -= &means; 254 | observations /= &std_dev; 255 | 256 | // add bias input 257 | let input = stack![Axis(0), observations, [1.0]]; 258 | let output = evaluator.evaluate(input.clone()); 259 | 260 | if debug { 261 | dbg!(&input); 262 | dbg!(&output); 263 | } 264 | 265 | let (observation, reward, is_done) = match env.step(&SpaceData::BOX(output.clone())) { 266 | Ok(State { 267 | observation, 268 | reward, 269 | is_done, 270 | }) => (observation, reward, is_done), 271 | Err(err) => { 272 | error!("evaluation error: {}", err); 273 | dbg!(run); 274 | dbg!(input); 275 | dbg!(output); 276 | dbg!(evaluator); 277 | dbg!(net); 278 | dbg!(all_observations); 279 | panic!("evaluation error"); 280 | } 281 | }; 282 | 283 | recent_observation = observation; 284 | total_reward += reward; 285 | 286 | if is_done { 287 | if render { 288 | println!("finished with reward {} after {} steps", total_reward, step); 289 | } 290 | break; 291 | } 292 | } 293 | fitness += total_reward; 294 | } 295 | 296 | if debug { 297 | dbg!(&all_observations); 298 | } 299 | 300 | (fitness / runs as f64, all_observations) 301 | } 302 | -------------------------------------------------------------------------------- /src/elites_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use rand::distributions::{Distribution, WeightedIndex}; 4 | use rand::Rng; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::individual::Individual; 8 | 9 | #[derive(Debug, Clone, Deserialize, Serialize)] 10 | pub struct ElitesMap { 11 | map: HashMap, Individual>, 12 | resolution: usize, 13 | feature_ranges: Vec<(f64, f64)>, 14 | } 15 | 16 | impl ElitesMap { 17 | pub fn new(resolution: usize, feature_ranges: Vec<(f64, f64)>) -> Self { 18 | Self { 19 | map: HashMap::new(), 20 | resolution, 21 | feature_ranges, 22 | } 23 | } 24 | 25 | // #[tracing::instrument] 26 | pub fn place_individual(&mut self, individual: Individual) { 27 | assert!( 28 | individual.behavior.len() == self.feature_ranges.len(), 29 | "behavior descriptor did not match features ranges" 30 | ); 31 | 32 | let cell_index: Vec = individual 33 | .behavior 34 | .iter() 35 | .enumerate() 36 | .zip(self.feature_ranges.iter()) 37 | .map(|((index, mut feature_value), (feature_min, feature_max))| { 38 | // cap feature value to configured interval 39 | if feature_value >= feature_max { 40 | tracing::warn!( 41 | "capping feature {} with value {} to feature_max {}", 42 | index, 43 | feature_value, 44 | feature_max, 45 | ); 46 | feature_value = feature_max; 47 | } 48 | if feature_value < feature_min { 49 | tracing::warn!( 50 | "capping feature {} with value {} to feature_min {}", 51 | index, 52 | feature_value, 53 | feature_min, 54 | ); 55 | feature_value = feature_min; 56 | } 57 | 58 | // add some epsilon to spanned range to have [min, max[ resolution 59 | ((feature_value - feature_min) / (feature_max - feature_min + 1E-15) 60 | * self.resolution as f64) 61 | .floor() as usize 62 | }) 63 | .collect(); 64 | 65 | self.map 66 | .entry(cell_index) 67 | .and_modify(|previous_individual| { 68 | if previous_individual.fitness > individual.fitness { 69 | // fitness did not improve, do nothing 70 | } else { 71 | *previous_individual = individual.clone(); 72 | } 73 | }) 74 | .or_insert(individual); 75 | } 76 | 77 | // ACTUALLY RANDOM 78 | /* pub fn get_random_individual(&self, rng: &mut impl Rng) -> Individual { 79 | self.map 80 | .values() 81 | .choose(rng) 82 | .cloned() 83 | .expect("map did not held any individual") 84 | } */ 85 | 86 | // RANDOM BUT WEIGHTED BY GLOBAL FITNESS 87 | /* pub fn get_random_individual(&self, rng: &mut impl Rng) -> Individual { 88 | let individuals: Vec<&Individual> = self.map.values().collect(); 89 | let fitnesses: Vec = individuals 90 | .iter() 91 | .map(|individual| individual.fitness) 92 | .collect(); 93 | 94 | let mut min_fitness = f64::MAX; 95 | 96 | for &fitness in &fitnesses { 97 | if fitness < min_fitness { 98 | min_fitness = fitness; 99 | } 100 | } 101 | 102 | let weights: Vec = fitnesses 103 | .iter() 104 | // .map(|&fitness| (fitness - min_fitness + 1.0).log2()) 105 | .map(|&fitness| (fitness - min_fitness)) 106 | .collect(); 107 | 108 | let dist = WeightedIndex::new(&weights).unwrap(); 109 | 110 | individuals[dist.sample(rng)].clone() 111 | } */ 112 | 113 | // RANDOM BUT WEIGHTED BY DOMINATED NEIGHBORS 114 | pub fn get_random_individual(&self, rng: &mut impl Rng) -> Individual { 115 | let individuals: Vec<(&Vec, &Individual)> = self.map.iter().collect(); 116 | 117 | let weights: Vec = individuals 118 | .iter() 119 | .map(|(position, individual)| { 120 | let neighbors_count = self.neighbors(position).count() as f64; 121 | let dominated_neighbors_count = self 122 | .neighbors(position) 123 | .filter(|neighbor| individual.fitness > neighbor.fitness) 124 | .count() as f64; 125 | // always count oneself in as dominated to produce non-zero weight 126 | (dominated_neighbors_count + 1.0) / (neighbors_count + 1.0) 127 | }) 128 | .collect(); 129 | 130 | /* if weights.iter().any(|&w| w < 1.0) { 131 | dbg!(&weights); 132 | } */ 133 | 134 | let individuals: Vec<&Individual> = individuals 135 | .iter() 136 | .map(|&(_, individual)| individual) 137 | .collect(); 138 | 139 | let dist = WeightedIndex::new(&weights).unwrap(); 140 | 141 | individuals[dist.sample(rng)].clone() 142 | } 143 | 144 | pub fn update_resolution(&mut self, resolution: usize) { 145 | let stored_individuals = std::mem::replace(&mut self.map, HashMap::new()); 146 | 147 | self.resolution = resolution; 148 | 149 | for (_, individual) in stored_individuals { 150 | self.place_individual(individual); 151 | } 152 | } 153 | 154 | pub fn top_individual(&self) -> Individual { 155 | self.map 156 | .values() 157 | .max_by(|a, b| { 158 | a.fitness 159 | .partial_cmp(&b.fitness) 160 | .expect("could not compare floats") 161 | }) 162 | .cloned() 163 | .expect("map did not held any individual") 164 | } 165 | 166 | pub fn sorted_individuals(&self) -> Vec<&Individual> { 167 | let mut individuals: Vec<&Individual> = self.map.values().collect(); 168 | 169 | individuals.sort_by(|a, b| { 170 | b.fitness 171 | .partial_cmp(&a.fitness) 172 | .expect("could not compare floats") 173 | }); 174 | 175 | individuals 176 | } 177 | 178 | pub fn len(&self) -> usize { 179 | self.map.len() 180 | } 181 | 182 | pub fn capacity(&self) -> usize { 183 | self.resolution.pow(self.feature_ranges.len() as u32) 184 | } 185 | } 186 | 187 | impl ElitesMap { 188 | fn neighbors<'a>(&'a self, position: &'a Vec) -> impl Iterator { 189 | assert!( 190 | position.len() == self.feature_ranges.len(), 191 | "requested neighbors for invalid position {:?}", 192 | position 193 | ); 194 | 195 | assert!( 196 | position.iter().all(|&chunk| chunk < self.resolution), 197 | "position contains invalid chunk index" 198 | ); 199 | 200 | (0..position.len()) 201 | .flat_map(move |feature| { 202 | let maximum = self.resolution - 1; 203 | let mut up = position.clone(); 204 | let mut down = position.clone(); 205 | 206 | match position[feature] { 207 | 0 => { 208 | up[feature] += 1; 209 | vec![up] 210 | } 211 | max_chunk if max_chunk == maximum => { 212 | down[feature] -= 1; 213 | vec![down] 214 | } 215 | _ => { 216 | down[feature] -= 1; 217 | up[feature] += 1; 218 | vec![up, down] 219 | } 220 | } 221 | }) 222 | .flat_map(move |neighbor_position| self.map.get(&neighbor_position)) 223 | } 224 | } 225 | 226 | #[cfg(test)] 227 | mod tests { 228 | use rand::prelude::ThreadRng; 229 | 230 | use super::ElitesMap; 231 | use crate::individual::Individual; 232 | #[test] 233 | fn place_and_retrieve() { 234 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, 5.0)]); 235 | 236 | let individual = Individual { 237 | behavior: vec![3.0], 238 | fitness: 4.2, 239 | ..Default::default() 240 | }; 241 | 242 | elites_map.place_individual(individual); 243 | 244 | assert_eq!( 245 | elites_map 246 | .map 247 | .get(&vec![3]) 248 | .map(|individual| individual.fitness), 249 | Some(4.2) 250 | ); 251 | } 252 | 253 | #[test] 254 | fn get_random() { 255 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, 5.0)]); 256 | 257 | let individual = Individual { 258 | behavior: vec![3.0], 259 | fitness: 4.2, 260 | ..Default::default() 261 | }; 262 | 263 | elites_map.place_individual(individual); 264 | 265 | let mut rng = ThreadRng::default(); 266 | 267 | assert!((elites_map.get_random_individual(&mut rng).fitness - 4.2).abs() < f64::EPSILON); 268 | } 269 | 270 | #[test] 271 | fn multiple_features() { 272 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, 5.0), (-5.0, 5.0)]); 273 | 274 | let individual = Individual { 275 | behavior: vec![3.0, -3.0], 276 | fitness: 4.2, 277 | ..Default::default() 278 | }; 279 | 280 | elites_map.place_individual(individual); 281 | 282 | assert_eq!( 283 | elites_map 284 | .map 285 | .get(&vec![3, 0]) 286 | .map(|individual| individual.fitness), 287 | Some(4.2) 288 | ); 289 | } 290 | 291 | #[test] 292 | fn handle_out_of_feature_range() { 293 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, -1.0), (1.0, 5.0)]); 294 | 295 | let individual = Individual { 296 | behavior: vec![-9.0, 9.0], 297 | fitness: 3.9, 298 | ..Default::default() 299 | }; 300 | 301 | elites_map.place_individual(individual); 302 | 303 | assert_eq!( 304 | elites_map 305 | .map 306 | .get(&vec![0, 3]) 307 | .map(|individual| individual.fitness), 308 | Some(3.9) 309 | ); 310 | } 311 | 312 | #[test] 313 | fn update_when_fitter() { 314 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, 5.0)]); 315 | 316 | let individual_base = Individual { 317 | behavior: vec![3.0], 318 | fitness: 1.0, 319 | ..Default::default() 320 | }; 321 | 322 | let individual_less_fit = Individual { 323 | behavior: vec![3.0], 324 | fitness: 0.0, 325 | ..Default::default() 326 | }; 327 | 328 | let individual_more_fit = Individual { 329 | behavior: vec![3.0], 330 | fitness: 2.0, 331 | ..Default::default() 332 | }; 333 | 334 | elites_map.place_individual(individual_base); 335 | 336 | assert_eq!( 337 | elites_map 338 | .map 339 | .get(&vec![3]) 340 | .map(|individual| individual.fitness), 341 | Some(1.0) 342 | ); 343 | 344 | elites_map.place_individual(individual_less_fit); 345 | 346 | assert_eq!( 347 | elites_map 348 | .map 349 | .get(&vec![3]) 350 | .map(|individual| individual.fitness), 351 | Some(1.0) 352 | ); 353 | 354 | elites_map.place_individual(individual_more_fit); 355 | 356 | assert_eq!( 357 | elites_map 358 | .map 359 | .get(&vec![3]) 360 | .map(|individual| individual.fitness), 361 | Some(2.0) 362 | ); 363 | } 364 | 365 | #[test] 366 | fn update_resolution() { 367 | let mut elites_map = ElitesMap::new(2, vec![(1.0, 2.0)]); 368 | 369 | let individual = Individual { 370 | behavior: vec![1.5], 371 | fitness: 3.9, 372 | ..Default::default() 373 | }; 374 | 375 | elites_map.place_individual(individual); 376 | 377 | assert_eq!( 378 | elites_map 379 | .map 380 | .get(&vec![0]) 381 | .map(|individual| individual.fitness), 382 | Some(3.9) 383 | ); 384 | 385 | elites_map.update_resolution(3); 386 | 387 | assert_eq!( 388 | elites_map 389 | .map 390 | .get(&vec![1]) 391 | .map(|individual| individual.fitness), 392 | Some(3.9) 393 | ); 394 | } 395 | 396 | #[test] 397 | fn get_top_individual() { 398 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, 5.0)]); 399 | 400 | let individual_less_fit = Individual { 401 | behavior: vec![3.0], 402 | fitness: 0.0, 403 | ..Default::default() 404 | }; 405 | 406 | let individual_more_fit = Individual { 407 | behavior: vec![-3.0], 408 | fitness: 2.0, 409 | ..Default::default() 410 | }; 411 | 412 | elites_map.place_individual(individual_less_fit); 413 | elites_map.place_individual(individual_more_fit); 414 | 415 | assert!((elites_map.top_individual().fitness - 2.0).abs() < f64::EPSILON); 416 | } 417 | 418 | #[test] 419 | fn get_capacity() { 420 | let elites_map = ElitesMap::new(4, vec![(-5.0, 5.0), (-5.0, 5.0)]); 421 | 422 | assert_eq!(elites_map.capacity(), 16); 423 | } 424 | 425 | #[test] 426 | fn get_sorted_individuals() { 427 | let mut elites_map = ElitesMap::new(4, vec![(-5.0, 5.0)]); 428 | 429 | let individual_less_fit = Individual { 430 | behavior: vec![3.0], 431 | fitness: 1.0, 432 | ..Default::default() 433 | }; 434 | 435 | let individual_more_fit = Individual { 436 | behavior: vec![-3.0], 437 | fitness: 2.0, 438 | ..Default::default() 439 | }; 440 | 441 | elites_map.place_individual(individual_less_fit); 442 | elites_map.place_individual(individual_more_fit); 443 | 444 | let sorted_individuals = elites_map.sorted_individuals(); 445 | 446 | assert!((sorted_individuals[0].fitness - 2.0).abs() < f64::EPSILON); 447 | assert!((sorted_individuals[1].fitness - 1.0).abs() < f64::EPSILON); 448 | } 449 | 450 | #[test] 451 | fn get_neighbors() { 452 | let mut elites_map = ElitesMap::new(3, vec![(0.0, 3.0)]); 453 | 454 | let individual_up = Individual { 455 | behavior: vec![3.0], 456 | ..Default::default() 457 | }; 458 | 459 | let individual_center = Individual { 460 | behavior: vec![2.0], 461 | ..Default::default() 462 | }; 463 | 464 | let individual_down = Individual { 465 | behavior: vec![1.0], 466 | ..Default::default() 467 | }; 468 | 469 | elites_map.place_individual(individual_up); 470 | elites_map.place_individual(individual_center); 471 | elites_map.place_individual(individual_down); 472 | 473 | let position = vec![1usize]; 474 | 475 | let neighbors: Vec<&Individual> = elites_map.neighbors(&position).collect(); 476 | 477 | dbg!(neighbors.len()); 478 | 479 | assert_eq!(neighbors[0].behavior, vec![3.0]); 480 | assert_eq!(neighbors[1].behavior, vec![1.0]); 481 | } 482 | } 483 | --------------------------------------------------------------------------------