use std::usize;
mod minet_display;
pub use minet_display::*;
mod minet_encoding;
pub use minet_encoding::*;
mod minet_activation;
use minet_activation::*;
use rand::{seq::SliceRandom, thread_rng, Rng};
use rand_distr::{Distribution, Normal};
#[derive(Clone)]
pub struct Minet {
pub genes: Vec<(f32, Vec<(usize, f32)>)>,
pub input: usize,
pub hidden: usize,
pub output: usize,
pub fitness: f32,
}
const INITIAL_WEIGHT_STD_DEVIATION: f32 = 0.5;
const WEIGHT_STD_DEVIATION: f32 = 0.1;
const BIAS_STD_DEVIATION: f32 = 0.016;
const SYNAPSE_PROBABILITY: f64 = 0.4;
impl Minet {
pub fn new(input: usize, hidden: usize, output: usize) -> Self {
let total_neurons = input + hidden + output;
let genes = vec![(0.0, Vec::new()); total_neurons];
let mut minet = Minet {
genes,
input,
hidden,
output,
fitness: 0.0,
};
for i in input..total_neurons {
minet.connect_random_from(i);
minet.connect_random_from(i);
minet.connect_random_from(i);
}
minet
}
pub fn mutate(&mut self) {
self.mutate_weights();
self.mutate_bias();
let mut rng = rand::thread_rng();
if rng.gen_bool(SYNAPSE_PROBABILITY) {
self.synapse_swap();
}
}
pub fn synapse_swap(&mut self) {
self.synapse_remove_random();
self.synapse_connect_random();
}
pub fn crossbreed(&self, other: &Self) -> Self {
let mut new_genes: Vec<(f32, Vec<(usize, f32)>)> = Vec::new();
let parent1 = self.genes.clone();
let parent2 = other.genes.clone();
let mut rng = rand::thread_rng();
for i in 0..parent1.len() {
let gene1 = parent1[i].clone();
let gene2 = parent2[i].clone();
let new_gene = if rng.gen_bool(0.5) { gene1 } else { gene2 };
new_genes.push(new_gene);
}
let child = Minet {
genes: new_genes,
input: self.input,
hidden: self.hidden,
output: self.output,
fitness: 0.0,
};
child
}
pub fn mutate_weights(&mut self) {
for gene in self.genes.iter_mut() {
for synapse in gene.1.iter_mut() {
synapse.1 += sample_normal(WEIGHT_STD_DEVIATION);
}
}
}
pub fn mutate_bias(&mut self) {
for gene in self.genes.iter_mut() {
gene.0 += sample_normal(BIAS_STD_DEVIATION);
}
}
pub fn forward(&self, inputs: Vec<f32>) -> Vec<f32> {
let input_neurons = self.input;
let hidden_neurons = self.hidden;
let output_neurons = self.output;
let length = self.genes.len();
let mut activation_map = vec![0.0; length];
for i in 0..input_neurons {
activation_map[i] = inputs[i];
for &(target_idx, weight) in &self.genes[i].1 {
activation_map[target_idx as usize] += inputs[i] * weight;
}
}
for i in input_neurons..(input_neurons + hidden_neurons) {
activation_map[i] += self.genes[i].0;
activation_map[i] = tanh(activation_map[i]);
for &(target_idx, weight) in &self.genes[i].1 {
activation_map[target_idx as usize] += activation_map[i] * weight;
}
}
let mut outputs = Vec::with_capacity(output_neurons);
for i in (input_neurons + hidden_neurons)..(input_neurons + hidden_neurons + output_neurons)
{
activation_map[i] += self.genes[i].0;
activation_map[i] = sigmoid(activation_map[i]);
outputs.push(activation_map[i]);
}
outputs
}
pub fn synapse_remove_random(&mut self) {
let mut rng = rand::thread_rng();
let connected_neurons: Vec<(usize, usize)> = self
.genes
.iter()
.enumerate()
.flat_map(|(i, gene)| gene.1.iter().enumerate().map(move |(j, _)| (i, j)))
.collect();
if let Some(&(neuron, synapse)) = connected_neurons.choose(&mut rng) {
self.genes[neuron].1.remove(synapse);
} else {
println!("No synapses to remove.");
}
}
pub fn synapse_connect_random(&mut self) {
let mut rng = rand::thread_rng();
let non_output = self.input + self.hidden;
loop {
let source = rng.gen_range(0..non_output);
let target_candidates = self.synapse_candidates(source);
if let Some(&target) = target_candidates.choose(&mut rng) {
let weight = sample_normal(INITIAL_WEIGHT_STD_DEVIATION);
self.genes[source].1.push((target, weight));
break;
}
}
}
pub fn connect_random_from(&mut self, to_index: usize) -> usize {
let mut rng = rand::thread_rng();
let non_output = self.input + self.hidden;
let from_index = rng.gen_range(0..(to_index.clamp(0, non_output)));
let synapse_candidates = self.synapse_candidates(from_index);
for i in 0..synapse_candidates.len() {
let target = synapse_candidates[i];
if target == to_index {
let weight = sample_normal(INITIAL_WEIGHT_STD_DEVIATION);
self.genes[from_index].1.push((to_index, weight));
}
}
from_index
}
fn synapse_candidates(&self, source: usize) -> Vec<usize> {
if source >= self.genes.len() - self.output {
return Vec::new();
}
self.genes
.iter()
.enumerate()
.skip(source + 1)
.filter(|&(i, _)| i >= self.input && !self.synapse_is_connected(source, i))
.map(|(i, _)| i)
.collect()
}
fn synapse_is_connected(&self, source: usize, target: usize) -> bool {
self.genes[source].1.iter().any(|&(t, _)| t == target)
}
pub fn synapse_count(&self) -> usize {
self.genes.iter().map(|gene| gene.1.len()).sum()
}
}
fn sample_normal(std_dev: f32) -> f32 {
let normal = Normal::new(0.0, std_dev).expect("Invalid parameters for Normal distribution");
let mut rng = thread_rng();
normal.sample(&mut rng)
}
pub fn crossbreed_population(
mut population: Vec<Minet>,
survival_rate: f32,
target_population: usize,
) -> Vec<Minet> {
let population_size = population.len();
let surviving_count = (population_size as f32 * survival_rate).round() as usize;
let new_target = target_population - surviving_count;
population.sort_by(|a, b| b.fitness.partial_cmp(&a.fitness).unwrap());
population.truncate(surviving_count);
for i in 0..population.len() {
population[i].fitness = 0.0;
}
for _ in 0..new_target{
let parent1 = population.choose(&mut thread_rng()).unwrap();
let parent2 = population.choose(&mut thread_rng()).unwrap();
let mut child = parent1.crossbreed(parent2);
child.mutate();
population.push(child);
}
population
}