use log::{debug, error, info};
use petgraph::dot::Dot;
use petgraph::graph::{DefaultIx, DiGraph, EdgeIndex, NodeIndex};
use petgraph::visit::EdgeRef;
use petgraph::Direction;
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use std::fmt;
use crate::evaluation::{sigmoid, ActivationFn};
fn should_mutate(chance: f32) -> bool {
return (rand::thread_rng().gen_range(0..100) as f32) < (chance * 100.0);
}
pub struct GenomeStats {
pub fitness: f32,
pub raw_fitness: f32,
pub max_fitness: f32,
}
#[derive(Clone, Deserialize, Serialize)]
pub struct Genome {
pub(crate) network: DiGraph<Neuron, Gene>,
pub(crate) n_inputs: usize,
pub(crate) n_outputs: usize,
pub(crate) fitness: f32,
raw_fitness: f32,
pub(crate) max_fitness: f32,
}
impl Genome {
pub(crate) fn new(
n_inputs: usize,
n_outputs: usize,
innovation: &mut u64,
connection_mut_rate: f32,
node_mut_rate: f32,
weight_mut_rate: f32,
perturb_rate: f32,
weight_mut_step_size: f32,
disable_mut_rate: f32,
enable_mut_rate: f32,
) -> Self {
let mut network = DiGraph::<Neuron, Gene>::with_capacity(n_inputs + n_outputs, n_inputs);
let input_idxs = (0..n_inputs)
.map(|_| network.add_node(Neuron::new()))
.collect::<Vec<_>>();
let output_idxs = (0..n_outputs)
.map(|_| network.add_node(Neuron::new()))
.collect::<Vec<_>>();
for input in input_idxs {
for output in &output_idxs {
*innovation += 1;
network.add_edge(input, *output, Gene::new(*innovation));
}
}
let mut genome = Genome {
network,
n_inputs,
n_outputs,
fitness: 0.0,
raw_fitness: 0.0,
max_fitness: 0.0,
};
genome.mutate(
innovation,
connection_mut_rate,
node_mut_rate,
weight_mut_rate,
perturb_rate,
weight_mut_step_size,
disable_mut_rate,
enable_mut_rate,
);
return genome;
}
pub(crate) fn from_parents(g1: &Genome, g2: &Genome) -> Self {
let mut child = g1.clone();
for gene in child.network.edge_weights_mut() {
match g2.gene_by_innovation(gene.innovation) {
Some(gene2) => {
if rand::thread_rng().gen_range(0..=1) == 1 {
gene.weight = gene2.weight;
}
}
None => {}
}
}
return child;
}
pub fn active_network(&self) -> DiGraph<Neuron, Gene> {
return self.network.filter_map(
|idx, neuron| {
if idx.index() < self.n_inputs {
if neuron.activation_value != 0.0 {
return Some(neuron.clone());
}
return None;
}
for edge in self.network.edges_directed(idx, Direction::Incoming) {
if edge.weight().enabled && edge.weight().weight > 0.0 {
let node = self.network.node_weight(edge.source()).unwrap();
if node.activation_value != 0.0 {
return Some(neuron.clone());
}
}
}
return None;
},
|_, gene| {
if gene.enabled {
return Some(gene.clone());
}
return None;
},
);
}
pub fn stats(&self) -> GenomeStats {
return GenomeStats {
fitness: self.fitness,
raw_fitness: self.raw_fitness,
max_fitness: self.max_fitness,
};
}
pub fn get_max_fitness(&self) -> f32 {
return self.max_fitness;
}
pub fn get_outputs(&self) -> Vec<f32> {
let mut outputs: Vec<f32> = vec![];
for i in 0..self.n_outputs {
let node = &self.network[NodeIndex::<DefaultIx>::new(self.n_inputs + i)];
outputs.push(node.activation_value);
}
return outputs;
}
pub fn update_fitness(&mut self, new_fitness: f32) {
self.fitness = new_fitness;
if self.fitness > self.max_fitness {
self.max_fitness = self.fitness
}
}
pub fn evaluate(
&mut self,
inputs: &Vec<f32>,
hidden_activation_fn: Option<ActivationFn>,
output_activation_fn: Option<ActivationFn>,
) {
let activate_hidden = match hidden_activation_fn {
Some(f) => f,
None => sigmoid,
};
let activate_output = match output_activation_fn {
Some(f) => f,
None => sigmoid,
};
info!("Evaluating genome...");
if inputs.len() != self.n_inputs {
error!(
"Wrong number of inputs: {}, need: {}",
inputs.len(),
self.n_inputs
);
panic!(
"Wrong number of inputs: {}, need: {}",
inputs.len(),
self.n_inputs
);
}
let mut i: usize = 0;
debug!("Genome state before loading inputs: {:?}", self);
for neuron in self.network.node_weights_mut() {
if i == inputs.len() {
break;
}
neuron.activation_value = inputs[i];
i += 1;
}
debug!("Genome state after loading inputs: {:?}", self);
let nodes_minus_inputs = self.network.node_count() - self.n_inputs;
debug!("node_count: {}", self.network.node_count());
debug!("nodes_minus_inputs: {}", nodes_minus_inputs);
let mut active_inputs: Vec<bool> = Vec::with_capacity(nodes_minus_inputs);
for _ in 0..nodes_minus_inputs {
active_inputs.push(false);
}
let mut activated: Vec<bool> = Vec::with_capacity(nodes_minus_inputs);
for _ in 0..nodes_minus_inputs {
activated.push(false);
}
let mut bailcount = 0;
loop {
debug!("Evaluation loop...bailcount is {}", bailcount);
if bailcount == 20 {
info!("Bailing evaluation...");
break;
}
bailcount += 1;
debug!("activated: {:?}", activated);
debug!("active_inputs: {:?}", active_inputs);
let mut outputs_active = true;
for i in 0..self.n_outputs {
if !activated[i] {
outputs_active = false;
}
}
if outputs_active {
debug!("All outputs are active!");
break;
}
debug!("Start evaluation loop 1");
for i in 0..(self.network.node_count() - self.n_inputs) {
debug!("Working on non-input node: {}", i);
let idx = NodeIndex::new(i + self.n_inputs);
let mut value = 0.0;
active_inputs[i] = false;
for edge in self.network.edges_directed(idx, Direction::Incoming) {
let gene = edge.weight();
debug!("Found upstream gene: {:?}", gene);
let upstream_neuron = &self.network[edge.source()];
debug!("Found upstream neuron: {:?}", upstream_neuron);
let is_input = edge.source().index() < self.n_inputs;
debug!("Is upstream neuron an input? {}", is_input);
let mut should_activate = false;
if gene.enabled {
if is_input {
should_activate = true;
} else {
if active_inputs[edge.source().index() - self.n_inputs] {
should_activate = true;
}
}
}
if should_activate {
active_inputs[i] = true;
debug!("Activating neuron from upstream");
debug!(
"Gene weight is {}, upstream activation value is {}",
gene.weight, upstream_neuron.activation_value
);
value += gene.weight * upstream_neuron.activation_value;
}
}
debug!("Final value for non-input node {}: {}", i, value);
let neuron = self.network.node_weight_mut(idx).unwrap();
neuron.value = value;
}
debug!("After loop 1, activated: {:?}", activated);
debug!("After loop 1, active_inputs: {:?}", active_inputs);
debug!("Starting evaluation loop 2");
for i in 0..(self.network.node_count() - self.n_inputs) {
debug!("Working on non-input node: {}", i);
let neuron = &mut self.network[NodeIndex::new(i + self.n_inputs)];
debug!("Found neuron: {:?}", neuron);
if active_inputs[i] {
if i < self.n_outputs {
debug!("Neuron is output");
info!("Raw output value is: {}", neuron.value);
neuron.activation_value = activate_output(neuron.value);
info!("Activated output value is: {}", neuron.activation_value);
} else {
debug!("Neuron is hidden");
neuron.activation_value = activate_hidden(neuron.value);
}
activated[i] = true;
}
}
}
}
fn gene_by_innovation(&self, inno: u64) -> Option<&Gene> {
for gene in self.network.edge_weights() {
if gene.innovation == inno {
return Some(gene);
}
}
return None;
}
pub(crate) fn mutate(
&mut self,
innovation: &mut u64,
connection_mut_rate: f32,
node_mut_rate: f32,
weight_mut_rate: f32,
perturb_rate: f32,
weight_mut_step_size: f32,
disable_mut_rate: f32,
enable_mut_rate: f32,
) {
if should_mutate(connection_mut_rate) {
self.mutate_connection(innovation);
} else if should_mutate(node_mut_rate) {
self.mutate_node(innovation);
} else {
if should_mutate(weight_mut_rate) {
self.mutate_weights(perturb_rate, weight_mut_step_size);
}
if should_mutate(disable_mut_rate) {
self.mutate_enable_disable(false);
}
if should_mutate(enable_mut_rate) {
self.mutate_enable_disable(true);
}
}
}
fn mutate_weights(&mut self, perturb_rate: f32, weight_mut_step_size: f32) {
for gene in self.network.edge_weights_mut() {
if should_mutate(perturb_rate) {
gene.weight =
((rand::thread_rng().gen_range(0..10) as f32 * 0.1) * 10.0).round() / 10.0;
} else {
if rand::thread_rng().gen_range(0..=1) > 0 {
gene.weight += weight_mut_step_size;
} else {
gene.weight -= weight_mut_step_size;
}
}
}
}
fn mutate_connection(&mut self, innovation: &mut u64) {
let mut nodes = (0..self.network.node_count()).collect::<Vec<_>>();
nodes.shuffle(&mut thread_rng());
for n1 in &nodes {
let idx1 = NodeIndex::<DefaultIx>::new(*n1);
for n2 in &nodes {
if n1 == n2 {
continue;
}
let idx2 = NodeIndex::<DefaultIx>::new(*n2);
if self.network.find_edge(idx1, idx2).is_none() {
*innovation += 1;
self.network.add_edge(idx1, idx2, Gene::new(*innovation));
return;
}
}
}
}
fn mutate_node(&mut self, innovation: &mut u64) {
let mut edge_idx =
EdgeIndex::<DefaultIx>::new(rand::thread_rng().gen_range(0..self.network.edge_count()));
let mut old_connection = &mut self.network[edge_idx];
for i in 0..101 {
if i == 100 {
return;
}
if old_connection.enabled {
break;
}
edge_idx = EdgeIndex::<DefaultIx>::new(
rand::thread_rng().gen_range(0..self.network.edge_count()),
);
old_connection = &mut self.network[edge_idx];
}
old_connection.enabled = false;
let new_node = Neuron::new();
*innovation += 1;
let mut upstream_connection = Gene::new(*innovation);
upstream_connection.weight = 1.0;
*innovation += 1;
let mut downstream_connection = Gene::new(*innovation);
downstream_connection.weight = old_connection.weight;
let (upstream_idx, downstream_idx) = match self.network.edge_endpoints(edge_idx) {
Some((u, d)) => (u, d),
None => return,
};
let new_idx = self.network.add_node(new_node);
self.network
.add_edge(upstream_idx, new_idx, upstream_connection);
self.network
.add_edge(new_idx, downstream_idx, downstream_connection);
}
fn mutate_enable_disable(&mut self, type_: bool) {
let mut candidates: Vec<&mut Gene> = self
.network
.edge_weights_mut()
.filter(|g| g.enabled != type_)
.collect();
if candidates.len() > 0 {
let choice = rand::thread_rng().gen_range(0..candidates.len());
candidates[choice].enabled = !candidates[choice].enabled;
}
}
}
impl fmt::Display for Genome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
return write!(
f,
"Raw Fitness: {}, Fitness: {}, Max Fitness: {}",
self.raw_fitness, self.fitness, self.max_fitness,
);
}
}
impl fmt::Debug for Genome {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
return write!(f, "Network: {:?}", Dot::new(&self.network));
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Neuron {
pub(crate) activation_value: f32,
pub(crate) value: f32,
}
impl Neuron {
pub(crate) fn new() -> Self {
return Neuron {
activation_value: 0.0,
value: 0.0,
};
}
}
impl fmt::Display for Neuron {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
return write!(f, "{}", self.value,);
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Gene {
pub(crate) weight: f32,
pub(crate) enabled: bool,
pub(crate) innovation: u64,
}
impl Gene {
pub(crate) fn new(innovation: u64) -> Self {
return Gene {
weight: 0.5,
enabled: true,
innovation,
};
}
}
impl fmt::Display for Gene {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
return write!(
f,
"weight: {}, enabled: {}, inno: {}",
self.weight, self.enabled, self.innovation,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn new_genome(inputs: usize, outputs: usize, inno: &mut u64) -> Genome {
return Genome::new(inputs, outputs, inno, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
}
#[test]
fn test_new_genome_no_mutations() {
let mut innovation = 0;
let genome = new_genome(5, 3, &mut innovation);
assert_eq!(innovation, 15);
assert_eq!(genome.network.node_count(), 8);
assert_eq!(genome.network.edge_count(), 15);
assert_eq!(genome.n_inputs, 5);
assert_eq!(genome.n_outputs, 3);
let disabled_edges = genome.network.edge_weights().filter(|e| !e.enabled).count();
let altered_edge_weights = genome
.network
.edge_weights()
.filter(|e| e.weight != 0.5)
.count();
assert_eq!(disabled_edges, 0);
assert_eq!(altered_edge_weights, 0);
}
#[test]
fn test_new_genome_connection_mut() {
let mut innovation = 0;
let genome = Genome::new(5, 3, &mut innovation, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(innovation, 16);
assert_eq!(genome.network.node_count(), 8);
assert_eq!(genome.network.edge_count(), 16);
assert_eq!(genome.n_inputs, 5);
assert_eq!(genome.n_outputs, 3);
let disabled_edges = genome.network.edge_weights().filter(|e| !e.enabled).count();
let altered_edge_weights = genome
.network
.edge_weights()
.filter(|e| e.weight != 0.5)
.count();
assert_eq!(disabled_edges, 0);
assert_eq!(altered_edge_weights, 0);
}
#[test]
fn test_new_genome_node_mut() {
let mut innovation = 0;
let genome = Genome::new(5, 3, &mut innovation, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(innovation, 17);
assert_eq!(genome.network.node_count(), 9);
assert_eq!(genome.network.edge_count(), 17);
assert_eq!(genome.n_inputs, 5);
assert_eq!(genome.n_outputs, 3);
}
#[test]
fn test_genome_from_parents() {
let mut innovation = 0;
let g1 = new_genome(100, 1, &mut innovation);
let mut g2 = g1.clone();
for edge in g2.network.edge_weights_mut() {
edge.weight = 50.0;
}
let baby = Genome::from_parents(&g1, &g2);
for weight in baby.network.edge_weights().map(|e| e.weight) {
assert!(
weight == 0.5 || weight == 50.0,
"unexpected weight: {}",
weight
);
}
}
#[test]
fn test_get_outputs_one_output() {
let mut innovation = 0;
let mut genome = new_genome(5, 1, &mut innovation);
let idx = NodeIndex::new(5);
genome.network[idx].activation_value = 5.0;
assert_eq!(genome.get_outputs(), [5.0]);
}
#[test]
fn test_get_outputs_multiple() {
let mut innovation = 0;
let mut genome = new_genome(5, 3, &mut innovation);
for i in 5..=7 {
let idx = NodeIndex::new(i);
genome.network[idx].activation_value = i as f32;
}
assert_eq!(genome.get_outputs(), [5.0, 6.0, 7.0]);
}
#[test]
fn test_mutate_connection() {
let mut innovation = 0;
let mut genome = new_genome(5, 1, &mut innovation);
assert_eq!(genome.network.edge_count(), 5);
genome.mutate(&mut innovation, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(
genome.network.edge_count(),
6,
"Expected 6 edges after connection mutation!"
);
}
#[test]
fn test_mutate_node() {
let mut innovation = 0;
let mut genome = new_genome(5, 1, &mut innovation);
assert_eq!(genome.network.node_count(), 6);
assert_eq!(genome.network.edge_count(), 5);
genome.mutate(&mut innovation, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
assert_eq!(
genome.network.node_count(),
7,
"Expected 7 nodes after connection mutation!"
);
assert_eq!(
genome.network.edge_count(),
7,
"Expected 7 edges after connection mutation!"
);
let disabled_edges = genome.network.edge_weights().filter(|e| !e.enabled).count();
assert_eq!(
disabled_edges, 1,
"Expected one disabled edge after node mutation"
);
}
#[test]
fn test_mutate_weights_unperturbed() {
let mut innovation = 0;
let mut genome = new_genome(1, 1, &mut innovation);
assert_eq!(genome.network.node_count(), 2);
assert_eq!(genome.network.edge_count(), 1);
genome.mutate(&mut innovation, 0.0, 0.0, 1.0, 0.0, 0.1, 0.0, 0.0);
let idx = EdgeIndex::new(0);
let weight = genome.network[idx].weight;
assert!(
weight == 0.4 || weight == 0.6,
"Edge weight should've increased or decreased"
);
}
#[test]
fn test_mutate_weights_perturbed() {
let mut innovation = 0;
let mut genome = new_genome(1, 1, &mut innovation);
assert_eq!(genome.network.node_count(), 2);
assert_eq!(genome.network.edge_count(), 1);
let idx = EdgeIndex::new(0);
genome.network[idx].weight = -1.0;
genome.mutate(&mut innovation, 0.0, 0.0, 1.0, 1.0, 0.1, 0.0, 0.0);
let weight = genome.network[idx].weight;
assert!(
weight != -1.0,
"Edge weight should've increased or decreased"
);
}
#[test]
fn test_mutate_enable_disable() {
let mut innovation = 0;
let mut genome = new_genome(1, 1, &mut innovation);
assert_eq!(genome.network.node_count(), 2);
assert_eq!(genome.network.edge_count(), 1);
let idx = EdgeIndex::new(0);
genome.mutate(&mut innovation, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
assert_eq!(
genome.network[idx].enabled, false,
"Edge should've been disabled"
);
genome.mutate(&mut innovation, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0);
assert_eq!(
genome.network[idx].enabled, true,
"Edge should've been enabled"
);
}
#[test]
fn test_active_network() {
let mut innovation = 0;
let mut network = DiGraph::<Neuron, Gene>::with_capacity(5, 6);
let mut output_neuron = Neuron::new();
output_neuron.activation_value = 5.0;
let output = network.add_node(output_neuron);
let mut input_neuron = Neuron::new();
input_neuron.activation_value = 4.0;
let input = network.add_node(input_neuron);
let mut hidden_neuron = Neuron::new();
hidden_neuron.activation_value = 3.0;
let hidden = network.add_node(hidden_neuron);
let mut edge1_gene = Gene::new(innovation);
innovation += 1;
edge1_gene.enabled = false;
network.add_edge(input, output, edge1_gene);
let mut edge2_gene = Gene::new(innovation);
innovation += 1;
edge2_gene.weight = 0.4;
let edge2 = network.add_edge(input, hidden, edge2_gene);
let mut edge3_gene = Gene::new(innovation);
innovation += 1;
edge3_gene.weight = 0.6;
let edge3 = network.add_edge(hidden, output, edge3_gene);
let input2 = network.add_node(Neuron::new());
let hidden2 = network.add_node(Neuron::new());
let mut edge4 = Gene::new(innovation);
innovation += 1;
edge4.enabled = false;
network.add_edge(input2, output, edge4);
let edge5 = Gene::new(innovation);
network.add_edge(input2, hidden2, edge5);
let edge6 = Gene::new(innovation);
network.add_edge(hidden2, output, edge6);
let genome = Genome {
network: network.clone(),
n_inputs: 2,
n_outputs: 1,
fitness: 0.0,
raw_fitness: 0.0,
max_fitness: 0.0,
};
let active_network = genome.active_network();
assert_eq!(
active_network.raw_nodes()[0].weight,
network.node_weight(output).unwrap().clone()
);
assert_eq!(
active_network.raw_nodes()[1].weight,
network.node_weight(input).unwrap().clone()
);
assert_eq!(
active_network.raw_nodes()[2].weight,
network.node_weight(hidden).unwrap().clone()
);
assert_eq!(
active_network.raw_edges()[0].weight,
network.edge_weight(edge2).unwrap().clone()
);
assert_eq!(
active_network.raw_edges()[1].weight,
network.edge_weight(edge3).unwrap().clone()
);
}
}