Adding self determination of generation length

This commit is contained in:
vandomej 2024-04-08 23:27:28 -07:00
parent 98803b3700
commit 822df77f62
7 changed files with 1089 additions and 180 deletions

163
analyze_data.py Normal file
View file

@ -0,0 +1,163 @@
# Re-importing necessary libraries
import json
import matplotlib.pyplot as plt
from collections import defaultdict
import numpy as np
# Simplified JSON data for demonstration
with open('gemla/round2.json', 'r') as file:
simplified_json_data = json.load(file)
target_node_id = '0c1e64dc-6ddf-4dbb-bf6e-e8218b925194'
# Function to traverse the tree to find a node id
def traverse_left_nodes(node):
if node is None:
return []
left_node = node.get("left")
if left_node is None:
return [node]
return [node] + traverse_left_nodes(left_node)
# Function to traverse the tree to find a node id
def traverse_right_nodes(node):
if node is None:
return []
right_node = node.get("right")
left_node = node.get("left")
if right_node is None and left_node is None:
return []
elif right_node and left_node:
return [right_node] + traverse_right_nodes(left_node)
return []
# Getting the left graph
left_nodes = traverse_left_nodes(simplified_json_data[0])
left_nodes.reverse()
# print(node)
# Print properties available on the first node
node = left_nodes[0]
# print(node["val"].keys())
scores = []
for node in left_nodes:
# print(node)
# print(f'Node ID: {node["val"]["id"]}')
# print(f'Node scores length: {len(node["val"]["node"]["scores"])}')
if node["val"]["node"]:
node_scores = node["val"]["node"]["scores"]
if node_scores:
for score in node_scores:
scores.append(score)
# print(scores)
scores_values = [list(score_set.values()) for score_set in scores]
# Set up the figure for plotting on the same graph
fig, ax = plt.subplots(figsize=(10, 6))
# Generate a boxplot for each set of scores on the same graph
boxplots = ax.boxplot(scores_values, vert=False, patch_artist=True, labels=[f'Set {i+1}' for i in range(len(scores_values))])
# Set figure name to node id
# fig.canvas.set_window_title('Main node line')
# Labeling
ax.set_xlabel(f'Scores - Main Line')
ax.set_ylabel('Score Sets')
ax.yaxis.grid(True) # Add horizontal grid lines for clarity
# Set y-axis labels to be visible
ax.set_yticklabels([f'Set {i+1}' for i in range(len(scores_values))])
# Getting most recent right graph
right_nodes = traverse_right_nodes(simplified_json_data[0])
target_node_id = None
target_node = None
if target_node_id:
for node in right_nodes:
if node["val"]["id"] == target_node_id:
target_node = node
break
else:
target_node = right_nodes[1]
scores = target_node["val"]["node"]["scores"]
scores_values = [list(score_set.values()) for score_set in scores]
# Set up the figure for plotting on the same graph
fig, ax = plt.subplots(figsize=(10, 6))
# Generate a boxplot for each set of scores on the same graph
boxplots = ax.boxplot(scores_values, vert=False, patch_artist=True, labels=[f'Set {i+1}' for i in range(len(scores_values))])
# Labeling
ax.set_xlabel(f'Scores: {target_node['val']['id']}')
ax.set_ylabel('Score Sets')
ax.yaxis.grid(True) # Add horizontal grid lines for clarity
# Set y-axis labels to be visible
ax.set_yticklabels([f'Set {i+1}' for i in range(len(scores_values))])
# Find the highest scoring sets combining all scores and generations
scores = []
for node in left_nodes:
if node["val"]["node"]:
node_scores = node["val"]["node"]["scores"]
translated_node_scores = []
if node_scores:
for i in range(len(node_scores)):
for (individual, score) in node_scores[i].items():
translated_node_scores.append((node["val"]["id"], i, score))
scores.append(translated_node_scores)
# Add scores from the right nodes
for node in right_nodes:
if node["val"]["node"]:
node_scores = node["val"]["node"]["scores"]
translated_node_scores = []
if node_scores:
for i in range(len(node_scores)):
for (individual, score) in node_scores[i].items():
translated_node_scores.append((node["val"]["id"], i, score))
# Organize scores by individual and then by generation
individual_generation_scores = defaultdict(lambda: defaultdict(list))
for sublist in scores:
for id, generation, score in sublist:
individual_generation_scores[id][generation].append(score)
# Calculate Q3 for each individual's generation
individual_generation_q3 = {}
for id, generations in individual_generation_scores.items():
for gen, scores in generations.items():
individual_generation_q3[(id, gen)] = np.percentile(scores, 75)
# Sort by Q3 value, highest first, and select the top 20
top_20_individual_generations = sorted(individual_generation_q3, key=individual_generation_q3.get, reverse=True)[:40]
# Prepare scores for the top 20 for plotting
top_20_scores = [individual_generation_scores[id][gen] for id, gen in top_20_individual_generations]
# Adjust labels for clarity, indicating both the individual ID and generation
labels = [f'{id[:8]}... Gen {gen}' for id, gen in top_20_individual_generations]
# Generate box and whisker plots for the top 20 individual generations
fig, ax = plt.subplots(figsize=(12, 10))
ax.boxplot(top_20_scores, vert=False, patch_artist=True, labels=labels)
ax.set_xlabel('Scores')
ax.set_ylabel('Individual Generation')
ax.set_title('Top 20 Individual Generations by Q3 Value')
# Display the plot
plt.show()

View file

@ -47,10 +47,7 @@ fn main() -> Result<()> {
let mut gemla = log_error(
Gemla::<FighterNN>::new(
&PathBuf::from(args.file),
GemlaConfig {
generations_per_height: 5,
overwrite: false,
},
GemlaConfig { overwrite: false },
DataFormat::Json,
)
.await,

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ const POPULATION_REDUCTION_SIZE: u64 = 3;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TestState {
pub population: Vec<i64>,
pub max_generations: u64,
}
#[async_trait]
@ -26,10 +27,16 @@ impl GeneticNode for TestState {
population.push(thread_rng().gen_range(0..100))
}
Ok(Box::new(TestState { population }))
Ok(Box::new(TestState {
population,
max_generations: 10,
}))
}
async fn simulate(&mut self, _context: GeneticNodeContext<Self::Context>) -> Result<(), Error> {
async fn simulate(
&mut self,
context: GeneticNodeContext<Self::Context>,
) -> Result<bool, Error> {
let mut rng = thread_rng();
self.population = self
@ -38,7 +45,11 @@ impl GeneticNode for TestState {
.map(|p| p.saturating_add(rng.gen_range(-1..2)))
.collect();
Ok(())
if context.generation >= self.max_generations {
Ok(false)
} else {
Ok(true)
}
}
async fn mutate(&mut self, _context: GeneticNodeContext<Self::Context>) -> Result<(), Error> {
@ -93,13 +104,15 @@ impl GeneticNode for TestState {
v = v[..(POPULATION_REDUCTION_SIZE as usize)].to_vec();
let mut result = TestState { population: v };
let mut result = TestState {
population: v,
max_generations: 10,
};
result
.mutate(GeneticNodeContext {
id: *id,
generation: 0,
max_generations: 0,
gemla_context,
})
.await?;
@ -118,7 +131,6 @@ mod tests {
let state = TestState::initialize(GeneticNodeContext {
id: Uuid::new_v4(),
generation: 0,
max_generations: 0,
gemla_context: (),
})
.await
@ -131,6 +143,7 @@ mod tests {
async fn test_simulate() {
let mut state = TestState {
population: vec![1, 1, 2, 3],
max_generations: 1,
};
let original_population = state.population.clone();
@ -139,7 +152,6 @@ mod tests {
.simulate(GeneticNodeContext {
id: Uuid::new_v4(),
generation: 0,
max_generations: 0,
gemla_context: (),
})
.await
@ -153,7 +165,6 @@ mod tests {
.simulate(GeneticNodeContext {
id: Uuid::new_v4(),
generation: 0,
max_generations: 0,
gemla_context: (),
})
.await
@ -162,7 +173,6 @@ mod tests {
.simulate(GeneticNodeContext {
id: Uuid::new_v4(),
generation: 0,
max_generations: 0,
gemla_context: (),
})
.await
@ -177,13 +187,13 @@ mod tests {
async fn test_mutate() {
let mut state = TestState {
population: vec![4, 3, 3],
max_generations: 1,
};
state
.mutate(GeneticNodeContext {
id: Uuid::new_v4(),
generation: 0,
max_generations: 0,
gemla_context: (),
})
.await
@ -196,10 +206,12 @@ mod tests {
async fn test_merge() {
let state1 = TestState {
population: vec![1, 2, 4, 5],
max_generations: 1,
};
let state2 = TestState {
population: vec![0, 1, 3, 7],
max_generations: 1,
};
let merged_state = TestState::merge(&state1, &state2, &Uuid::new_v4(), ())

View file

@ -28,7 +28,6 @@ pub enum GeneticState {
#[derive(Clone, Debug)]
pub struct GeneticNodeContext<S> {
pub generation: u64,
pub max_generations: u64,
pub id: Uuid,
pub gemla_context: S,
}
@ -46,7 +45,8 @@ pub trait GeneticNode: Send {
/// TODO
async fn initialize(context: GeneticNodeContext<Self::Context>) -> Result<Box<Self>, Error>;
async fn simulate(&mut self, context: GeneticNodeContext<Self::Context>) -> Result<(), Error>;
async fn simulate(&mut self, context: GeneticNodeContext<Self::Context>)
-> Result<bool, Error>;
/// Mutates members in a population and/or crossbreeds them to produce new offspring.
///
@ -72,7 +72,6 @@ where
node: Option<T>,
state: GeneticState,
generation: u64,
max_generations: u64,
id: Uuid,
}
@ -85,7 +84,6 @@ where
node: None,
state: GeneticState::Initialize,
generation: 1,
max_generations: 1,
id: Uuid::new_v4(),
}
}
@ -96,19 +94,17 @@ where
T: GeneticNode + Debug + Send + Clone,
T::Context: Send + Sync + Clone + Debug + Serialize + DeserializeOwned + 'static + Default,
{
pub fn new(max_generations: u64) -> Self {
pub fn new() -> Self {
GeneticNodeWrapper::<T> {
max_generations,
..Default::default()
}
}
pub fn from(data: T, max_generations: u64, id: Uuid) -> Self {
pub fn from(data: T, id: Uuid) -> Self {
GeneticNodeWrapper {
node: Some(data),
state: GeneticState::Simulate,
generation: 1,
max_generations,
id,
}
}
@ -125,10 +121,6 @@ where
self.id
}
pub fn max_generations(&self) -> u64 {
self.max_generations
}
pub fn generation(&self) -> u64 {
self.generation
}
@ -140,7 +132,6 @@ where
pub async fn process_node(&mut self, gemla_context: T::Context) -> Result<GeneticState, Error> {
let context = GeneticNodeContext {
generation: self.generation,
max_generations: self.max_generations,
id: self.id,
gemla_context,
};
@ -151,14 +142,15 @@ where
self.state = GeneticState::Simulate;
}
(GeneticState::Simulate, Some(n)) => {
n.simulate(context.clone())
let next_generation = n
.simulate(context.clone())
.await
.with_context(|| format!("Error simulating node: {:?}", self))?;
self.state = if self.generation >= self.max_generations {
GeneticState::Finish
} else {
self.state = if next_generation {
GeneticState::Mutate
} else {
GeneticState::Finish
};
}
(GeneticState::Mutate, Some(n)) => {
@ -187,6 +179,7 @@ mod tests {
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
struct TestState {
pub score: f64,
pub max_generations: u64,
}
#[async_trait]
@ -195,10 +188,14 @@ mod tests {
async fn simulate(
&mut self,
_context: GeneticNodeContext<Self::Context>,
) -> Result<(), Error> {
context: GeneticNodeContext<Self::Context>,
) -> Result<bool, Error> {
self.score += 1.0;
Ok(())
if context.generation >= self.max_generations {
Ok(false)
} else {
Ok(true)
}
}
async fn mutate(
@ -211,7 +208,10 @@ mod tests {
async fn initialize(
_context: GeneticNodeContext<Self::Context>,
) -> Result<Box<TestState>, Error> {
Ok(Box::new(TestState { score: 0.0 }))
Ok(Box::new(TestState {
score: 0.0,
max_generations: 2,
}))
}
async fn merge(
@ -226,13 +226,12 @@ mod tests {
#[test]
fn test_new() -> Result<(), Error> {
let genetic_node = GeneticNodeWrapper::<TestState>::new(10);
let genetic_node = GeneticNodeWrapper::<TestState>::new();
let other_genetic_node = GeneticNodeWrapper::<TestState> {
node: None,
state: GeneticState::Initialize,
generation: 1,
max_generations: 10,
id: genetic_node.id(),
};
@ -243,15 +242,17 @@ mod tests {
#[test]
fn test_from() -> Result<(), Error> {
let val = TestState { score: 0.0 };
let val = TestState {
score: 0.0,
max_generations: 10,
};
let uuid = Uuid::new_v4();
let genetic_node = GeneticNodeWrapper::from(val.clone(), 10, uuid);
let genetic_node = GeneticNodeWrapper::from(val.clone(), uuid);
let other_genetic_node = GeneticNodeWrapper::<TestState> {
node: Some(val),
state: GeneticState::Simulate,
generation: 1,
max_generations: 10,
id: genetic_node.id(),
};
@ -262,9 +263,12 @@ mod tests {
#[test]
fn test_as_ref() -> Result<(), Error> {
let val = TestState { score: 3.0 };
let val = TestState {
score: 3.0,
max_generations: 10,
};
let uuid = Uuid::new_v4();
let genetic_node = GeneticNodeWrapper::from(val.clone(), 10, uuid);
let genetic_node = GeneticNodeWrapper::from(val.clone(), uuid);
let ref_value = genetic_node.as_ref().unwrap();
@ -275,9 +279,12 @@ mod tests {
#[test]
fn test_id() -> Result<(), Error> {
let val = TestState { score: 3.0 };
let val = TestState {
score: 3.0,
max_generations: 10,
};
let uuid = Uuid::new_v4();
let genetic_node = GeneticNodeWrapper::from(val.clone(), 10, uuid);
let genetic_node = GeneticNodeWrapper::from(val.clone(), uuid);
let id_value = genetic_node.id();
@ -286,24 +293,14 @@ mod tests {
Ok(())
}
#[test]
fn test_max_generations() -> Result<(), Error> {
let val = TestState { score: 3.0 };
let uuid = Uuid::new_v4();
let genetic_node = GeneticNodeWrapper::from(val.clone(), 10, uuid);
let max_generations = genetic_node.max_generations();
assert_eq!(max_generations, 10);
Ok(())
}
#[test]
fn test_state() -> Result<(), Error> {
let val = TestState { score: 3.0 };
let val = TestState {
score: 3.0,
max_generations: 10,
};
let uuid = Uuid::new_v4();
let genetic_node = GeneticNodeWrapper::from(val.clone(), 10, uuid);
let genetic_node = GeneticNodeWrapper::from(val.clone(), uuid);
let state = genetic_node.state();
@ -314,7 +311,7 @@ mod tests {
#[tokio::test]
async fn test_process_node() -> Result<(), Error> {
let mut genetic_node = GeneticNodeWrapper::<TestState>::new(2);
let mut genetic_node = GeneticNodeWrapper::<TestState>::new();
assert_eq!(genetic_node.state(), GeneticState::Initialize);
assert_eq!(genetic_node.process_node(()).await?, GeneticState::Simulate);

View file

@ -57,7 +57,6 @@ type SimulationTree<T> = Box<Tree<GeneticNodeWrapper<T>>>;
/// ```
#[derive(Serialize, Deserialize, Copy, Clone)]
pub struct GemlaConfig {
pub generations_per_height: u64,
pub overwrite: bool,
}
@ -126,9 +125,9 @@ where
// 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, _)| {
.mutate(|(d, _, _)| {
let mut tree: Option<SimulationTree<T>> =
Gemla::increase_height(d.take(), c, steps);
Gemla::increase_height(d.take(), steps);
mem::swap(d, &mut tree);
})
.await?;
@ -268,11 +267,7 @@ where
gemla_context.clone(),
)
.await?;
tree.val = GeneticNodeWrapper::from(
*merged_node,
tree.val.max_generations(),
tree.val.id(),
);
tree.val = GeneticNodeWrapper::from(*merged_node, tree.val.id());
}
}
(Some(l), Some(r)) => {
@ -284,11 +279,7 @@ where
trace!("Copying node {}", l.val.id());
if let Some(left_node) = l.val.as_ref() {
GeneticNodeWrapper::from(
left_node.clone(),
tree.val.max_generations(),
tree.val.id(),
);
GeneticNodeWrapper::from(left_node.clone(), tree.val.id());
}
}
(Some(l), None) => Gemla::merge_completed_nodes(l, gemla_context.clone()).await?,
@ -296,11 +287,7 @@ where
trace!("Copying node {}", r.val.id());
if let Some(right_node) = r.val.as_ref() {
tree.val = GeneticNodeWrapper::from(
right_node.clone(),
tree.val.max_generations(),
tree.val.id(),
);
tree.val = GeneticNodeWrapper::from(right_node.clone(), tree.val.id());
}
}
(None, Some(r)) => Gemla::merge_completed_nodes(r, gemla_context.clone()).await?,
@ -353,11 +340,7 @@ where
}
}
fn increase_height(
tree: Option<SimulationTree<T>>,
config: &GemlaConfig,
amount: u64,
) -> Option<SimulationTree<T>> {
fn increase_height(tree: Option<SimulationTree<T>>, amount: u64) -> Option<SimulationTree<T>> {
if amount == 0 {
tree
} else {
@ -365,13 +348,11 @@ where
tree.as_ref().map(|t| t.height() as u64).unwrap_or(0) + amount - 1;
Some(Box::new(Tree::new(
GeneticNodeWrapper::new(config.generations_per_height),
Gemla::increase_height(tree, config, amount - 1),
GeneticNodeWrapper::new(),
Gemla::increase_height(tree, amount - 1),
// The right branch height has to equal the left branches total height
if left_branch_height > 0 {
Some(Box::new(btree!(GeneticNodeWrapper::new(
left_branch_height * config.generations_per_height
))))
Some(Box::new(btree!(GeneticNodeWrapper::new())))
} else {
None
},
@ -446,6 +427,7 @@ mod tests {
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
struct TestState {
pub score: f64,
pub max_generations: u64,
}
#[async_trait]
@ -454,10 +436,10 @@ mod tests {
async fn simulate(
&mut self,
_context: GeneticNodeContext<Self::Context>,
) -> Result<(), Error> {
context: GeneticNodeContext<Self::Context>,
) -> Result<bool, Error> {
self.score += 1.0;
Ok(())
Ok(context.generation < self.max_generations)
}
async fn mutate(
@ -470,7 +452,10 @@ mod tests {
async fn initialize(
_context: GeneticNodeContext<Self::Context>,
) -> Result<Box<TestState>, Error> {
Ok(Box::new(TestState { score: 0.0 }))
Ok(Box::new(TestState {
score: 0.0,
max_generations: 10,
}))
}
async fn merge(
@ -498,10 +483,7 @@ mod tests {
assert!(!path.exists());
// Testing initial creation
let mut config = GemlaConfig {
generations_per_height: 1,
overwrite: true,
};
let mut config = GemlaConfig { overwrite: true };
let mut gemla = Gemla::<TestState>::new(&p, config, DataFormat::Json).await?;
// Now we can use `.await` within the spawned blocking task.
@ -559,10 +541,7 @@ mod tests {
CleanUp::new(&path).run(move |p| {
rt.block_on(async {
// Testing initial creation
let config = GemlaConfig {
generations_per_height: 10,
overwrite: true,
};
let config = GemlaConfig { overwrite: true };
let mut gemla = Gemla::<TestState>::new(&p, config, DataFormat::Json).await?;
// Now we can use `.await` within the spawned blocking task.