simple_neat 0.1.2

A simple NEAT like create for rust
Documentation
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()
            {
                // Disable the connection
                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, // Initially 1
                    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, // Copy the original 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; //Correct from range input..(tot-outputs) -> inputs + hiddens
            }

            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();
    }
}