diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000..68c1a8c
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,6 @@
+Thanks for contributing to this project.
+
+To get started, check out the [issues page](https://github.com/inflectrix/neat). You can either find a feature/fix from there or start a new issue, then begin implementing it in your own fork of this repo.
+
+Once you are done making the changes you'd like the make, start a pull request to the [dev](https://github.com/inflectrix/neat/tree/dev) branch. State your changes and request a review. After all branch rules have been satisfied, someone with management permissions on this repository will merge it.
+
diff --git a/Cargo.lock b/Cargo.lock
index 78fcb3c..4356c96 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -50,15 +50,37 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "genetic-rs"
-version = "0.3.0"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94601f3db2fb341f71a4470134eb1f71d39f54c2fe264122698eda67cd1c91b"
+dependencies = [
+ "genetic-rs-common",
+ "genetic-rs-macros",
+]
+
+[[package]]
+name = "genetic-rs-common"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "694916cca5538e02a6f04a0fcb5deef9297b460c697520b9f88ea4b9c0a1a09c"
+checksum = "4f41b0e3f6ccb66a00e7fc9170d4e02b1ae80c85f03c67b76b067b3637fd314a"
dependencies = [
"rand",
"rayon",
"replace_with",
]
+[[package]]
+name = "genetic-rs-macros"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5ec3b9e69a6836bb0f0c8fa6972e6322e0b49108f7b3ed40769feb452c120a"
+dependencies = [
+ "genetic-rs-common",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "getrandom"
version = "0.2.12"
@@ -70,6 +92,12 @@ dependencies = [
"wasi",
]
+[[package]]
+name = "itoa"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
+
[[package]]
name = "libc"
version = "0.2.153"
@@ -78,7 +106,7 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "neat"
-version = "0.2.1"
+version = "0.4.0"
dependencies = [
"bincode",
"genetic-rs",
@@ -86,6 +114,7 @@ dependencies = [
"rayon",
"serde",
"serde-big-array",
+ "serde_json",
]
[[package]]
@@ -168,6 +197,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690"
+[[package]]
+name = "ryu"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
+
[[package]]
name = "serde"
version = "1.0.197"
@@ -197,11 +232,22 @@ dependencies = [
"syn",
]
+[[package]]
+name = "serde_json"
+version = "1.0.114"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
[[package]]
name = "syn"
-version = "2.0.50"
+version = "2.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
+checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index e08a721..acc8fcc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "neat"
description = "Crate for working with NEAT in rust"
-version = "0.2.1"
+version = "0.4.0"
edition = "2021"
authors = ["Inflectrix"]
repository = "https://github.com/inflectrix/neat"
@@ -26,11 +26,12 @@ serde = ["dep:serde", "dep:serde-big-array"]
[dependencies]
-genetic-rs = "0.3"
+genetic-rs = { version = "0.5.1", features = ["derive"] }
rand = "0.8.5"
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"
\ No newline at end of file
+bincode = "1.3.3"
+serde_json = "1.0.114"
\ No newline at end of file
diff --git a/README.md b/README.md
index 140a66c..ad775e2 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,14 @@
[
](https://crates.io/crates/neat)
[
](https://docs.rs/neat)
-Implementation of the NEAT algorithm using `genetic-rs`
+Implementation of the NEAT algorithm using `genetic-rs`.
### Features
- rayon - Uses parallelization on the `NeuralNetwork` struct and adds the `rayon` feature to the `genetic-rs` re-export.
- serde - Adds the NNTSerde struct and allows for serialization of `NeuralNetworkTopology`
-- crossover - Implements the `CrossoverReproduction` trait on `NeuralNetworkTopology`
+- crossover - Implements the `CrossoverReproduction` trait on `NeuralNetworkTopology` and adds the `crossover` feature to the `genetic-rs` re-export.
+
+*Do you like this repo and want to support it? If so, leave a ⭐*
### How To Use
When working with this crate, you'll want to use the `NeuralNetworkTopology` struct in your agent's DNA and
@@ -18,55 +20,58 @@ Here's an example of how one might use this crate:
```rust
use neat::*;
-#[derive(Clone)]
+#[derive(Clone, RandomlyMutable, DivisionReproduction)]
struct MyAgentDNA {
network: NeuralNetworkTopology<1, 2>,
- other_stuff: Foo,
-}
-
-impl RandomlyMutable for MyAgentDNA {
- fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) {
- self.network.mutate(rate, rng);
- self.other_stuff.mutate(rate, rng);
- }
-}
-
-impl DivisionReproduction for MyAgentDNA {
- fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self {
- Self {
- network: self.network.spawn_child(rng),
- // ...
- }
- }
}
impl GenerateRandom for MyAgentDNA {
fn gen_random(rng: &mut impl rand::Rng) -> Self {
Self {
network: NeuralNetworkTopology::new(0.01, 3, rng),
- other_stuff: Foo::gen_random(rng),
}
}
}
struct MyAgent {
network: NeuralNetwork<1, 2>,
- some_other_state: Bar,
+ // ... other state
}
impl From<&MyAgentDNA> for MyAgent {
fn from(value: &MyAgentDNA) -> Self {
Self {
network: NeuralNetwork::from(&value.network),
- some_other_state: Bar::default(),
}
}
}
fn fitness(dna: &MyAgentDNA) -> f32 {
+ // agent will simply try to predict whether a number is greater than 0.5
let mut agent = MyAgent::from(dna);
+ let mut rng = rand::thread_rng();
+ let mut fitness = 0;
- // ... use agent.network.predict() and agent.network.flush() throughout multiple iterations
+ // use repeated tests to avoid situational bias and some local maximums, overall providing more accurate score
+ for _ in 0..10 {
+ let n = rng.gen::();
+ let above = n > 0.5;
+
+ let res = agent.network.predict([n]);
+ let resi = res.iter().max_index();
+
+ if resi == 0 ^ above {
+ // agent did not guess correctly, punish slightly (too much will hinder exploration)
+ fitness -= 0.5;
+
+ continue;
+ }
+
+ // agent guessed correctly, they become more fit.
+ fitness += 3.;
+ }
+
+ fitness
}
fn main() {
@@ -78,9 +83,20 @@ fn main() {
division_pruning_nextgen,
);
- // ... simulate generations, etc.
+ // simulate 100 generations
+ for _ in 0..100 {
+ sim.next_generation();
+ }
+
+ // display fitness results
+ let fits: Vec<_> = sim.entities
+ .iter()
+ .map(fitness)
+ .collect();
+
+ dbg!(&fits, fits.iter().max());
}
```
### License
-This crate falls under the `MIT` license
\ No newline at end of file
+This crate falls under the `MIT` license
diff --git a/examples/basic.rs b/examples/basic.rs
index 93f5cf9..bcd5d6d 100644
--- a/examples/basic.rs
+++ b/examples/basic.rs
@@ -3,36 +3,14 @@
use neat::*;
use rand::prelude::*;
-#[derive(PartialEq, Clone, Debug)]
+#[derive(PartialEq, Clone, Debug, DivisionReproduction, RandomlyMutable)]
+#[cfg_attr(feature = "crossover", derive(CrossoverReproduction))]
struct AgentDNA {
network: NeuralNetworkTopology<2, 4>,
}
-impl RandomlyMutable for AgentDNA {
- fn mutate(&mut self, rate: f32, rng: &mut impl Rng) {
- self.network.mutate(rate, rng);
- }
-}
-
impl Prunable for AgentDNA {}
-impl DivisionReproduction for AgentDNA {
- fn divide(&self, rng: &mut impl Rng) -> Self {
- let mut child = self.clone();
- child.mutate(self.network.mutation_rate, rng);
- child
- }
-}
-
-#[cfg(feature = "crossover")]
-impl CrossoverReproduction for AgentDNA {
- fn crossover(&self, other: &Self, rng: &mut impl Rng) -> Self {
- Self {
- network: self.network.crossover(&other.network, rng),
- }
- }
-}
-
impl GenerateRandom for AgentDNA {
fn gen_random(rng: &mut impl rand::Rng) -> Self {
Self {
@@ -165,14 +143,17 @@ fn main() {
sim.next_generation();
}
- let fits: Vec<_> = sim.genomes.iter().map(fitness).collect();
+ let mut fits: Vec<_> = sim.genomes.iter().map(|e| (e, fitness(e))).collect();
- let maxfit = fits
- .iter()
- .max_by(|a, b| a.partial_cmp(b).unwrap())
- .unwrap();
+ fits.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap());
- dbg!(&fits, maxfit);
+ dbg!(&fits);
+
+ if cfg!(feature = "serde") {
+ let intermediate = NNTSerde::from(&fits[0].0.network);
+ let serialized = serde_json::to_string(&intermediate).unwrap();
+ println!("{}", serialized);
+ }
}
#[cfg(all(feature = "crossover", feature = "rayon"))]
@@ -183,12 +164,15 @@ fn main() {
sim.next_generation();
}
- let fits: Vec<_> = sim.genomes.iter().map(fitness).collect();
+ let mut fits: Vec<_> = sim.genomes.iter().map(|e| (e, fitness(e))).collect();
- let maxfit = fits
- .iter()
- .max_by(|a, b| a.partial_cmp(b).unwrap())
- .unwrap();
+ fits.sort_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap());
- dbg!(&fits, maxfit);
+ dbg!(&fits);
+
+ if cfg!(feature = "serde") {
+ let intermediate = NNTSerde::from(&fits[0].0.network);
+ let serialized = serde_json::to_string(&intermediate).unwrap();
+ println!("serialized: {}", serialized);
+ }
}