7 releases

new 0.1.6 Nov 15, 2024
0.1.5 Feb 11, 2024
0.1.3 Jan 25, 2024
0.1.2 Sep 20, 2023
0.1.0 Sep 19, 2022

#25 in Science

Download history 13/week @ 2024-07-27 7/week @ 2024-09-21 1/week @ 2024-09-28

360 downloads per month

GPL-3.0 license

110KB
1.5K SLoC

repository crates docs.rs build

nogithub

Perestroika

Perestroika - a library for simulating evolution.

Description

Perestroika is a perhaps naive library that provides primitives to design simulations that are based on genetic algorithms.

These primitives include Nodes which are agreggated into Layers, which in turn are interconnected with Connections. The whole structure is then considered to be a Genome, which is capable of trying to propagate an input to an output.

Genomes are also subjects to mutation events, which can happen randomly or controllably. This can be used to drive optimization processes and evolution simulations.

Unlike CNNs, in Perestroika, the connections can connect nodes from different layers, as long as the relationship between the source and target is from a shallow layer to a deeper layer.

Usage

Add this to your Cargo.toml:

[dependencies]
perestroika = "0.1"

Or

cargo add perestroika

Quick start

Since the crate relies heavily on randomness, in many examples a specific seed is used for the sake of reproducibility, this is an intended use case of the crate as well.

Minimal

To generate the smallest useful Genome with an input and an output Layers and a Connection:

use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;

use perestroika::Error;
use perestroika::Genome;
use perestroika::GenomeBuilder;
use perestroika::DepthType;

// This genome uses ChaCha8Rng as the random number generator.
// Let's also set a seed for reproducibility:
let rng: ChaCha8Rng = ChaCha8Rng::seed_from_u64(42);

// It is a very simple genome, consisting of a single input node a single output node
// and a connection between them.
let mut genome: Genome = GenomeBuilder::new()
    .with_shape(&[1, 1])?
    .with_fully_connected_layers(&[[DepthType::Input, DepthType::Output]])?
    .build()?;

// Nodes by default use the Identity activation function.
let output = genome.propagate(&[0.42])?;
assert_ne!(output, &[0.0]);
# Ok::<(), Error>(())

More practical

Generating a more complex genome and propagating through it.

use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;

use perestroika::Error;
use perestroika::Genome;
use perestroika::GenomeBuilder;

let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567);

let mut genome: Genome = GenomeBuilder::new()
    .with_shape(&vec![8, 6, 4, 2, 4, 6, 4])?
    .with_cnn_structure()?
    .build()?;

// Let's create an input vector:
let input_vector: Vec<f64> = vec![0.1, 0.2, 0.3, 0.31428, 0.424242, 0.999, -0.17, 0.5];
// Be sure that the input vector and the InputLayer have matching dimensions.

// Propagate it through the genome to get the output:
let output: Vec<f64> = genome.propagate(&input_vector)?;

// Output: [1.0, 0.20511543379524064, -1.0, 0.15121278824891932].
# Ok::<(), Error>(())

Using different random number generators

It is possible to use other random number generators, as long as they impl Rng + Clone. A possible place to use the generator is in the mutate_randomly method.

use rand::rngs::StdRng;
use rand::Rng;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;

use perestroika::Error;
use perestroika::Genome;
use perestroika::GenomeBuilder;

let mut std_rng = StdRng::seed_from_u64(17);
let mut chacha8_rng = rand_chacha::ChaCha8Rng::seed_from_u64(17);

let mut genome: Genome = GenomeBuilder::new()
    .with_shape(&vec![4, 16, 4])?
    .with_cnn_structure()?
    .build()?;

// Mutate the genome.
genome.mutate_randomly(&mut std_rng)?;
genome.mutate_randomly(&mut chacha8_rng)?;

# Ok::<(), Error>(())

Documentation and examples

See the crate's documentation for the docs.

Check out the examples directory for examples to get going:

For a more comprehensive simulation example with GUI and tunable parameters, check the kombinat repository.

Notes

Note 1: The current roadmap is to figure an API within the 0.1.* versions and stabilize it. This means that until 0.2, the API and functionality will probably change, break or differ in one way or another between versions.

Note 2: Perestroika aims to be a minimalist simulation library with very minimal dependencies. Currently (v0.1.6), it depends only on rand-* for the generators.

Issues, bugs, suggestions

For issues, bugs, suggestions, comments or anything of the sort feel free to open a new issue on the repository.

Perestroika's Zulip might be a place for more direct communication, although it is pretty empty at the moment.

Motivation

The motivation behind this project is to make a full scale project in Rust, while integrating many more other fields such as game development in Bevy, DevOps and, of course, neural networks.

Uses

Currently being used in an in-development game that I prototype in my spare time.

More projects that employ perestroika will be listed below.

Background

"Perestroika" is the Russian word for "Reconstruction" and serves as a double pun: Perestroika was a late USSR political movement trying to reconstruct the regime; it is also a very fitting description for Genetic recombination (or genetic reshuffling) which is the target of this library.

Bibliography and sources

Efficient Evolution of Neural Networks through Complexification as the paper and background.

The Bibites provide a good overview of an already implemented game that follows similar principles.

Hosting

Get it on Codeberg

Dependencies

~1.3–2MB
~36K SLoC