From ca3989421db3b3c582f7057cca65d457e46e4e98 Mon Sep 17 00:00:00 2001 From: vandomej Date: Mon, 11 Mar 2024 15:37:52 -0700 Subject: [PATCH] Update logic for increasing height --- gemla/src/bin/bin.rs | 10 ++-- gemla/src/bin/fighter_nn/mod.rs | 84 ++++++++++++++++----------------- gemla/src/core/mod.rs | 36 +++++++++----- 3 files changed, 68 insertions(+), 62 deletions(-) diff --git a/gemla/src/bin/bin.rs b/gemla/src/bin/bin.rs index 1455d7c..4cb05c8 100644 --- a/gemla/src/bin/bin.rs +++ b/gemla/src/bin/bin.rs @@ -56,15 +56,15 @@ fn main() -> anyhow::Result<()> { let mut gemla = log_error(Gemla::::new( &PathBuf::from(args.file), GemlaConfig { - generations_per_node: 3, - overwrite: true, + generations_per_height: 3, + overwrite: false, }, DataFormat::Json, ))?; - log_error(gemla.simulate(3).await)?; - - Ok(()) + loop { + log_error(gemla.simulate(5).await)?; + } }) }); diff --git a/gemla/src/bin/fighter_nn/mod.rs b/gemla/src/bin/fighter_nn/mod.rs index cabbb29..2033667 100644 --- a/gemla/src/bin/fighter_nn/mod.rs +++ b/gemla/src/bin/fighter_nn/mod.rs @@ -40,26 +40,30 @@ impl GeneticNode for FighterNN { // Check for the highest number of the folder name and increment it by 1 fn initialize(context: &GeneticNodeContext) -> Result, Error> { let base_path = PathBuf::from(BASE_DIR); - - let mut folder = base_path.join(format!("fighter_nn_{:06}", context.id)); - fs::create_dir(&folder)?; - - //Create a new directory for the first generation + + let folder = base_path.join(format!("fighter_nn_{:06}", context.id)); + // Ensures directory is created if it doesn't exist and does nothing if it exists + fs::create_dir_all(&folder) + .with_context(|| format!("Failed to create or access the folder: {:?}", folder))?; + + //Create a new directory for the first generation, using create_dir_all to avoid errors if it already exists let gen_folder = folder.join("0"); - fs::create_dir(&gen_folder)?; - + fs::create_dir_all(&gen_folder) + .with_context(|| format!("Failed to create or access the generation folder: {:?}", gen_folder))?; + // Create the first generation in this folder for i in 0..POPULATION { // Filenames are stored in the format of "xxxxxx_fighter_nn_0.net", "xxxxxx_fighter_nn_1.net", etc. Where xxxxxx is the folder name let nn = gen_folder.join(format!("{:06}_fighter_nn_{}.net", context.id, i)); let mut fann = Fann::new(NEURAL_NETWORK_SHAPE) - .with_context(|| format!("Failed to create nn"))?; + .with_context(|| "Failed to create nn")?; fann.set_activation_func_hidden(ActivationFunc::SigmoidSymmetric); fann.set_activation_func_output(ActivationFunc::SigmoidSymmetric); + // This will overwrite any existing file with the same name fann.save(&nn) - .with_context(|| format!("Failed to save nn"))?; + .with_context(|| format!("Failed to save nn at {:?}", nn))?; } - + Ok(Box::new(FighterNN { id: context.id, folder, @@ -114,7 +118,7 @@ impl GeneticNode for FighterNN { // Create the new generation folder let new_gen_folder = self.folder.join(format!("{}", self.generation + 1)); - fs::create_dir(&new_gen_folder)?; + fs::create_dir_all(&new_gen_folder).with_context(|| format!("Failed to create or access new generation folder: {:?}", new_gen_folder))?; // Remove the 5 nn's with the lowest scores let mut sorted_scores: Vec<_> = self.scores[self.generation as usize].iter().collect(); @@ -195,44 +199,36 @@ impl GeneticNode for FighterNN { fn merge(left: &FighterNN, right: &FighterNN, id: &Uuid) -> Result, Error> { let base_path = PathBuf::from(BASE_DIR); - - // Find next highest let folder = base_path.join(format!("fighter_nn_{:06}", id)); - fs::create_dir(&folder)?; - - //Create a new directory for the first generation - let gen_folder = folder.join("0"); - fs::create_dir(&gen_folder)?; - - // Take the 5 nn's with the highest scores from the left nn's and save them to the new fighter folder - let mut sorted_scores: Vec<_> = left.scores[left.generation as usize].iter().collect(); - sorted_scores.sort_by(|a, b| a.1.partial_cmp(b.1).unwrap()); - let mut remaining = sorted_scores[(left.population_size / 2)..].iter().map(|(k, _)| *k).collect::>(); - for i in 0..(left.population_size / 2) { - let nn = left.folder.join(format!("{}", left.generation)).join(format!("{:06}_fighter_nn_{}.net", left.id, remaining.pop().unwrap())); - let new_nn = folder.join(format!("0")).join(format!("{:06}_fighter_nn_{}.net", id, i)); - trace!("From: {:?}, To: {:?}", &nn, &new_nn); - fs::copy(&nn, &new_nn) - .with_context(|| format!("Failed to copy left nn"))?; - } - - // Take the 5 nn's with the highest scores from the right nn's and save them to the new fighter folder - sorted_scores = right.scores[right.generation as usize].iter().collect(); - sorted_scores.sort_by(|a, b| a.1.partial_cmp(b.1).unwrap()); - remaining = sorted_scores[(right.population_size / 2)..].iter().map(|(k, _)| *k).collect::>(); - for i in (right.population_size / 2)..right.population_size { - let nn = right.folder.join(format!("{}", right.generation)).join(format!("{:06}_fighter_nn_{}.net", right.id, remaining.pop().unwrap())); - let new_nn = folder.join(format!("0")).join(format!("{:06}_fighter_nn_{}.net", id, i)); - trace!("From: {:?}, To: {:?}", &nn, &new_nn); - fs::copy(&nn, &new_nn) - .with_context(|| format!("Failed to copy right nn"))?; - } - + + // Ensure the folder exists, including the generation subfolder. + fs::create_dir_all(&folder.join("0")) + .with_context(|| format!("Failed to create directory {:?}", folder.join("0")))?; + + // Function to copy NNs from a source FighterNN to the new folder. + let copy_nns = |source: &FighterNN, folder: &PathBuf, id: &Uuid, start_idx: usize| -> Result<(), Error> { + let mut sorted_scores: Vec<_> = source.scores[source.generation as usize].iter().collect(); + sorted_scores.sort_by(|a, b| a.1.partial_cmp(b.1).unwrap()); + let remaining = sorted_scores[(source.population_size / 2)..].iter().map(|(k, _)| *k).collect::>(); + + for (i, nn_id) in remaining.into_iter().enumerate() { + let nn_path = source.folder.join(source.generation.to_string()).join(format!("{:06}_fighter_nn_{}.net", source.id, nn_id)); + let new_nn_path = folder.join("0").join(format!("{:06}_fighter_nn_{}.net", id, start_idx + i)); + fs::copy(&nn_path, &new_nn_path) + .with_context(|| format!("Failed to copy nn from {:?} to {:?}", nn_path, new_nn_path))?; + } + Ok(()) + }; + + // Copy the top half of NNs from each parent to the new folder. + copy_nns(left, &folder, id, 0)?; + copy_nns(right, &folder, id, left.population_size as usize / 2)?; + Ok(Box::new(FighterNN { id: *id, folder, generation: 0, - population_size: POPULATION, + population_size: left.population_size, // Assuming left and right have the same population size. scores: vec![HashMap::new()], })) } diff --git a/gemla/src/core/mod.rs b/gemla/src/core/mod.rs index 61df62e..152ed6d 100644 --- a/gemla/src/core/mod.rs +++ b/gemla/src/core/mod.rs @@ -55,7 +55,7 @@ type SimulationTree = Box>>; /// ``` #[derive(Serialize, Deserialize, Copy, Clone)] pub struct GemlaConfig { - pub generations_per_node: u64, + pub generations_per_height: u64, pub overwrite: bool, } @@ -103,12 +103,22 @@ where } pub async fn simulate(&mut self, steps: u64) -> Result<(), Error> { - // Before we can process nodes we must create blank nodes in their place to keep track of which nodes have been processed - // in the tree and which nodes have not. - self.data.mutate(|(d, c)| { - let mut tree: Option> = Gemla::increase_height(d.take(), c, steps); - mem::swap(d, &mut tree); - })?; + // Only increase height if the tree is uninitialized or completed + if self.tree_ref().is_none() || + self + .tree_ref() + .map(|t| Gemla::is_completed(t)) + .unwrap_or(true) + { + // Before we can process nodes we must create blank nodes in their place to keep track of which nodes have been processed + // in the tree and which nodes have not. + self.data.mutate(|(d, c)| { + let mut tree: Option> = Gemla::increase_height(d.take(), c, steps); + mem::swap(d, &mut tree); + })?; + } + + info!( "Height of simulation tree increased to {}", @@ -286,16 +296,16 @@ where if amount == 0 { tree } else { - let left_branch_right = + let left_branch_height = tree.as_ref().map(|t| t.height() as u64).unwrap_or(0) + amount - 1; Some(Box::new(Tree::new( - GeneticNodeWrapper::new(config.generations_per_node), + GeneticNodeWrapper::new(config.generations_per_height), Gemla::increase_height(tree, config, amount - 1), // The right branch height has to equal the left branches total height - if left_branch_right > 0 { + if left_branch_height > 0 { Some(Box::new(btree!(GeneticNodeWrapper::new( - left_branch_right * config.generations_per_node + left_branch_height * config.generations_per_height )))) } else { None @@ -399,7 +409,7 @@ mod tests { // Testing initial creation let mut config = GemlaConfig { - generations_per_node: 1, + generations_per_height: 1, overwrite: true }; let mut gemla = Gemla::::new(&p, config, DataFormat::Json)?; @@ -439,7 +449,7 @@ mod tests { CleanUp::new(&path).run(|p| { // Testing initial creation let config = GemlaConfig { - generations_per_node: 10, + generations_per_height: 10, overwrite: true }; let mut gemla = Gemla::::new(&p, config, DataFormat::Json)?;