1 | # enome
|
2 | ## [![Build Status](https://travis-ci.org/fiberwire/enome.svg?branch=master)](https://travis-ci.org/fiberwire/enome)
|
3 | ## A Genome generation library
|
4 |
|
5 | ## Note: This library is still in the early stages of active development, and should not be considered producion-ready by any means.
|
6 |
|
7 | ### This library is written in TypeScript, and I recommend using it with a TypeScript project.
|
8 |
|
9 | ### There are a few different ways you can use enome.
|
10 |
|
11 | 1. `Natural Selection`
|
12 | - Automatically evolves genomes based on their fitness.
|
13 | 2. `Artificial Selection`
|
14 | - Allows you to select which genomes will reproduce to spawn future generations
|
15 | - Genomes are presented in a queue-like manner, where you review and take action on genomes one at a time.
|
16 | 3. `Artificial Pooled Selection`
|
17 | - Allows you to select which genomes will be parents to future generations
|
18 | - Parents are not recycled back into the general population queue, and are instead part of a separate pool of genomes that are exclusively used for reproduction.
|
19 | - You can set the size of the parent pool, and when the capacity is reached, any new additions will remove the oldest parent from the pool.
|
20 | 4. Go it on your own
|
21 | - You can use enome primitives (genomes and genes) however you want.
|
22 | - The premade population types are just there to make your life easier. If their functionality doesn't fit your needs, you're free to make your own, or not use one at all.
|
23 | - enome provides a host of operators for doing various things to/with genomes.
|
24 |
|
25 | How enome generates `Genome`s:
|
26 | - Generates a `sequence` of `values` between `zero` and `one`
|
27 | - Groups those `values` into `Genes` by averaging them together
|
28 | - This results in the `Genome` being less sensitive to `mutation`
|
29 | - The sensitivity is customizable by varying the number of `values` that go into each `Gene`
|
30 | - Groups those `Genes` into a `Genome`
|
31 | - `Genome` exposes a property called `nucleo` that allows you to get the next `Gene` in the `Genome`.
|
32 | - This allows you to pass the `Genome` around, consuming its `Gene`s as you need them.
|
33 |
|
34 | How enome determines the `fitness` of a `Genome`:
|
35 | - You define your own `fitness` function.
|
36 | - Generally, your `fitness` function should accept a `Genome` and return an `Evaluation`.
|
37 | - Your fitness function will necessarily create an object from your genome that it will test.
|
38 | - `Evaluation` is just an interface that has the following properties
|
39 | - `fitness`: `number`
|
40 | - the relative `fitness` of the `Genome` being evaluated.
|
41 | - `genome`: `Genome`
|
42 | - the genome that is being evaluated.
|
43 | - `result`: `T`
|
44 | - the object that is created from your genome.
|
45 |
|
46 | What enome allows you to do:
|
47 | - write code that maps a `Genome` to whatever `object` you want to build by consuming genes one at a time.
|
48 | - Define your hyperparameters in your `options` object.
|
49 | - `mutate` and `evolve` that object by mutating and evolving the `Genome` that maps to that object.
|
50 | - do it very simply, by providing upper and lower bounds for each variable you want to evolve.
|
51 |
|
52 |
|
53 | # Example usage for Natural Selection
|
54 | say you want to evolve a list of three numbers between 1 and 100 that will add up to 256.
|
55 |
|
56 | ```
|
57 | import * as _ from 'lodash';
|
58 | import {
|
59 | sampledReproduce,
|
60 | best,
|
61 | Evaluation,
|
62 | Genome,
|
63 | GenomeOptions,
|
64 | Gene,
|
65 | Population,
|
66 | PopulationOptions,
|
67 | replenish
|
68 | } from 'enome';
|
69 |
|
70 | // create an interface that adds your custom options to GenomeOptions
|
71 | interface ListOptions extends GenomeOptions {
|
72 | min: number,
|
73 | max: number,
|
74 | length: number
|
75 | }
|
76 |
|
77 | //define a function that will map a Genome to whatever kind of object you want.
|
78 | function createList(genome: Genome<ListOptions>): number[] {
|
79 | return _.range(genome.options.length)
|
80 | .map(i => genome.nucleo.int(genome.options.min, genome.options.max));
|
81 | }
|
82 |
|
83 | //define a function that will determine the fitness of a Genome.
|
84 | function fitness(genome: Genome<ListOptions>): Evaluation<ListOptions, number[]> {
|
85 | let target = 256;
|
86 |
|
87 | let list = createList(replenish(genome));
|
88 | let sum = _.sum(list);
|
89 | let error = Math.abs(target - sum);
|
90 |
|
91 | return { fitness: 1/error, genome: genome, result: list };
|
92 | }
|
93 |
|
94 | //set genome options
|
95 | let gOptions: ListOptions = {
|
96 | genomeLength: 10,
|
97 | geneLength: 1,
|
98 | min: 1,
|
99 | max: 100,
|
100 | length: 3,
|
101 | loopGenes: false
|
102 | }
|
103 |
|
104 | //set population options
|
105 | let pOptions: PopulationOptions = {
|
106 | populationSize: 20,
|
107 | fillType: 'random', //can be either worst or random
|
108 | fillPercent: 0.15,
|
109 | mutateOptions: {
|
110 | safe: false,
|
111 | sampled: false,
|
112 | sampleSize: 5,
|
113 | mutateChance: 0.15,
|
114 | mutateType: 'sub' //can be either sub or avg
|
115 | },
|
116 | reproduceOptions: {
|
117 | safe: true,
|
118 | sampled: false,
|
119 | sampleSize: 5
|
120 | }
|
121 | };
|
122 |
|
123 | //create population
|
124 | let pop = new NaturalSelection(
|
125 | pOptions,
|
126 | gOptions,
|
127 | createList,
|
128 | fitness
|
129 | );
|
130 |
|
131 | //evolve synchronously
|
132 | let evSync = pop.evolve(100);
|
133 | let list = evSync.result;
|
134 | let fit = evSync.fitness;
|
135 | console.log(`\t`, `list: ${list}, sum: ${_.sum(list)}, fitness: ${fit}`);
|
136 |
|
137 | //or reactively
|
138 | let ev = pop.evolve$(100)
|
139 | .subscribe(e => {
|
140 | let list = e.result;
|
141 | let fit = e.fitness;
|
142 | console.log(`\t`, `list: ${list}, sum: ${_.sum(list)}, fitness: ${fit}`);
|
143 | },
|
144 | err => console.log(err))
|
145 |
|
146 | ``` |
\ | No newline at end of file |