From 9ed73ce58772eb7379dc7580a0afa1734f8f7cb0 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:33:16 +0000 Subject: [PATCH 01/23] fix mutation to include negative numbers --- src/topology.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topology.rs b/src/topology.rs index 1b9fd65..6b067f9 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -197,7 +197,7 @@ impl RandomlyMutable for NeuralNetworkTopology() * rate; + *w += rng.gen_range(-1.0..1.0) * rate; } } } From 75eb9ef474848c431a783efa2947a77182b007f2 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:34:56 +0000 Subject: [PATCH 02/23] save on gh actions calls --- .github/workflows/ci-cd.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 5404413..fef7ca6 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,6 +1,9 @@ name: CI-CD -on: [push, pull_request] +on: + push: + branches: [main] + pull_request: jobs: test: From 479a36164fede36bc96f7d157bd4f8b8d5c9114f Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:42:28 +0000 Subject: [PATCH 03/23] update dep in Cargo.toml --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 656ece5..591db00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "genetic-rs" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec3a578daff9a0d1302fd5ffb32d0f5e2c11c1f62ce921f519709d1e5cc8280" +checksum = "694916cca5538e02a6f04a0fcb5deef9297b460c697520b9f88ea4b9c0a1a09c" dependencies = [ "rand", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 67f215f..590afdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,6 @@ max-index = [] [dependencies] -genetic-rs = "0.2.1" +genetic-rs = "0.3" rand = "0.8.5" rayon = { version = "1.8.1", optional = true } \ No newline at end of file From f314ace6688b300e067a6314f0625c6ecccf2876 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:37:17 +0000 Subject: [PATCH 04/23] change function names --- examples/basic.rs | 2 +- src/topology.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 57004ed..256d4cc 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -15,7 +15,7 @@ impl RandomlyMutable for AgentDNA { impl Prunable for AgentDNA {} impl DivisionReproduction for AgentDNA { - fn spawn_child(&self, rng: &mut impl Rng) -> Self { + fn divide(&self, rng: &mut impl Rng) -> Self { let mut child = self.clone(); child.mutate(self.network.mutation_rate, rng); child diff --git a/src/topology.rs b/src/topology.rs index 6b067f9..2c9f09f 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -204,7 +204,7 @@ impl RandomlyMutable for NeuralNetworkTopology DivisionReproduction for NeuralNetworkTopology { - fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self { + fn divide(&self, rng: &mut impl rand::Rng) -> Self { let mut child = self.clone(); child.mutate(self.mutation_rate, rng); child @@ -213,7 +213,7 @@ impl DivisionReproduction for NeuralNetworkTopol #[cfg(feature = "crossover")] impl CrossoverReproduction for NeuralNetworkTopology { - fn spawn_child(&self, other: &Self, rng: &mut impl Rng) -> Self { + fn crossover(&self, other: &Self, rng: &mut impl Rng) -> Self { todo!(); } } From 0325fb80697e440d10209f124998a9c85d8e2ad4 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:38:56 +0000 Subject: [PATCH 05/23] fix error with example --- examples/basic.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 256d4cc..aa67020 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -112,7 +112,7 @@ fn main() { sim.next_generation(); } - let fits: Vec<_> = sim.entities.iter().map(fitness).collect(); + let fits: Vec<_> = sim.genomes.iter().map(fitness).collect(); let maxfit = fits .iter() @@ -130,7 +130,7 @@ fn main() { sim.next_generation(); } - let fits: Vec<_> = sim.entities.iter().map(fitness).collect(); + let fits: Vec<_> = sim.genomes.iter().map(fitness).collect(); let maxfit = fits .iter() From 4f74e87189ddbffb4e441165b7f048ec92822d5c Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:44:37 +0000 Subject: [PATCH 06/23] fix addneuron and add removeneuron --- src/topology.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/topology.rs b/src/topology.rs index 2c9f09f..a296c86 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -163,8 +163,12 @@ impl RandomlyMutable for NeuralNetworkTopology RandomlyMutable for NeuralNetworkTopology() <= rate && !self.hidden_layers.is_empty() { + // remove a neuron + let (_, mut loc) = self.rand_neuron(rng); + + while !loc.is_hidden() { + (_, loc) = self.rand_neuron(rng); + } + + self.hidden_layers.remove(loc.unwrap()); + + let mut done = false; + 'outer: for n in &self.hidden_layers { + let n2 = n.write().unwrap(); + + for (i, (loc2, _)) in n2.inputs.iter().enumerate() { + if i == loc { + n2.inputs.remove(i); + done = true; + break 'outer; + } + } + } + + if !done { + 'outer: for n in &self.output_layer { + let n2 = n.write().unwrap(); + + for (i, (loc2, _)) in n2.inputs.iter().enumerate() { + if i == loc { + n2.inputs.remove(i); + done = true; + break 'outer; + } + } + } + } + } + if rng.gen::() <= rate { // mutate a connection let (mut n, _) = self.rand_neuron(rng); @@ -211,12 +253,14 @@ impl DivisionReproduction for NeuralNetworkTopol } } +/* #[cfg(feature = "crossover")] impl CrossoverReproduction for NeuralNetworkTopology { fn crossover(&self, other: &Self, rng: &mut impl Rng) -> Self { todo!(); } } +*/ /// A stateless version of [`Neuron`][crate::Neuron]. #[derive(Debug, Clone)] From e255eaf4f472206b9e9d1a1f06185433a7bbfc52 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:17:20 +0000 Subject: [PATCH 07/23] add bias mutation --- src/topology.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/topology.rs b/src/topology.rs index a296c86..e8111fd 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -241,6 +241,14 @@ impl RandomlyMutable for NeuralNetworkTopology() <= rate { + // mutate bias + let (n, _) = self.rand_neuron(rng); + let mut n = n.write().unwrap(); + + n.bias += rng.gen_range(-1.0..1.0) * rate; + } } } } From c9efe0e31ba66a6ac9df28a079b0a0fc05b6f52d Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:46:14 +0000 Subject: [PATCH 08/23] implement activation function mutation --- Cargo.toml | 2 +- src/runnable.rs | 12 ++++-- src/topology.rs | 101 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 590afdc..70b272b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ license = "MIT" [features] default = ["max-index"] -#crossover = ["genetic-rs/crossover"] +crossover = ["genetic-rs/crossover"] rayon = ["genetic-rs/rayon", "dep:rayon"] max-index = [] diff --git a/src/runnable.rs b/src/runnable.rs index efcd3b2..360b74a 100644 --- a/src/runnable.rs +++ b/src/runnable.rs @@ -82,7 +82,7 @@ impl NeuralNetwork { n.state.value += self.process_neuron(l) * w; } - n.sigmoid(); + n.activate(); n.state.value } @@ -240,6 +240,9 @@ pub struct Neuron { /// The current state of the neuron. pub state: NeuronState, + + /// The neuron's activation function + pub activation: ActivationFn, } impl Neuron { @@ -248,9 +251,9 @@ impl Neuron { self.state.value = self.bias; } - /// Applies the sigoid activation function to the state's current value. - pub fn sigmoid(&mut self) { - self.state.value = 1. / (1. + std::f32::consts::E.powf(-self.state.value)) + /// Applies the activation function to the neuron + pub fn activate(&mut self) { + self.state.value = (self.activation.func)(self.state.value); } } @@ -263,6 +266,7 @@ impl From<&NeuronTopology> for Neuron { value: value.bias, ..Default::default() }, + activation: value.activation.clone(), } } } diff --git a/src/topology.rs b/src/topology.rs index e8111fd..b264613 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,8 +1,23 @@ -use std::sync::{Arc, RwLock}; +use std::{fmt, sync::{Arc, RwLock}}; use genetic_rs::prelude::*; use rand::prelude::*; +/// Creates an [`ActivationFn`] object from a function +#[macro_export] +macro_rules! activation_fn { + ($F: path) => { + ActivationFn { + func: Arc::new($F), + name: String::from(stringify!($F)), + } + }; + + {$($F: path),*} => { + [$(activation_fn!($F)),*] + }; +} + /// A stateless neural network topology. /// This is the struct you want to use in your agent's inheritance. /// See [`NeuralNetwork::from`][crate::NeuralNetwork::from] for how to convert this to a runnable neural network. @@ -28,7 +43,7 @@ impl NeuralNetworkTopology { /// Creates a new [`NeuralNetworkTopology`]. pub fn new(mutation_rate: f32, mutation_passes: usize, rng: &mut impl Rng) -> Self { let input_layer: [Arc>; I] = (0..I) - .map(|_| Arc::new(RwLock::new(NeuronTopology::new(vec![], rng)))) + .map(|_| Arc::new(RwLock::new(NeuronTopology::new_with_activation(vec![], activation_fn!(linear_activation), rng)))) .collect::>() .try_into() .unwrap(); @@ -51,7 +66,7 @@ impl NeuralNetworkTopology { }) .collect(); - output_layer.push(Arc::new(RwLock::new(NeuronTopology::new(input, rng)))); + output_layer.push(Arc::new(RwLock::new(NeuronTopology::new_with_activation(input, activation_fn!(sigmoid), rng)))); } let output_layer = output_layer.try_into().unwrap(); @@ -202,10 +217,10 @@ impl RandomlyMutable for NeuralNetworkTopology RandomlyMutable for NeuralNetworkTopology RandomlyMutable for NeuralNetworkTopology() <= rate && !self.hidden_layers.is_empty() { + // mutate activation function + let activations = activation_fn! { + sigmoid, + relu, + f32::tanh + }; + + let (mut n, mut loc) = self.rand_neuron(rng); + + while !loc.is_hidden() { + (n, loc) = self.rand_neuron(rng); + } + + let mut nw = n.write().unwrap(); + + // should probably not clone, but its not a huge efficiency issue anyways + nw.activation = activations[rng.gen_range(0..activations.len())].clone(); + } } } } @@ -270,6 +304,33 @@ impl CrossoverReproduction for NeuralNetworkTopology { } */ +/// An activation function object that implements [`fmt::Debug`] and is [`Send`] +#[derive(Clone)] +pub struct ActivationFn { + /// The actual activation function. + pub func: Arc f32 + Send + 'static>, + name: String, +} + +impl fmt::Debug for ActivationFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{}", self.name) + } +} + +/// The sigmoid activation function. +pub fn sigmoid(n: f32) -> f32 { + 1. / (1. + std::f32::consts::E.powf(-n)) +} + +/// The ReLU activation function. +pub fn relu(n: f32) -> f32 { + n.max(0.) +} + +/// Activation function that does nothing. +pub fn linear_activation(n: f32) -> f32 {n} + /// A stateless version of [`Neuron`][crate::Neuron]. #[derive(Debug, Clone)] pub struct NeuronTopology { @@ -278,16 +339,38 @@ pub struct NeuronTopology { /// The neuron's bias. pub bias: f32, + + /// The neuron's activation function. + pub activation: ActivationFn, } impl NeuronTopology { /// Creates a new neuron with the given input locations. pub fn new(inputs: Vec, rng: &mut impl Rng) -> Self { - let inputs = inputs.into_iter().map(|i| (i, rng.gen::())).collect(); + let activations = activation_fn! { + sigmoid, + relu, + f32::tanh + }; + + Self::new_with_activations(inputs, activations, rng) + } + + /// Takes a collection of activation functions and chooses a random one to use. + pub fn new_with_activations(inputs: Vec, activations: impl IntoIterator, rng: &mut impl Rng) -> Self { + let mut activations: Vec<_> = activations.into_iter().collect(); + + Self::new_with_activation(inputs, activations.remove(rng.gen_range(0..activations.len())), rng) + } + + /// Creates a neuron with the activation. + pub fn new_with_activation(inputs: Vec, activation: ActivationFn, rng: &mut impl Rng) -> Self { + let inputs = inputs.into_iter().map(|i| (i, rng.gen_range(-1.0..1.0))).collect(); Self { inputs, bias: rng.gen(), + activation, } } } From 174313d84eb18188056a3e1ffa9e225cc2b6f3aa Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:55:18 +0000 Subject: [PATCH 09/23] fix making ActivationFn sync --- src/runnable.rs | 2 +- src/topology.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runnable.rs b/src/runnable.rs index 360b74a..5bdb82d 100644 --- a/src/runnable.rs +++ b/src/runnable.rs @@ -112,7 +112,7 @@ impl NeuralNetwork { let mut nw = n.write().unwrap(); nw.state.value += val; - nw.sigmoid(); + nw.activate(); nw.state.value } diff --git a/src/topology.rs b/src/topology.rs index b264613..f369ddf 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -308,7 +308,7 @@ impl CrossoverReproduction for NeuralNetworkTopology { #[derive(Clone)] pub struct ActivationFn { /// The actual activation function. - pub func: Arc f32 + Send + 'static>, + pub func: Arc f32 + Send + Sync + 'static>, name: String, } From 582edc0b9684c97394195c818d10c7cb564793b7 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:56:18 +0000 Subject: [PATCH 10/23] cargo fmt --- src/topology.rs | 51 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index f369ddf..b5ae19a 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,4 +1,7 @@ -use std::{fmt, sync::{Arc, RwLock}}; +use std::{ + fmt, + sync::{Arc, RwLock}, +}; use genetic_rs::prelude::*; use rand::prelude::*; @@ -43,7 +46,13 @@ impl NeuralNetworkTopology { /// Creates a new [`NeuralNetworkTopology`]. pub fn new(mutation_rate: f32, mutation_passes: usize, rng: &mut impl Rng) -> Self { let input_layer: [Arc>; I] = (0..I) - .map(|_| Arc::new(RwLock::new(NeuronTopology::new_with_activation(vec![], activation_fn!(linear_activation), rng)))) + .map(|_| { + Arc::new(RwLock::new(NeuronTopology::new_with_activation( + vec![], + activation_fn!(linear_activation), + rng, + ))) + }) .collect::>() .try_into() .unwrap(); @@ -66,7 +75,11 @@ impl NeuralNetworkTopology { }) .collect(); - output_layer.push(Arc::new(RwLock::new(NeuronTopology::new_with_activation(input, activation_fn!(sigmoid), rng)))); + output_layer.push(Arc::new(RwLock::new(NeuronTopology::new_with_activation( + input, + activation_fn!(sigmoid), + rng, + )))); } let output_layer = output_layer.try_into().unwrap(); @@ -182,8 +195,7 @@ impl RandomlyMutable for NeuralNetworkTopology RandomlyMutable for NeuralNetworkTopology f32 { } /// Activation function that does nothing. -pub fn linear_activation(n: f32) -> f32 {n} +pub fn linear_activation(n: f32) -> f32 { + n +} /// A stateless version of [`Neuron`][crate::Neuron]. #[derive(Debug, Clone)] @@ -357,15 +371,30 @@ impl NeuronTopology { } /// Takes a collection of activation functions and chooses a random one to use. - pub fn new_with_activations(inputs: Vec, activations: impl IntoIterator, rng: &mut impl Rng) -> Self { + pub fn new_with_activations( + inputs: Vec, + activations: impl IntoIterator, + rng: &mut impl Rng, + ) -> Self { let mut activations: Vec<_> = activations.into_iter().collect(); - Self::new_with_activation(inputs, activations.remove(rng.gen_range(0..activations.len())), rng) + Self::new_with_activation( + inputs, + activations.remove(rng.gen_range(0..activations.len())), + rng, + ) } /// Creates a neuron with the activation. - pub fn new_with_activation(inputs: Vec, activation: ActivationFn, rng: &mut impl Rng) -> Self { - let inputs = inputs.into_iter().map(|i| (i, rng.gen_range(-1.0..1.0))).collect(); + pub fn new_with_activation( + inputs: Vec, + activation: ActivationFn, + rng: &mut impl Rng, + ) -> Self { + let inputs = inputs + .into_iter() + .map(|i| (i, rng.gen_range(-1.0..1.0))) + .collect(); Self { inputs, From 1f0bf3bea145271bbc3bf970eee3254e43a8eeed Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:54:58 +0000 Subject: [PATCH 11/23] fix topology index error (runnable still an issue) --- src/topology.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/topology.rs b/src/topology.rs index b5ae19a..4f4e7f7 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -138,6 +138,30 @@ impl NeuralNetworkTopology { } } } + + fn deletion_shift(&self, deleted: NeuronLocation) { + if !deleted.is_hidden() { + panic!("Invalid neuron deletion"); + } + + for n in &self.hidden_layers { + let mut nw = n.write().unwrap(); + for (loc, _w) in &mut nw.inputs { + if loc.is_hidden() && loc.unwrap() > deleted.unwrap() { + *loc = NeuronLocation::Hidden(loc.unwrap() - 1); + } + } + } + + for n in &self.output_layer { + let mut nw = n.write().unwrap(); + for (loc, _w) in &mut nw.inputs { + if loc.is_hidden() && loc.unwrap() > deleted.unwrap() { + *loc = NeuronLocation::Hidden(loc.unwrap() - 1); + } + } + } + } } // need to do all this manually because Arcs are cringe @@ -252,6 +276,8 @@ impl RandomlyMutable for NeuralNetworkTopology() <= rate { From 5fcfffbf920f4c94a7b82b38563eb6fc726b938b Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:07:40 +0000 Subject: [PATCH 12/23] restructure neuron deletion (and potentially fix) --- src/topology.rs | 61 +++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 4f4e7f7..306d751 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -139,28 +139,36 @@ impl NeuralNetworkTopology { } } - fn deletion_shift(&self, deleted: NeuronLocation) { - if !deleted.is_hidden() { + fn delete_neuron(&mut self, loc: NeuronLocation) -> NeuronTopology { + if !loc.is_hidden() { panic!("Invalid neuron deletion"); } - + + let index = loc.unwrap(); + let n = Arc::into_inner(self.hidden_layers.remove(index)) + .unwrap() + .into_inner() + .unwrap(); + for n in &self.hidden_layers { let mut nw = n.write().unwrap(); for (loc, _w) in &mut nw.inputs { - if loc.is_hidden() && loc.unwrap() > deleted.unwrap() { + if loc.is_hidden() && loc.unwrap() > index { *loc = NeuronLocation::Hidden(loc.unwrap() - 1); } } } - - for n in &self.output_layer { - let mut nw = n.write().unwrap(); - for (loc, _w) in &mut nw.inputs { - if loc.is_hidden() && loc.unwrap() > deleted.unwrap() { - *loc = NeuronLocation::Hidden(loc.unwrap() - 1); + + for n2 in &self.output_layer { + let mut nw = n2.write().unwrap(); + for (iloc, _w) in &mut nw.inputs { + if iloc.is_hidden() && iloc.unwrap() > index { + *iloc = NeuronLocation::Hidden(loc.unwrap() - 1); } } } + + n } } @@ -248,36 +256,9 @@ impl RandomlyMutable for NeuralNetworkTopology() <= rate { From 8ea5fd7f1171664eb2bfd332b87482ac1c244666 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:14:12 +0000 Subject: [PATCH 13/23] fix index error --- src/topology.rs | 50 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 306d751..8e291ff 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -145,30 +145,46 @@ impl NeuralNetworkTopology { } let index = loc.unwrap(); - let n = Arc::into_inner(self.hidden_layers.remove(index)) - .unwrap() - .into_inner() - .unwrap(); - + let neuron = Arc::into_inner(self.hidden_layers.remove(index)).unwrap(); + for n in &self.hidden_layers { let mut nw = n.write().unwrap(); - for (loc, _w) in &mut nw.inputs { - if loc.is_hidden() && loc.unwrap() > index { - *loc = NeuronLocation::Hidden(loc.unwrap() - 1); - } - } + + nw.inputs = nw.inputs + .iter() + .filter_map(|&(input_loc, w)| { + if !input_loc.is_hidden() { + return Some((input_loc, w)); + } + + if input_loc.unwrap() == index { + return None; + } + + Some((NeuronLocation::Hidden(input_loc.unwrap() - 1), w)) + }) + .collect(); } for n2 in &self.output_layer { let mut nw = n2.write().unwrap(); - for (iloc, _w) in &mut nw.inputs { - if iloc.is_hidden() && iloc.unwrap() > index { - *iloc = NeuronLocation::Hidden(loc.unwrap() - 1); - } - } + nw.inputs = nw.inputs + .iter() + .filter_map(|&(input_loc, w)| { + if !input_loc.is_hidden() { + return Some((input_loc, w)); + } + + if input_loc.unwrap() == index { + return None; + } + + Some((NeuronLocation::Hidden(input_loc.unwrap() - 1), w)) // TODO fix attempt to subtract with overflow (no idea) + }) + .collect(); } - n + neuron.into_inner().unwrap() } } @@ -225,7 +241,7 @@ impl RandomlyMutable for NeuralNetworkTopology Date: Fri, 16 Feb 2024 17:00:36 +0000 Subject: [PATCH 14/23] fix remaining subtraction/index errors --- src/topology.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 8e291ff..a639cff 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -124,11 +124,7 @@ impl NeuralNetworkTopology { let i = rng.gen_range(0..self.input_layer.len()); (self.input_layer[i].clone(), NeuronLocation::Input(i)) } - 1 => { - if self.hidden_layers.is_empty() { - return self.rand_neuron(rng); - } - + 1 if !self.hidden_layers.is_empty() => { let i = rng.gen_range(0..self.hidden_layers.len()); (self.hidden_layers[i].clone(), NeuronLocation::Hidden(i)) } @@ -161,7 +157,11 @@ impl NeuralNetworkTopology { return None; } - Some((NeuronLocation::Hidden(input_loc.unwrap() - 1), w)) + if input_loc.unwrap() > index { + return Some((NeuronLocation::Hidden(input_loc.unwrap() - 1), w)); + } + + Some((input_loc, w)) }) .collect(); } @@ -179,7 +179,11 @@ impl NeuralNetworkTopology { return None; } - Some((NeuronLocation::Hidden(input_loc.unwrap() - 1), w)) // TODO fix attempt to subtract with overflow (no idea) + if input_loc.unwrap() > index { + return Some((NeuronLocation::Hidden(input_loc.unwrap() - 1), w)); + } + + Some((input_loc, w)) }) .collect(); } From 8dd5b685d1d197bd018939f02f4d8fdc365dc9ce Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:49:36 +0000 Subject: [PATCH 15/23] fix is_connection_cyclic --- src/topology.rs | 64 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index a639cff..53f25dc 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,6 +1,5 @@ use std::{ - fmt, - sync::{Arc, RwLock}, + collections::HashSet, fmt, sync::{Arc, RwLock} }; use genetic_rs::prelude::*; @@ -93,17 +92,47 @@ impl NeuralNetworkTopology { } } - fn is_connection_cyclic(&self, loc1: NeuronLocation, loc2: NeuronLocation) -> bool { - if loc1 == loc2 { + /// Creates a new connection between the neurons. + /// If the connection is cyclic, it does not add a connection and returns false. + /// Otherwise, it returns true. + pub fn add_connection(&mut self, from: NeuronLocation, to: NeuronLocation, weight: f32) -> bool { + if self.is_connection_cyclic(from, to) { + return false; + } + + // Add the connection since it is not cyclic + self.get_neuron(to).write().unwrap().inputs.push((from, weight)); + + true + } + + fn is_connection_cyclic(&self, from: NeuronLocation, to: NeuronLocation) -> bool { + if to.is_input() || from.is_output() { return true; } - for &(n, _w) in &self.get_neuron(loc1).read().unwrap().inputs { - if self.is_connection_cyclic(n, loc2) { + let mut visited = HashSet::new(); + self.dfs(from, to, &mut visited) + } + + // TODO rayon implementation + fn dfs(&self, current: NeuronLocation, target: NeuronLocation, visited: &mut HashSet) -> bool { + if current == target { + return true; + } + + visited.insert(current); + + let n = self.get_neuron(current); + let nr = n.read().unwrap(); + + for &(input, _) in &nr.inputs { + if !visited.contains(&input) && self.dfs(input, target, visited) { return true; } } - + + visited.remove(¤t); false } @@ -244,8 +273,7 @@ impl RandomlyMutable for NeuralNetworkTopology RandomlyMutable for NeuralNetworkTopology() <= rate { // add a connection - let (mut n1, mut loc1) = self.rand_neuron(rng); + let (_, mut loc1) = self.rand_neuron(rng); + let (_, mut loc2) = self.rand_neuron(rng); - while n1.read().unwrap().inputs.is_empty() { - (n1, loc1) = self.rand_neuron(rng); + while loc1.is_output() || !self.add_connection(loc1, loc2, rng.gen::()) { + (_, loc1) = self.rand_neuron(rng); + (_, loc2) = self.rand_neuron(rng); } - - let (mut n2, mut loc2) = self.rand_neuron(rng); - - while self.is_connection_cyclic(loc1, loc2) { - (n2, loc2) = self.rand_neuron(rng); - } - - n2.write().unwrap().inputs.push((loc1, rng.gen())); } if rng.gen::() <= rate && !self.hidden_layers.is_empty() { @@ -432,7 +454,7 @@ impl NeuronTopology { } /// A pseudo-pointer of sorts used to make structural conversions very fast and easy to write. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq)] pub enum NeuronLocation { /// Points to a neuron in the input layer at contained index. Input(usize), From 7a8396a89063a158794f793a11d291521b78f2e8 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:50:54 +0000 Subject: [PATCH 16/23] cargo fmt --- src/topology.rs | 54 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 53f25dc..384de65 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -1,5 +1,7 @@ use std::{ - collections::HashSet, fmt, sync::{Arc, RwLock} + collections::HashSet, + fmt, + sync::{Arc, RwLock}, }; use genetic_rs::prelude::*; @@ -95,17 +97,26 @@ impl NeuralNetworkTopology { /// Creates a new connection between the neurons. /// If the connection is cyclic, it does not add a connection and returns false. /// Otherwise, it returns true. - pub fn add_connection(&mut self, from: NeuronLocation, to: NeuronLocation, weight: f32) -> bool { + pub fn add_connection( + &mut self, + from: NeuronLocation, + to: NeuronLocation, + weight: f32, + ) -> bool { if self.is_connection_cyclic(from, to) { return false; } - + // Add the connection since it is not cyclic - self.get_neuron(to).write().unwrap().inputs.push((from, weight)); - + self.get_neuron(to) + .write() + .unwrap() + .inputs + .push((from, weight)); + true } - + fn is_connection_cyclic(&self, from: NeuronLocation, to: NeuronLocation) -> bool { if to.is_input() || from.is_output() { return true; @@ -114,24 +125,29 @@ impl NeuralNetworkTopology { let mut visited = HashSet::new(); self.dfs(from, to, &mut visited) } - + // TODO rayon implementation - fn dfs(&self, current: NeuronLocation, target: NeuronLocation, visited: &mut HashSet) -> bool { + fn dfs( + &self, + current: NeuronLocation, + target: NeuronLocation, + visited: &mut HashSet, + ) -> bool { if current == target { return true; } - + visited.insert(current); - + let n = self.get_neuron(current); let nr = n.read().unwrap(); - + for &(input, _) in &nr.inputs { if !visited.contains(&input) && self.dfs(input, target, visited) { return true; } } - + visited.remove(¤t); false } @@ -168,14 +184,15 @@ impl NeuralNetworkTopology { if !loc.is_hidden() { panic!("Invalid neuron deletion"); } - + let index = loc.unwrap(); let neuron = Arc::into_inner(self.hidden_layers.remove(index)).unwrap(); for n in &self.hidden_layers { let mut nw = n.write().unwrap(); - nw.inputs = nw.inputs + nw.inputs = nw + .inputs .iter() .filter_map(|&(input_loc, w)| { if !input_loc.is_hidden() { @@ -194,10 +211,11 @@ impl NeuralNetworkTopology { }) .collect(); } - + for n2 in &self.output_layer { let mut nw = n2.write().unwrap(); - nw.inputs = nw.inputs + nw.inputs = nw + .inputs .iter() .filter_map(|&(input_loc, w)| { if !input_loc.is_hidden() { @@ -216,7 +234,7 @@ impl NeuralNetworkTopology { }) .collect(); } - + neuron.into_inner().unwrap() } } @@ -298,7 +316,7 @@ impl RandomlyMutable for NeuralNetworkTopology Date: Thu, 22 Feb 2024 16:47:48 +0000 Subject: [PATCH 17/23] implement serde (untested) --- Cargo.lock | 76 ++++++++++++++++++++++++++ Cargo.toml | 8 ++- src/lib.rs | 3 ++ src/topology.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 591db00..dc3f519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -71,9 +80,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" name = "neat" version = "0.1.0" dependencies = [ + "bincode", "genetic-rs", "rand", "rayon", + "serde", + "serde-big-array", ] [[package]] @@ -82,6 +94,24 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.5" @@ -138,6 +168,52 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 70b272b..8cfdfc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,15 @@ default = ["max-index"] crossover = ["genetic-rs/crossover"] rayon = ["genetic-rs/rayon", "dep:rayon"] max-index = [] +serde = ["dep:serde", "dep:serde-big-array"] [dependencies] genetic-rs = "0.3" rand = "0.8.5" -rayon = { version = "1.8.1", optional = true } \ No newline at end of file +rayon = { version = "1.8.1", optional = true } +serde = { version = "1.0.197", features = ["derive"], optional = true } +serde-big-array = { version = "0.5.1", optional = true } + +[dev-dependencies] +bincode = "1.3.3" diff --git a/src/lib.rs b/src/lib.rs index 72bdab4..5b96fc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,6 @@ pub mod runnable; pub use genetic_rs::prelude::*; pub use runnable::*; pub use topology::*; + +#[cfg(feature = "serde")] +pub use nnt_serde::*; diff --git a/src/topology.rs b/src/topology.rs index 384de65..dd3f0b6 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -7,6 +7,111 @@ use std::{ use genetic_rs::prelude::*; use rand::prelude::*; +#[cfg(feature = "serde")] +use serde::{Serialize, Serializer, Deserialize, Deserializer}; + +#[cfg(feature = "serde")] +pub mod nnt_serde { + use super::*; + use serde::{Serialize, Deserialize}; + use serde_big_array::BigArray; + + #[derive(Serialize, Deserialize)] + pub struct NNTSerde { + #[serde(with = "BigArray")] + input_layer: [NeuronTopology; I], + + hidden_layers: Vec, + + #[serde(with = "BigArray")] + output_layer: [NeuronTopology; O], + + mutation_rate: f32, + mutation_passes: usize, + } + + impl From<&NeuralNetworkTopology> for NNTSerde { + fn from(value: &NeuralNetworkTopology) -> Self { + let input_layer = value.input_layer + .iter() + .map(|n| n.read().unwrap().clone()) + .collect::>() + .try_into() + .unwrap(); + + let hidden_layers = value.hidden_layers + .iter() + .map(|n| n.read().unwrap().clone()) + .collect(); + + let output_layer = value.output_layer + .iter() + .map(|n| n.read().unwrap().clone()) + .collect::>() + .try_into() + .unwrap(); + + Self { + input_layer, + hidden_layers, + output_layer, + mutation_rate: value.mutation_rate, + mutation_passes: value.mutation_passes, + } + } + } + + impl Into> for NNTSerde { + fn into(self) -> NeuralNetworkTopology { + let input_layer = self.input_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + let hidden_layers = self.hidden_layers + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect(); + + let output_layer = self.output_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + NeuralNetworkTopology { + input_layer, + hidden_layers, + output_layer, + mutation_rate: self.mutation_rate, + mutation_passes: self.mutation_passes, + } + } + } + + #[cfg(test)] + #[test] + fn serde() { + let mut rng = rand::thread_rng(); + let nnt = NeuralNetworkTopology::<10, 10>::new(0.1, 3, &mut rng); + let nnts = NNTSerde::from(&nnt); + + let encoded = bincode::serialize(&nnts).unwrap(); + + if let Some(_) = option_env!("TEST_CREATEFILE") { + std::fs::write("serde-test.nn", encoded).unwrap(); + } + + let decoded: NNTSerde<10, 10> = bincode::deserialize(&encoded).unwrap(); + let nnt2: NeuralNetworkTopology<10, 10> = decoded.into(); + + dbg!(nnt, nnt2); + } +} + /// Creates an [`ActivationFn`] object from a function #[macro_export] macro_rules! activation_fn { @@ -397,6 +502,37 @@ impl fmt::Debug for ActivationFn { } } +#[cfg(feature = "serde")] +impl Serialize for ActivationFn { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.name) + } +} + +#[cfg(feature = "serde")] +impl<'a> Deserialize<'a> for ActivationFn { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a> + { + let name = String::deserialize(deserializer)?; + let activations = activation_fn! { + sigmoid, + relu, + f32::tanh + }; + + for a in activations { + if a.name == name { + return Ok(a); + } + } + + // eventually will make an activation fn registry of sorts. + panic!("Custom activation functions currently not supported.") // TODO return error instead of raw panic + } +} + /// The sigmoid activation function. pub fn sigmoid(n: f32) -> f32 { 1. / (1. + std::f32::consts::E.powf(-n)) @@ -414,6 +550,7 @@ pub fn linear_activation(n: f32) -> f32 { /// A stateless version of [`Neuron`][crate::Neuron]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NeuronTopology { /// The input locations and weights. pub inputs: Vec<(NeuronLocation, f32)>, @@ -473,6 +610,7 @@ impl NeuronTopology { /// A pseudo-pointer of sorts used to make structural conversions very fast and easy to write. #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NeuronLocation { /// Points to a neuron in the input layer at contained index. Input(usize), From bb4fe373be78eb42b02cc6e084bbd4be9c1fb7ad Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:58:02 +0000 Subject: [PATCH 18/23] add docstrings --- src/topology.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/topology.rs b/src/topology.rs index dd3f0b6..7e26e34 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -10,12 +10,14 @@ use rand::prelude::*; #[cfg(feature = "serde")] use serde::{Serialize, Serializer, Deserialize, Deserializer}; +/// Contains useful structs for serializing/deserializing a [`NeuronTopology`] #[cfg(feature = "serde")] pub mod nnt_serde { use super::*; use serde::{Serialize, Deserialize}; use serde_big_array::BigArray; + /// A serializable wrapper for [`NeuronToplogy`]. See [`NNTSerde::from`] for conversion. #[derive(Serialize, Deserialize)] pub struct NNTSerde { #[serde(with = "BigArray")] From a4eba64dffbbbab354666718c2dffe930a67c638 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:07:01 +0000 Subject: [PATCH 19/23] fix deser --- src/topology.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 7e26e34..a0104bb 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -104,7 +104,7 @@ pub mod nnt_serde { let encoded = bincode::serialize(&nnts).unwrap(); if let Some(_) = option_env!("TEST_CREATEFILE") { - std::fs::write("serde-test.nn", encoded).unwrap(); + std::fs::write("serde-test.nn", &encoded).unwrap(); } let decoded: NNTSerde<10, 10> = bincode::deserialize(&encoded).unwrap(); @@ -521,7 +521,8 @@ impl<'a> Deserialize<'a> for ActivationFn { let activations = activation_fn! { sigmoid, relu, - f32::tanh + f32::tanh, + linear_activation }; for a in activations { From 34bf32dbd6547da4f06abb7e07ebdeb69c1a0cee Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:02:37 +0000 Subject: [PATCH 20/23] fix clippy warnings --- src/topology.rs | 77 ++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index a0104bb..58a2cc9 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -21,15 +21,15 @@ pub mod nnt_serde { #[derive(Serialize, Deserialize)] pub struct NNTSerde { #[serde(with = "BigArray")] - input_layer: [NeuronTopology; I], + pub(crate) input_layer: [NeuronTopology; I], - hidden_layers: Vec, + pub(crate) hidden_layers: Vec, #[serde(with = "BigArray")] - output_layer: [NeuronTopology; O], + pub(crate) output_layer: [NeuronTopology; O], - mutation_rate: f32, - mutation_passes: usize, + pub(crate) mutation_rate: f32, + pub(crate) mutation_passes: usize, } impl From<&NeuralNetworkTopology> for NNTSerde { @@ -63,37 +63,6 @@ pub mod nnt_serde { } } - impl Into> for NNTSerde { - fn into(self) -> NeuralNetworkTopology { - let input_layer = self.input_layer - .into_iter() - .map(|n| Arc::new(RwLock::new(n))) - .collect::>() - .try_into() - .unwrap(); - - let hidden_layers = self.hidden_layers - .into_iter() - .map(|n| Arc::new(RwLock::new(n))) - .collect(); - - let output_layer = self.output_layer - .into_iter() - .map(|n| Arc::new(RwLock::new(n))) - .collect::>() - .try_into() - .unwrap(); - - NeuralNetworkTopology { - input_layer, - hidden_layers, - output_layer, - mutation_rate: self.mutation_rate, - mutation_passes: self.mutation_passes, - } - } - } - #[cfg(test)] #[test] fn serde() { @@ -481,14 +450,38 @@ impl DivisionReproduction for NeuralNetworkTopol } } -/* -#[cfg(feature = "crossover")] -impl CrossoverReproduction for NeuralNetworkTopology { - fn crossover(&self, other: &Self, rng: &mut impl Rng) -> Self { - todo!(); +#[cfg(feature = "serde")] +impl From> for NeuralNetworkTopology { + fn from(value: nnt_serde::NNTSerde) -> Self { + let input_layer = value.input_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + let hidden_layers = value.hidden_layers + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect(); + + let output_layer = value.output_layer + .into_iter() + .map(|n| Arc::new(RwLock::new(n))) + .collect::>() + .try_into() + .unwrap(); + + NeuralNetworkTopology { + input_layer, + hidden_layers, + output_layer, + mutation_rate: value.mutation_rate, + mutation_passes: value.mutation_passes, + } } } -*/ + /// An activation function object that implements [`fmt::Debug`] and is [`Send`] #[derive(Clone)] From 674ad6e147142bb1eba19100906794a2b4b0a6b8 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:02:50 +0000 Subject: [PATCH 21/23] cargo fmt --- src/topology.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/topology.rs b/src/topology.rs index 58a2cc9..20181dc 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -8,13 +8,13 @@ use genetic_rs::prelude::*; use rand::prelude::*; #[cfg(feature = "serde")] -use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Contains useful structs for serializing/deserializing a [`NeuronTopology`] #[cfg(feature = "serde")] pub mod nnt_serde { use super::*; - use serde::{Serialize, Deserialize}; + use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; /// A serializable wrapper for [`NeuronToplogy`]. See [`NNTSerde::from`] for conversion. @@ -34,19 +34,22 @@ pub mod nnt_serde { impl From<&NeuralNetworkTopology> for NNTSerde { fn from(value: &NeuralNetworkTopology) -> Self { - let input_layer = value.input_layer + let input_layer = value + .input_layer .iter() .map(|n| n.read().unwrap().clone()) .collect::>() .try_into() .unwrap(); - let hidden_layers = value.hidden_layers + let hidden_layers = value + .hidden_layers .iter() .map(|n| n.read().unwrap().clone()) .collect(); - let output_layer = value.output_layer + let output_layer = value + .output_layer .iter() .map(|n| n.read().unwrap().clone()) .collect::>() @@ -451,21 +454,26 @@ impl DivisionReproduction for NeuralNetworkTopol } #[cfg(feature = "serde")] -impl From> for NeuralNetworkTopology { +impl From> + for NeuralNetworkTopology +{ fn from(value: nnt_serde::NNTSerde) -> Self { - let input_layer = value.input_layer + let input_layer = value + .input_layer .into_iter() .map(|n| Arc::new(RwLock::new(n))) .collect::>() .try_into() .unwrap(); - let hidden_layers = value.hidden_layers + let hidden_layers = value + .hidden_layers .into_iter() .map(|n| Arc::new(RwLock::new(n))) .collect(); - let output_layer = value.output_layer + let output_layer = value + .output_layer .into_iter() .map(|n| Arc::new(RwLock::new(n))) .collect::>() @@ -482,7 +490,6 @@ impl From> for NeuralN } } - /// An activation function object that implements [`fmt::Debug`] and is [`Send`] #[derive(Clone)] pub struct ActivationFn { @@ -508,7 +515,7 @@ impl Serialize for ActivationFn { impl<'a> Deserialize<'a> for ActivationFn { fn deserialize(deserializer: D) -> Result where - D: Deserializer<'a> + D: Deserializer<'a>, { let name = String::deserialize(deserializer)?; let activations = activation_fn! { From 62e8876cee5bf8bea04c7a2cd63dcb798d326b2b Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:44:41 +0000 Subject: [PATCH 22/23] add feature docs --- Cargo.toml | 6 +++++- src/lib.rs | 2 ++ src/runnable.rs | 2 +- src/topology.rs | 3 ++- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8cfdfc5..7d44305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,15 @@ keywords = ["genetic", "machine-learning", "ai", "algorithm", "evolution"] categories = ["algorithms", "science", "simulation"] license = "MIT" +[package.metadata.docs.rs] +features = ["serde"] +rustdoc-args = ["--cfg", "docsrs"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = ["max-index"] -crossover = ["genetic-rs/crossover"] +#crossover = ["genetic-rs/crossover"] rayon = ["genetic-rs/rayon", "dep:rayon"] max-index = [] serde = ["dep:serde", "dep:serde-big-array"] diff --git a/src/lib.rs b/src/lib.rs index 5b96fc9..7bdd42d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,13 @@ //! ### Feature Roadmap: //! - [x] base (single-core) crate //! - [x] rayon +//! - [x] serde //! - [ ] crossover //! //! You can get started by looking at [genetic-rs docs](https://docs.rs/genetic-rs) and checking the examples for this crate. #![warn(missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] /// A module containing the [`NeuralNetworkTopology`] struct. This is what you want to use in the DNA of your agent, as it is the thing that goes through nextgens and suppors mutation. pub mod topology; diff --git a/src/runnable.rs b/src/runnable.rs index 5bdb82d..53d69e0 100644 --- a/src/runnable.rs +++ b/src/runnable.rs @@ -8,7 +8,7 @@ use rayon::prelude::*; #[cfg(feature = "rayon")] use std::sync::{Arc, RwLock}; -/// A runnable, stated Neural Network generated from a [NeuralNetworkToplogy]. Use [`NeuralNetwork::from`] to go from stateles to runnable. +/// A runnable, stated Neural Network generated from a [NeuralNetworkTopology]. Use [`NeuralNetwork::from`] to go from stateles to runnable. /// Because this has state, you need to run [`NeuralNetwork::flush_state`] between [`NeuralNetwork::predict`] calls. #[derive(Debug)] #[cfg(not(feature = "rayon"))] diff --git a/src/topology.rs b/src/topology.rs index 20181dc..ef7df33 100644 --- a/src/topology.rs +++ b/src/topology.rs @@ -11,13 +11,14 @@ use rand::prelude::*; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Contains useful structs for serializing/deserializing a [`NeuronTopology`] +#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[cfg(feature = "serde")] pub mod nnt_serde { use super::*; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; - /// A serializable wrapper for [`NeuronToplogy`]. See [`NNTSerde::from`] for conversion. + /// A serializable wrapper for [`NeuronTopology`]. See [`NNTSerde::from`] for conversion. #[derive(Serialize, Deserialize)] pub struct NNTSerde { #[serde(with = "BigArray")] From e7f3eca01732cbfe198714f53d3677da4e17e9c3 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+inflectrix@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:27:07 +0000 Subject: [PATCH 23/23] update version number --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc3f519..4ff8f86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "neat" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bincode", "genetic-rs", diff --git a/Cargo.toml b/Cargo.toml index 7d44305..fd2c603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "neat" description = "Crate for working with NEAT in rust" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["Inflectrix"] repository = "https://github.com/inflectrix/neat"