Migrating files over and getting a working build
This commit is contained in:
parent
c4e0064bc1
commit
5ab3c2382e
14 changed files with 4471 additions and 0 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -20,3 +20,8 @@ Cargo.lock
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|
171
analyze_data.py
Normal file
171
analyze_data.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
# 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/round4.json', 'r') as file:
|
||||||
|
simplified_json_data = json.load(file)
|
||||||
|
|
||||||
|
target_node_id = '523f8250-3101-4586-90a1-127ffa6d73d9'
|
||||||
|
|
||||||
|
# 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
|
||||||
|
ax.set_xscale('symlog', linthresh=1.0)
|
||||||
|
|
||||||
|
# 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])
|
||||||
|
if len(right_nodes) != 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[0]
|
||||||
|
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))])
|
||||||
|
|
||||||
|
ax.set_xscale('symlog', linthresh=1.0)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
if len(right_nodes) != 0:
|
||||||
|
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))
|
||||||
|
scores.append(translated_node_scores)
|
||||||
|
|
||||||
|
# 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_xscale('symlog', linthresh=1.0)
|
||||||
|
|
||||||
|
ax.set_xlabel('Scores')
|
||||||
|
ax.set_ylabel('Individual Generation')
|
||||||
|
ax.set_title('Top 20 Individual Generations by Q3 Value')
|
||||||
|
ax.yaxis.grid(True) # Add horizontal grid lines for clarity
|
||||||
|
|
||||||
|
# Display the plot
|
||||||
|
plt.show()
|
||||||
|
|
23
evolved-npcs/Cargo.toml
Normal file
23
evolved-npcs/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "evolved-npcs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.99"
|
||||||
|
async-trait = "0.1.89"
|
||||||
|
clap = { version = "4.5.47", features = ["derive"] }
|
||||||
|
console-subscriber = "0.4.1"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
fann = "0.1.8"
|
||||||
|
file_linked = "0.1.4"
|
||||||
|
futures = "0.3.31"
|
||||||
|
gemla = "0.1.3"
|
||||||
|
lerp = "0.5.0"
|
||||||
|
log = "0.4.28"
|
||||||
|
num_cpus = "1.17.0"
|
||||||
|
rand = "0.9.2"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0.143"
|
||||||
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
|
uuid = "1.18.1"
|
11
evolved-npcs/build.rs
Normal file
11
evolved-npcs/build.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fn main() {
|
||||||
|
// Replace this with the path to the directory containing `fann.lib`
|
||||||
|
let lib_dir = "F://vandomej/Downloads/vcpkg/packages/fann_x64-windows/lib";
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-search=native={}", lib_dir);
|
||||||
|
println!("cargo:rustc-link-lib=static=fann");
|
||||||
|
// Use `dylib=fann` instead of `static=fann` if you're linking dynamically
|
||||||
|
|
||||||
|
// If there are any additional directories where the compiler can find header files, you can specify them like this:
|
||||||
|
// println!("cargo:include={}", path_to_include_directory);
|
||||||
|
}
|
79
evolved-npcs/src/fighter_nn/fighter_context.rs
Normal file
79
evolved-npcs/src/fighter_nn/fighter_context.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use serde::ser::SerializeTuple;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
|
const SHARED_SEMAPHORE_CONCURRENCY_LIMIT: usize = 50;
|
||||||
|
const VISIBLE_SIMULATIONS_CONCURRENCY_LIMIT: usize = 1;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FighterContext {
|
||||||
|
pub shared_semaphore: Arc<Semaphore>,
|
||||||
|
pub visible_simulations: Arc<Semaphore>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FighterContext {
|
||||||
|
fn default() -> Self {
|
||||||
|
FighterContext {
|
||||||
|
shared_semaphore: Arc::new(Semaphore::new(SHARED_SEMAPHORE_CONCURRENCY_LIMIT)),
|
||||||
|
visible_simulations: Arc::new(Semaphore::new(VISIBLE_SIMULATIONS_CONCURRENCY_LIMIT)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom serialization to just output the concurrency limit.
|
||||||
|
impl Serialize for FighterContext {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
// Assuming the semaphore's available permits represent the concurrency limit.
|
||||||
|
// This part is tricky since Semaphore does not expose its initial permits.
|
||||||
|
// You might need to store the concurrency limit as a separate field if this assumption doesn't hold.
|
||||||
|
let concurrency_limit = SHARED_SEMAPHORE_CONCURRENCY_LIMIT;
|
||||||
|
let visible_concurrency_limit = VISIBLE_SIMULATIONS_CONCURRENCY_LIMIT;
|
||||||
|
// serializer.serialize_u64(concurrency_limit as u64)
|
||||||
|
|
||||||
|
// Serialize the concurrency limit as a tuple
|
||||||
|
let mut state = serializer.serialize_tuple(2)?;
|
||||||
|
state.serialize_element(&concurrency_limit)?;
|
||||||
|
state.serialize_element(&visible_concurrency_limit)?;
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom deserialization to reconstruct the FighterContext from a concurrency limit.
|
||||||
|
impl<'de> Deserialize<'de> for FighterContext {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
// Deserialize the tuple
|
||||||
|
let (_, _) = <(usize, usize)>::deserialize(deserializer)?;
|
||||||
|
Ok(FighterContext {
|
||||||
|
shared_semaphore: Arc::new(Semaphore::new(SHARED_SEMAPHORE_CONCURRENCY_LIMIT)),
|
||||||
|
visible_simulations: Arc::new(Semaphore::new(VISIBLE_SIMULATIONS_CONCURRENCY_LIMIT)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization() {
|
||||||
|
let context = FighterContext::default();
|
||||||
|
let serialized = serde_json::to_string(&context).unwrap();
|
||||||
|
let deserialized: FighterContext = serde_json::from_str(&serialized).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
context.shared_semaphore.available_permits(),
|
||||||
|
deserialized.shared_semaphore.available_permits()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
context.visible_simulations.available_permits(),
|
||||||
|
deserialized.visible_simulations.available_permits()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
1626
evolved-npcs/src/fighter_nn/mod.rs
Normal file
1626
evolved-npcs/src/fighter_nn/mod.rs
Normal file
File diff suppressed because it is too large
Load diff
1825
evolved-npcs/src/fighter_nn/neural_network_utility.rs
Normal file
1825
evolved-npcs/src/fighter_nn/neural_network_utility.rs
Normal file
File diff suppressed because it is too large
Load diff
71
evolved-npcs/src/main.rs
Normal file
71
evolved-npcs/src/main.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
extern crate clap;
|
||||||
|
extern crate gemla;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
mod fighter_nn;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use fighter_nn::FighterNN;
|
||||||
|
use file_linked::constants::data_format::DataFormat;
|
||||||
|
use gemla::{
|
||||||
|
core::{Gemla, GemlaConfig},
|
||||||
|
error::log_error,
|
||||||
|
};
|
||||||
|
use std::{path::PathBuf, time::Instant};
|
||||||
|
|
||||||
|
// const NUM_THREADS: usize = 2;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// The file to read/write the dataset from/to.
|
||||||
|
#[arg(short, long)]
|
||||||
|
file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs a simluation of a genetic algorithm against a dataset.
|
||||||
|
///
|
||||||
|
/// Use the -h, --h, or --help flag to see usage syntax.
|
||||||
|
/// TODO
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
env_logger::init();
|
||||||
|
// console_subscriber::init();
|
||||||
|
|
||||||
|
info!("Starting");
|
||||||
|
let now = Instant::now();
|
||||||
|
|
||||||
|
// Manually configure the Tokio runtime
|
||||||
|
let runtime: Result<()> = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.worker_threads(num_cpus::get())
|
||||||
|
// .worker_threads(NUM_THREADS)
|
||||||
|
.build()?
|
||||||
|
.block_on(async {
|
||||||
|
let args = Args::parse(); // Assuming Args::parse() doesn't need to be async
|
||||||
|
let mut gemla = log_error(
|
||||||
|
Gemla::<FighterNN>::new(
|
||||||
|
&PathBuf::from(args.file),
|
||||||
|
GemlaConfig { overwrite: false },
|
||||||
|
DataFormat::Json,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// let gemla_arc = Arc::new(gemla);
|
||||||
|
|
||||||
|
// Setup your application logic here
|
||||||
|
// If `gemla::simulate` needs to run sequentially, simply call it in sequence without spawning new tasks
|
||||||
|
|
||||||
|
// Example placeholder loop to continuously run simulate
|
||||||
|
loop {
|
||||||
|
// Arbitrary loop count for demonstration
|
||||||
|
gemla.simulate(1).await?;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
runtime?; // Handle errors from the block_on call
|
||||||
|
|
||||||
|
info!("Finished in {:?}", now.elapsed());
|
||||||
|
Ok(())
|
||||||
|
}
|
9
extract_fann_data/Cargo.toml
Normal file
9
extract_fann_data/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "extract_fann_data"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
fann = "0.1.8"
|
11
extract_fann_data/build.rs
Normal file
11
extract_fann_data/build.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fn main() {
|
||||||
|
// Replace this with the path to the directory containing `fann.lib`
|
||||||
|
let lib_dir = "F://vandomej/Downloads/vcpkg/packages/fann_x64-windows/lib";
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-search=native={}", lib_dir);
|
||||||
|
println!("cargo:rustc-link-lib=static=fann");
|
||||||
|
// Use `dylib=fann` instead of `static=fann` if you're linking dynamically
|
||||||
|
|
||||||
|
// If there are any additional directories where the compiler can find header files, you can specify them like this:
|
||||||
|
// println!("cargo:include={}", path_to_include_directory);
|
||||||
|
}
|
38
extract_fann_data/src/main.rs
Normal file
38
extract_fann_data/src/main.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
extern crate fann;
|
||||||
|
|
||||||
|
use fann::Fann;
|
||||||
|
use std::os::raw::c_uint;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
eprintln!("Usage: {} <network_file>", args[0]);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let network_file = &args[1];
|
||||||
|
match Fann::from_file(network_file) {
|
||||||
|
Ok(ann) => {
|
||||||
|
// Output layer sizes
|
||||||
|
let layer_sizes = ann.get_layer_sizes();
|
||||||
|
let bias_counts = ann.get_bias_counts();
|
||||||
|
|
||||||
|
println!("Layers:");
|
||||||
|
for (layer_size, bias_count) in layer_sizes.iter().zip(bias_counts.iter()) {
|
||||||
|
println!("{} {}", layer_size, bias_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output connections
|
||||||
|
println!("Connections:");
|
||||||
|
let connections = ann.get_connections();
|
||||||
|
|
||||||
|
for connection in connections {
|
||||||
|
println!("{} {} {}", connection.from_neuron, connection.to_neuron, connection.weight);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error loading network from file {}: {}", network_file, err);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
380
parameter_analysis.py
Normal file
380
parameter_analysis.py
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
# Re-importing necessary libraries
|
||||||
|
import json
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from collections import defaultdict
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import seaborn as sns
|
||||||
|
import matplotlib.colors as mcolors
|
||||||
|
import matplotlib.cm as cm
|
||||||
|
import matplotlib.ticker as ticker
|
||||||
|
|
||||||
|
# Simplified JSON data for demonstration
|
||||||
|
with open('gemla/round4.json', 'r') as file:
|
||||||
|
simplified_json_data = json.load(file)
|
||||||
|
|
||||||
|
# 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 most recent right graph
|
||||||
|
right_nodes = traverse_right_nodes(simplified_json_data[0])
|
||||||
|
|
||||||
|
# Heatmaps
|
||||||
|
# Data structure to store mutation rates, generations, and scores
|
||||||
|
mutation_rate_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# Populate the dictionary with scores indexed by mutation rate and generation
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
minor_mutation_rate = node_val["minor_mutation_rate"]
|
||||||
|
generation = node_val["generation"]
|
||||||
|
# Ensure each score is associated with the correct generation
|
||||||
|
for gen_index, score_list in enumerate(scores):
|
||||||
|
for score in score_list.values():
|
||||||
|
mutation_rate_data[minor_mutation_rate][gen_index].append(score)
|
||||||
|
|
||||||
|
# Prepare data for heatmap
|
||||||
|
max_generation = max(max(gens.keys()) for gens in mutation_rate_data.values())
|
||||||
|
heatmap_data = np.full((len(mutation_rate_data), max_generation + 1), np.nan)
|
||||||
|
|
||||||
|
# Populate the heatmap data with average scores
|
||||||
|
mutation_rates = sorted(mutation_rate_data.keys())
|
||||||
|
for i, mutation_rate in enumerate(mutation_rates):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = mutation_rate_data[mutation_rate][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the heatmap
|
||||||
|
df_heatmap = pd.DataFrame(
|
||||||
|
data=heatmap_data,
|
||||||
|
index=mutation_rates,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Data structure to store major mutation rates, generations, and scores
|
||||||
|
major_mutation_rate_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# Populate the dictionary with scores indexed by major mutation rate and generation
|
||||||
|
# This is assuming the structure to retrieve major_mutation_rate is similar to minor_mutation_rate
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
major_mutation_rate = node_val["major_mutation_rate"]
|
||||||
|
generation = node_val["generation"]
|
||||||
|
for gen_index, score_list in enumerate(scores):
|
||||||
|
for score in score_list.values():
|
||||||
|
major_mutation_rate_data[major_mutation_rate][gen_index].append(score)
|
||||||
|
|
||||||
|
# Prepare the heatmap data for major_mutation_rate similar to minor_mutation_rate
|
||||||
|
major_heatmap_data = np.full((len(major_mutation_rate_data), max_generation + 1), np.nan)
|
||||||
|
major_mutation_rates = sorted(major_mutation_rate_data.keys())
|
||||||
|
|
||||||
|
for i, major_rate in enumerate(major_mutation_rates):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = major_mutation_rate_data[major_rate][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
major_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_major_heatmap = pd.DataFrame(
|
||||||
|
data=major_heatmap_data,
|
||||||
|
index=major_mutation_rates,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# crossbreed_segments
|
||||||
|
# Data structure to store major mutation rates, generations, and scores
|
||||||
|
crossbreed_segments_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# Populate the dictionary with scores indexed by major mutation rate and generation
|
||||||
|
# This is assuming the structure to retrieve major_mutation_rate is similar to minor_mutation_rate
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
crossbreed_segments = node_val["crossbreed_segments"]
|
||||||
|
generation = node_val["generation"]
|
||||||
|
for gen_index, score_list in enumerate(scores):
|
||||||
|
for score in score_list.values():
|
||||||
|
crossbreed_segments_data[crossbreed_segments][gen_index].append(score)
|
||||||
|
|
||||||
|
# Prepare the heatmap data for crossbreed_segments similar to minor_mutation_rate
|
||||||
|
crossbreed_heatmap_data = np.full((len(crossbreed_segments_data), max_generation + 1), np.nan)
|
||||||
|
crossbreed_segments = sorted(crossbreed_segments_data.keys())
|
||||||
|
|
||||||
|
for i, crossbreed_segment in enumerate(crossbreed_segments):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = crossbreed_segments_data[crossbreed_segment][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
crossbreed_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_crossbreed_heatmap = pd.DataFrame(
|
||||||
|
data=crossbreed_heatmap_data,
|
||||||
|
index=crossbreed_segments,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# mutation_weight_range
|
||||||
|
# Data structure to store major mutation rates, generations, and scores
|
||||||
|
mutation_weight_range_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# Populate the dictionary with scores indexed by major mutation rate and generation
|
||||||
|
# This is assuming the structure to retrieve major_mutation_rate is similar to minor_mutation_rate
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
mutation_weight_range = node_val["mutation_weight_range"]
|
||||||
|
positive_extent = mutation_weight_range["end"]
|
||||||
|
negative_extent = -mutation_weight_range["start"]
|
||||||
|
mutation_weight_range = (positive_extent + negative_extent) / 2
|
||||||
|
generation = node_val["generation"]
|
||||||
|
for gen_index, score_list in enumerate(scores):
|
||||||
|
for score in score_list.values():
|
||||||
|
mutation_weight_range_data[mutation_weight_range][gen_index].append(score)
|
||||||
|
|
||||||
|
# Prepare the heatmap data for crossbreed_segments similar to minor_mutation_rate
|
||||||
|
mutation_weight_range_heatmap_data = np.full((len(mutation_weight_range_data), max_generation + 1), np.nan)
|
||||||
|
mutation_weight_ranges = sorted(mutation_weight_range_data.keys())
|
||||||
|
|
||||||
|
for i, mutation_weight_range in enumerate(mutation_weight_ranges):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = mutation_weight_range_data[mutation_weight_range][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
mutation_weight_range_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_mutation_weight_range_heatmap = pd.DataFrame(
|
||||||
|
data=mutation_weight_range_heatmap_data,
|
||||||
|
index=mutation_weight_ranges,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# weight_initialization_range
|
||||||
|
# Data structure to store major mutation rates, generations, and scores
|
||||||
|
weight_initialization_range_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# Populate the dictionary with scores indexed by major mutation rate and generation
|
||||||
|
# This is assuming the structure to retrieve major_mutation_rate is similar to minor_mutation_rate
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
weight_initialization_range = node_val["weight_initialization_range"]
|
||||||
|
positive_extent = weight_initialization_range["end"]
|
||||||
|
negative_extent = -weight_initialization_range["start"]
|
||||||
|
weight_initialization_range = (positive_extent + negative_extent) / 2
|
||||||
|
generation = node_val["generation"]
|
||||||
|
for gen_index, score_list in enumerate(scores):
|
||||||
|
for score in score_list.values():
|
||||||
|
weight_initialization_range_data[weight_initialization_range][gen_index].append(score)
|
||||||
|
|
||||||
|
# Prepare the heatmap data for crossbreed_segments similar to minor_mutation_rate
|
||||||
|
weight_initialization_range_heatmap_data = np.full((len(weight_initialization_range_data), max_generation + 1), np.nan)
|
||||||
|
weight_initialization_ranges = sorted(weight_initialization_range_data.keys())
|
||||||
|
|
||||||
|
for i, weight_initialization_range in enumerate(weight_initialization_ranges):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = weight_initialization_range_data[weight_initialization_range][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
weight_initialization_range_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_weight_initialization_range_heatmap = pd.DataFrame(
|
||||||
|
data=weight_initialization_range_heatmap_data,
|
||||||
|
index=weight_initialization_ranges,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# weight_initialization_range_skew
|
||||||
|
# Data structure to store major mutation rates, generations, and scores
|
||||||
|
weight_initialization_range_skew_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
# Populate the dictionary with scores indexed by major mutation rate and generation
|
||||||
|
# This is assuming the structure to retrieve major_mutation_rate is similar to minor_mutation_rate
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
weight_initialization_range = node_val["weight_initialization_range"]
|
||||||
|
positive_extent = weight_initialization_range["end"]
|
||||||
|
negative_extent = -weight_initialization_range["start"]
|
||||||
|
weight_initialization_range_skew = (positive_extent - negative_extent) / 2
|
||||||
|
generation = node_val["generation"]
|
||||||
|
for gen_index, score_list in enumerate(scores):
|
||||||
|
for score in score_list.values():
|
||||||
|
weight_initialization_range_skew_data[weight_initialization_range_skew][gen_index].append(score)
|
||||||
|
|
||||||
|
# Prepare the heatmap data for crossbreed_segments similar to minor_mutation_rate
|
||||||
|
weight_initialization_range_skew_heatmap_data = np.full((len(weight_initialization_range_skew_data), max_generation + 1), np.nan)
|
||||||
|
weight_initialization_range_skews = sorted(weight_initialization_range_skew_data.keys())
|
||||||
|
|
||||||
|
for i, weight_initialization_range_skew in enumerate(weight_initialization_range_skews):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = weight_initialization_range_skew_data[weight_initialization_range_skew][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
weight_initialization_range_skew_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_weight_initialization_range_skew_heatmap = pd.DataFrame(
|
||||||
|
data=weight_initialization_range_skew_heatmap_data,
|
||||||
|
index=weight_initialization_range_skews,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analyze number of neurons correlation to score
|
||||||
|
# We can get the number of neurons via node_val["nn_shapes"] which contains an array of maps
|
||||||
|
# Each map has a key for the individual id and a value which is an array of integers representing the number of neurons in each layer
|
||||||
|
# We can use the individual id to get the score from the scores array
|
||||||
|
# We then generate a density map of the number of neurons vs the score
|
||||||
|
neuron_number_score_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
nn_shapes = node_val["nn_shapes"]
|
||||||
|
# Both scores and nn_shapes are arrays where score is 1 less in length than nn_shapes (each index corresponds to a generation)
|
||||||
|
for gen_index, score in enumerate(scores):
|
||||||
|
for individual_id, nn_shape in nn_shapes[gen_index].items():
|
||||||
|
neuron_number = sum(nn_shape)
|
||||||
|
# check if score has a value for the individual id
|
||||||
|
if individual_id not in score:
|
||||||
|
continue
|
||||||
|
neuron_number_score_data[neuron_number][gen_index].append(score[individual_id])
|
||||||
|
|
||||||
|
# prepare the density map data
|
||||||
|
neuron_number_score_heatmap_data = np.full((len(neuron_number_score_data), max_generation + 1), np.nan)
|
||||||
|
neuron_numbers = sorted(neuron_number_score_data.keys())
|
||||||
|
|
||||||
|
for i, neuron_number in enumerate(neuron_numbers):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = neuron_number_score_data[neuron_number][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
neuron_number_score_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_neuron_number_score_heatmap = pd.DataFrame(
|
||||||
|
data=neuron_number_score_heatmap_data,
|
||||||
|
index=neuron_numbers,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Analyze number of layers correlation to score
|
||||||
|
nn_layers_score_data = defaultdict(lambda: defaultdict(list))
|
||||||
|
|
||||||
|
for node in right_nodes:
|
||||||
|
node_val = node["val"]["node"]
|
||||||
|
if node_val:
|
||||||
|
scores = node_val["scores"]
|
||||||
|
nn_shapes = node_val["nn_shapes"]
|
||||||
|
# Both scores and nn_shapes are arrays where score is 1 less in length than nn_shapes (each index corresponds to a generation)
|
||||||
|
for gen_index, score in enumerate(scores):
|
||||||
|
for individual_id, nn_shape in nn_shapes[gen_index].items():
|
||||||
|
layer_number = len(nn_shape)
|
||||||
|
# check if score has a value for the individual id
|
||||||
|
if individual_id not in score:
|
||||||
|
continue
|
||||||
|
nn_layers_score_data[layer_number][gen_index].append(score[individual_id])
|
||||||
|
|
||||||
|
# prepare the density map data
|
||||||
|
nn_layers_score_heatmap_data = np.full((len(nn_layers_score_data), max_generation + 1), np.nan)
|
||||||
|
nn_layers = sorted(nn_layers_score_data.keys())
|
||||||
|
|
||||||
|
for i, nn_layer in enumerate(nn_layers):
|
||||||
|
for generation in range(max_generation + 1):
|
||||||
|
scores = nn_layers_score_data[nn_layer][generation]
|
||||||
|
if scores: # Check if there are scores for this generation
|
||||||
|
nn_layers_score_heatmap_data[i, generation] = np.mean(scores)
|
||||||
|
|
||||||
|
# Creating a DataFrame for the major mutation rate heatmap
|
||||||
|
df_nn_layers_score_heatmap = pd.DataFrame(
|
||||||
|
data=nn_layers_score_heatmap_data,
|
||||||
|
index=nn_layers,
|
||||||
|
columns=range(max_generation + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# print("Format: ", custom_formatter(0.123498761234, 0))
|
||||||
|
|
||||||
|
# Creating subplots
|
||||||
|
fig, axs = plt.subplots(2, 2, figsize=(20, 14)) # Creates a 3x2 grid of subplots
|
||||||
|
|
||||||
|
# Plotting the minor mutation rate heatmap
|
||||||
|
sns.heatmap(df_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs[0, 0])
|
||||||
|
# axs[0, 0].set_title('Minor Mutation Rate')
|
||||||
|
axs[0, 0].set_xlabel('Minor Mutation Rate')
|
||||||
|
axs[0, 0].set_ylabel('Generation')
|
||||||
|
axs[0, 0].invert_yaxis()
|
||||||
|
|
||||||
|
# Plotting the major mutation rate heatmap
|
||||||
|
sns.heatmap(df_major_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs[0, 1])
|
||||||
|
# axs[0, 1].set_title('Major Mutation Rate')
|
||||||
|
axs[0, 1].set_xlabel('Major Mutation Rate')
|
||||||
|
axs[0, 1].invert_yaxis()
|
||||||
|
|
||||||
|
# Plotting the crossbreed_segments heatmap
|
||||||
|
sns.heatmap(df_crossbreed_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs[1, 0])
|
||||||
|
# axs[1, 0].set_title('Crossbreed Segments')
|
||||||
|
axs[1, 0].set_xlabel('Crossbreed Segments')
|
||||||
|
axs[1, 0].set_ylabel('Generation')
|
||||||
|
axs[1, 0].invert_yaxis()
|
||||||
|
|
||||||
|
# Plotting the mutation_weight_range heatmap
|
||||||
|
sns.heatmap(df_mutation_weight_range_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs[1, 1])
|
||||||
|
# axs[1, 1].set_title('Mutation Weight Range')
|
||||||
|
axs[1, 1].set_xlabel('Mutation Weight Range')
|
||||||
|
axs[1, 1].invert_yaxis()
|
||||||
|
|
||||||
|
fig3, axs3 = plt.subplots(1, 2, figsize=(20, 14)) # Creates a 3x2 grid of subplots
|
||||||
|
|
||||||
|
# Plotting the weight_initialization_range heatmap
|
||||||
|
sns.heatmap(df_weight_initialization_range_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs3[0])
|
||||||
|
# axs[2, 0].set_title('Weight Initialization Range')
|
||||||
|
axs3[0].set_xlabel('Weight Initialization Range')
|
||||||
|
axs3[0].set_ylabel('Generation')
|
||||||
|
axs3[0].invert_yaxis()
|
||||||
|
|
||||||
|
# Plotting the weight_initialization_range_skew heatmap
|
||||||
|
sns.heatmap(df_weight_initialization_range_skew_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs3[1])
|
||||||
|
# axs[2, 1].set_title('Weight Initialization Range Skew')
|
||||||
|
axs3[1].set_xlabel('Weight Initialization Range Skew')
|
||||||
|
axs3[1].set_ylabel('Generation')
|
||||||
|
axs3[1].invert_yaxis()
|
||||||
|
|
||||||
|
# Creating a new window for the scatter plots
|
||||||
|
fig2, axs2 = plt.subplots(2, 1, figsize=(20, 14)) # Creates a 2x1 grid of subplots
|
||||||
|
|
||||||
|
# Plotting the neuron number vs score heatmap
|
||||||
|
sns.heatmap(df_neuron_number_score_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs2[1])
|
||||||
|
# axs[3, 1].set_title('Neuron Number vs. Score')
|
||||||
|
axs2[1].set_xlabel('Neuron Number')
|
||||||
|
axs2[1].set_ylabel('Generation')
|
||||||
|
axs2[1].invert_yaxis()
|
||||||
|
|
||||||
|
# Plotting the number of layers vs score heatmap
|
||||||
|
sns.heatmap(df_nn_layers_score_heatmap.T, cmap='viridis', fmt=".4g", cbar_kws={'label': 'Mean Score'}, ax=axs2[0])
|
||||||
|
# axs[3, 1].set_title('Number of Layers vs. Score')
|
||||||
|
axs2[0].set_xlabel('Number of Layers')
|
||||||
|
axs2[0].set_ylabel('Generation')
|
||||||
|
axs2[0].invert_yaxis()
|
||||||
|
|
||||||
|
# Display the plot
|
||||||
|
plt.tight_layout() # Adjusts the subplots to fit into the figure area.
|
||||||
|
plt.show()
|
118
visualize_networks.py
Normal file
118
visualize_networks.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
import subprocess
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog
|
||||||
|
|
||||||
|
def select_file():
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # Hide the main window
|
||||||
|
file_path = filedialog.askopenfilename(
|
||||||
|
initialdir="/", # Set the initial directory to search for files
|
||||||
|
title="Select file",
|
||||||
|
filetypes=(("Net files", "*.net"), ("All files", "*.*"))
|
||||||
|
)
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
def get_fann_data(network_file):
|
||||||
|
# Adjust the path to the Rust executable as needed
|
||||||
|
result = subprocess.run(['./extract_fann_data/target/debug/extract_fann_data.exe', network_file], capture_output=True, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print("Error:", result.stderr)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
layer_sizes = []
|
||||||
|
connections = []
|
||||||
|
parsing_connections = False
|
||||||
|
|
||||||
|
for line in result.stdout.splitlines():
|
||||||
|
if line.startswith("Layers:"):
|
||||||
|
continue
|
||||||
|
elif line.startswith("Connections:"):
|
||||||
|
parsing_connections = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if parsing_connections:
|
||||||
|
from_neuron, to_neuron, weight = map(float, line.split())
|
||||||
|
connections.append((int(from_neuron), int(to_neuron), weight))
|
||||||
|
else:
|
||||||
|
layer_size, bias_count = map(int, line.split())
|
||||||
|
layer_sizes.append((layer_size, bias_count))
|
||||||
|
|
||||||
|
return layer_sizes, connections
|
||||||
|
|
||||||
|
def visualize_fann_network(network_file):
|
||||||
|
# Get network data
|
||||||
|
layer_sizes, connections = get_fann_data(network_file)
|
||||||
|
if layer_sizes is None or connections is None:
|
||||||
|
return # Error handling in get_fann_data should provide error output
|
||||||
|
|
||||||
|
# Create a directed graph
|
||||||
|
G = nx.DiGraph()
|
||||||
|
|
||||||
|
# Positions dictionary to hold the position of each neuron
|
||||||
|
pos = {}
|
||||||
|
node_count = 0
|
||||||
|
x_spacing = 1.0
|
||||||
|
y_spacing = 1.0
|
||||||
|
|
||||||
|
# Calculate the maximum layer size for proper spacing
|
||||||
|
max_layer_size = max(size for size, bias in layer_sizes)
|
||||||
|
|
||||||
|
# Build nodes and position them layer by layer from left to right
|
||||||
|
for layer_index, (layer_size, bias_count) in enumerate(layer_sizes):
|
||||||
|
y_positions = list(range(-layer_size-bias_count+1, 1, 1)) # Center-align vertically
|
||||||
|
y_positions = [y * (max_layer_size / (layer_size + bias_count)) * y_spacing for y in y_positions] # Adjust spacing
|
||||||
|
for neuron_index in range(layer_size + bias_count): # Include bias neurons
|
||||||
|
node_label = f"L{layer_index}N{neuron_index}"
|
||||||
|
G.add_node(node_count, label=node_label)
|
||||||
|
pos[node_count] = (layer_index * x_spacing, y_positions[neuron_index % len(y_positions)])
|
||||||
|
node_count += 1
|
||||||
|
|
||||||
|
# Add connections to the graph
|
||||||
|
for from_neuron, to_neuron, weight in connections:
|
||||||
|
G.add_edge(from_neuron, to_neuron, weight=weight)
|
||||||
|
|
||||||
|
max_weight = max(abs(weight) for _, _, weight in connections)
|
||||||
|
print(f"Max weight: {max_weight}")
|
||||||
|
|
||||||
|
# Draw nodes
|
||||||
|
nx.draw_networkx_nodes(G, pos, node_color='skyblue', node_size=200)
|
||||||
|
nx.draw_networkx_labels(G, pos, font_size=7)
|
||||||
|
|
||||||
|
# Custom function for edge properties
|
||||||
|
def adjust_properties(weight):
|
||||||
|
# if weight > 0:
|
||||||
|
# print("Weight:", weight)
|
||||||
|
color = 'green' if weight > 0 else 'red'
|
||||||
|
alpha = min((abs(weight) / max_weight) ** 3, 1)
|
||||||
|
# print(f"Color: {color}, Alpha: {alpha}")
|
||||||
|
return color, alpha
|
||||||
|
|
||||||
|
# Draw edges with custom properties
|
||||||
|
for u, v, d in G.edges(data=True):
|
||||||
|
color, alpha = adjust_properties(d['weight'])
|
||||||
|
nx.draw_networkx_edges(G, pos, edgelist=[(u, v)], edge_color=color, alpha=alpha, width=1.5, arrows=False)
|
||||||
|
|
||||||
|
# Show plot
|
||||||
|
plt.title('FANN Network Visualization')
|
||||||
|
plt.axis('off') # Turn off the axis
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
# Path to the FANN network file
|
||||||
|
fann_path = 'F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_4f2be613-ab26-4384-9a65-450e043984ea\\6\\4f2be613-ab26-4384-9a65-450e043984ea_fighter_nn_0.net'
|
||||||
|
# fann_path = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_fc294503-7b2a-40f8-be59-ccc486eb3f79\\0\\fc294503-7b2a-40f8-be59-ccc486eb3f79_fighter_nn_0.net"
|
||||||
|
# fann_path = 'F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_99c30a7f-40ab-4faf-b16a-b44703fdb6cd\\0\\99c30a7f-40ab-4faf-b16a-b44703fdb6cd_fighter_nn_0.net'
|
||||||
|
# Has a 4 layer network
|
||||||
|
# # Generation 1
|
||||||
|
# fann_path = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98\\1\\16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98_fighter_nn_0.net"
|
||||||
|
# # Generation 5
|
||||||
|
# fann_path = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98\\5\\16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98_fighter_nn_0.net"
|
||||||
|
# # Generation 10
|
||||||
|
# fann_path = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98\\10\\16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98_fighter_nn_0.net"
|
||||||
|
# # Generation 20
|
||||||
|
# fann_path = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98\\20\\16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98_fighter_nn_0.net"
|
||||||
|
# # Generation 32
|
||||||
|
# fann_path = "F:\\\\vandomej\\Projects\\dootcamp-AI-Simulation\\Simulations\\fighter_nn_16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98\\32\\16dfa1b4-03c7-45a6-84b4-22fe3c8e2d98_fighter_nn_0.net"
|
||||||
|
fann_path = select_file()
|
||||||
|
visualize_fann_network(fann_path)
|
104
visualize_simulation_tree.py
Normal file
104
visualize_simulation_tree.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# Re-importing necessary libraries
|
||||||
|
import json
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
import random
|
||||||
|
|
||||||
|
def hierarchy_pos(G, root=None, width=1., vert_gap=0.2, vert_loc=0, xcenter=0.5):
|
||||||
|
if not nx.is_tree(G):
|
||||||
|
raise TypeError('cannot use hierarchy_pos on a graph that is not a tree')
|
||||||
|
|
||||||
|
if root is None:
|
||||||
|
if isinstance(G, nx.DiGraph):
|
||||||
|
root = next(iter(nx.topological_sort(G)))
|
||||||
|
else:
|
||||||
|
root = random.choice(list(G.nodes))
|
||||||
|
|
||||||
|
def _hierarchy_pos(G, root, width=2., vert_gap=0.2, vert_loc=0, xcenter=0.5, pos=None, parent=None):
|
||||||
|
if pos is None:
|
||||||
|
pos = {root: (xcenter, vert_loc)}
|
||||||
|
else:
|
||||||
|
pos[root] = (xcenter, vert_loc)
|
||||||
|
children = list(G.successors(root)) # Use successors to get children for DiGraph
|
||||||
|
if not isinstance(G, nx.DiGraph):
|
||||||
|
if parent is not None:
|
||||||
|
children.remove(parent)
|
||||||
|
if len(children) != 0:
|
||||||
|
dx = width / len(children)
|
||||||
|
nextx = xcenter - width / 2 - dx / 2
|
||||||
|
for child in children:
|
||||||
|
nextx += dx
|
||||||
|
pos = _hierarchy_pos(G, child, width=dx*2.0, vert_gap=vert_gap,
|
||||||
|
vert_loc=vert_loc - vert_gap, xcenter=nextx,
|
||||||
|
pos=pos, parent=root)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter)
|
||||||
|
|
||||||
|
# Simplified JSON data for demonstration
|
||||||
|
with open('gemla/round4.json', 'r') as file:
|
||||||
|
simplified_json_data = json.load(file)
|
||||||
|
|
||||||
|
# Function to traverse the tree and create a graph
|
||||||
|
def traverse(node, graph, parent=None):
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
node_id = node["val"]["id"]
|
||||||
|
if "node" in node["val"] and node["val"]["node"]:
|
||||||
|
scores = node["val"]["node"]["scores"]
|
||||||
|
generations = node["val"]["node"]["generation"]
|
||||||
|
population_size = node["val"]["node"]["population_size"]
|
||||||
|
# Prepare to track the highest score across all generations and the corresponding individual
|
||||||
|
overall_max_score = float('-inf')
|
||||||
|
overall_max_score_individual = None
|
||||||
|
overall_max_score_gen = None
|
||||||
|
|
||||||
|
for gen, gen_scores in enumerate(scores):
|
||||||
|
if gen_scores: # Ensure the dictionary is not empty
|
||||||
|
# Find the max score and the individual for this generation
|
||||||
|
max_score_for_gen = max(gen_scores.values())
|
||||||
|
individual_with_max_score_for_gen = max(gen_scores, key=gen_scores.get)
|
||||||
|
|
||||||
|
# if max_score_for_gen > overall_max_score:
|
||||||
|
overall_max_score = max_score_for_gen
|
||||||
|
overall_max_score_individual = individual_with_max_score_for_gen
|
||||||
|
overall_max_score_gen = gen
|
||||||
|
|
||||||
|
# print debug statement
|
||||||
|
# print(f"Node {node_id}: Max score: {overall_max_score:.6f} (Individual {overall_max_score_individual} in Gen {overall_max_score_gen})")
|
||||||
|
# print(f"Left: {node.get('left')}, Right: {node.get('right')}")
|
||||||
|
label = f"{node_id}\nGenerations: {generations}, Population: {population_size}\nMax score: {overall_max_score:.6f} (Individual {overall_max_score_individual} in Gen {overall_max_score_gen + 1 if overall_max_score_gen is not None else 'N/A'})"
|
||||||
|
else:
|
||||||
|
label = node_id
|
||||||
|
|
||||||
|
graph.add_node(node_id, label=label)
|
||||||
|
if parent:
|
||||||
|
graph.add_edge(parent, node_id)
|
||||||
|
|
||||||
|
traverse(node.get("left"), graph, parent=node_id)
|
||||||
|
traverse(node.get("right"), graph, parent=node_id)
|
||||||
|
|
||||||
|
|
||||||
|
# Create a directed graph
|
||||||
|
G = nx.DiGraph()
|
||||||
|
|
||||||
|
# Populate the graph
|
||||||
|
traverse(simplified_json_data[0], G)
|
||||||
|
|
||||||
|
# Find the root node (a node with no incoming edges)
|
||||||
|
root_candidates = [node for node, indeg in G.in_degree() if indeg == 0]
|
||||||
|
|
||||||
|
if root_candidates:
|
||||||
|
root_node = root_candidates[0] # Assuming there's only one root candidate
|
||||||
|
else:
|
||||||
|
root_node = None # This should ideally never happen in a properly structured tree
|
||||||
|
|
||||||
|
# Use the determined root node for hierarchy_pos
|
||||||
|
if root_node is not None:
|
||||||
|
pos = hierarchy_pos(G, root=root_node)
|
||||||
|
labels = nx.get_node_attributes(G, 'label')
|
||||||
|
nx.draw(G, pos, labels=labels, with_labels=True, arrows=True)
|
||||||
|
plt.show()
|
||||||
|
else:
|
||||||
|
print("No root node found. Cannot draw the tree.")
|
Loading…
Add table
Reference in a new issue