use std::f32::consts::E;
use std::{cell::RefCell, cmp::Ordering};
use rand::{thread_rng, Rng};
pub const TANH: &dyn Fn(f32) -> f32 = &|x| (E.powf(x) - E.powf(-x)) / (E.powf(x) + E.powf(-x));
#[derive(Clone)]
pub struct Agent<'a> {
inputs: usize,
outputs: usize,
nodes: usize,
data_lists: Vec<f32>,
connection_list: Vec<Connection>,
activation_func: &'a dyn Fn(f32) -> f32,
}
#[derive(Clone, Copy)]
pub struct Connection {
start_node: usize,
end_node: usize,
enabled: bool,
weight: f32,
innovation_number: usize,
}
thread_local! {
static INNOVATION_COUNTER: RefCell<usize> = RefCell::new(0);
}
fn generate_innovation_number() -> usize {
INNOVATION_COUNTER.with(|counter| {
let mut num = counter.borrow_mut();
*num += 1;
*num
})
}
impl Agent<'_> {
pub fn create_agents(
amount: i32,
inputs: usize,
outputs: usize,
activation_func: &'static dyn Fn(f32) -> f32,
) -> Vec<Self> {
let mut result: Vec<Self> = vec![];
for _ in 0..amount {
result.push(Agent {
inputs,
outputs,
nodes: inputs + outputs,
data_lists: vec![0.0; (inputs + outputs).try_into().unwrap()],
connection_list: vec![],
activation_func: activation_func,
})
}
return result;
}
pub fn calculate(&mut self, input: &Vec<f32>) -> Vec<f32> {
if input.len() != self.inputs {
panic!(
"Input size ({}) doesn't match target input size ({})",
input.len(),
self.inputs
);
} else {
for idx in 0..(self.inputs) {
self.data_lists[idx] = input[idx];
}
}
for idx in (self.inputs)..(self.data_lists.len()) {
self.data_lists[idx] = 0.0;
}
self.sort_connections();
for connection in &self.connection_list {
if connection.enabled {
self.data_lists[connection.end_node] +=
(self.activation_func)(self.data_lists[connection.start_node])
* connection.weight;
}
}
return self.data_lists[self.inputs..self.inputs + self.outputs].to_vec();
}
pub fn sort_connections(&mut self) {
self.connection_list
.sort_by(|a, b| match a.start_node.cmp(&b.start_node) {
Ordering::Equal => a.end_node.cmp(&b.end_node),
ord => ord,
})
}
pub fn reproduce(
&self,
new_node_chance: f32,
new_connection_chance: f32,
reset_weight_chance: f32,
change_weight_chance: f32,
max_weight: f32,
max_weight_change: f32,
) -> Self {
let mut new_agent = Agent {
inputs: self.inputs,
outputs: self.outputs,
nodes: self.nodes,
data_lists: self.data_lists.clone(),
connection_list: self.connection_list.clone(),
activation_func: self.activation_func,
};
let mut rng = thread_rng();
if rng.gen_range(0.0..1.0) < new_node_chance && new_agent.connection_list.len() > 0 {
if let Some(connection) = new_agent
.clone()
.connection_list
.iter_mut()
.filter(|c| c.enabled)
.next()
{
connection.enabled = false;
let innovation_number_1 = generate_innovation_number();
let innovation_number_2 = generate_innovation_number();
new_agent.connection_list.push(Connection {
start_node: connection.start_node,
end_node: new_agent.nodes,
weight: 1.0, enabled: true,
innovation_number: innovation_number_1,
});
new_agent.connection_list.push(Connection {
start_node: new_agent.nodes,
end_node: connection.end_node,
weight: connection.weight, enabled: true,
innovation_number: innovation_number_2,
});
new_agent.data_lists.push(0.0);
new_agent.nodes += 1;
}
}
if rng.gen_range(0.0..1.0) < new_connection_chance {
let mut start_node = rng.gen_range(0..(self.nodes - self.outputs));
if start_node > self.inputs {
start_node += self.outputs; }
let end_node = rng.gen_range(self.inputs..self.nodes);
if !new_agent
.connection_list
.iter()
.any(|c| c.start_node == start_node && c.end_node == end_node)
{
let innovation_number = generate_innovation_number();
let new_connection = Connection {
start_node,
end_node,
enabled: true,
weight: rng.gen_range(-max_weight..max_weight),
innovation_number,
};
new_agent.connection_list.push(new_connection);
}
}
if rng.gen_range(0.0..1.0) < reset_weight_chance && new_agent.connection_list.len() > 0 {
let idx: usize = rng.gen_range(0..new_agent.connection_list.len());
new_agent.connection_list[idx].weight = rng.gen_range(-max_weight..max_weight);
}
if rng.gen_range(0.0..1.0) < change_weight_chance && new_agent.connection_list.len() > 0 {
let idx: usize = rng.gen_range(0..new_agent.connection_list.len());
new_agent.connection_list[idx].weight +=
rng.gen_range(-max_weight_change..max_weight_change);
new_agent.connection_list[idx].weight = new_agent.connection_list[idx]
.weight
.clamp(-max_weight, max_weight);
}
return new_agent;
}
pub fn print(&mut self) {
self.sort_connections();
println!();
println!("--------------------");
println!("Nodes: {} ", self.nodes - self.inputs - self.outputs);
println!("Connections: {} ", self.connection_list.len());
for idx in 0..self.connection_list.len() {
if self.connection_list[idx].enabled {
println!();
println!("Connection: {}", idx);
println!(" From : {}", self.connection_list[idx].start_node);
println!(" To : {}", self.connection_list[idx].end_node);
println!(" Weight : {}", self.connection_list[idx].weight);
println!(
" Id : {}",
self.connection_list[idx].innovation_number
);
}
}
println!("--------------------")
}
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use super::*;
#[test]
fn example_use() {
let mut agents = Agent::create_agents(100, 2, 1, TANH);
let mut rng = thread_rng();
const EPOCHS: i32 = 5000;
const PRINT_EPOCH: i32 = 10;
const SELECTION: usize = 10;
for epoch in 0..EPOCHS {
if epoch % PRINT_EPOCH == 0 {
println!("Epoch: {}", epoch);
}
let input = vec![rng.gen_range(-1.0..1.0), 1.0];
let mut result: Vec<f32> = vec![];
for agent in agents.iter_mut() {
result.push(agent.calculate(&input)[0]);
}
let mut new_agents: Vec<Agent> = vec![];
for idx in 0..agents.len() / SELECTION {
let index_of_max: usize = result
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(Ordering::Equal))
.map(|(index, _)| index)
.unwrap();
let best_agent = agents.remove(index_of_max);
for _ in 0..(SELECTION - 1) {
new_agents.push(best_agent.reproduce(0.02, 0.08, 0.02, 0.3, 2.0, 0.2));
}
new_agents.push(best_agent);
if idx == 0 && epoch % PRINT_EPOCH == 0 {
println!("Best result: {}", result[index_of_max]);
}
result.remove(index_of_max);
}
agents = new_agents;
}
agents[SELECTION].print();
}
}