evolvable 0.1.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.yardopts +4 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +39 -39
- data/LICENSE +21 -0
- data/README.md +234 -248
- data/README_YARD.md +237 -0
- data/bin/console +20 -43
- data/evolvable.gemspec +2 -3
- data/examples/ascii_art.rb +62 -0
- data/examples/ascii_gene.rb +9 -0
- data/examples/hello_world.rb +91 -0
- data/examples/images/diagram.png +0 -0
- data/examples/stickman.rb +77 -0
- data/exe/hello +16 -0
- data/lib/evolvable/count_gene.rb +42 -0
- data/lib/evolvable/equalize_goal.rb +29 -0
- data/lib/evolvable/error/undefined_method.rb +7 -0
- data/lib/evolvable/evaluation.rb +74 -0
- data/lib/evolvable/evolution.rb +58 -0
- data/lib/evolvable/gene.rb +67 -0
- data/lib/evolvable/gene_combination.rb +69 -0
- data/lib/evolvable/genome.rb +86 -0
- data/lib/evolvable/goal.rb +52 -0
- data/lib/evolvable/maximize_goal.rb +30 -0
- data/lib/evolvable/minimize_goal.rb +29 -0
- data/lib/evolvable/mutation.rb +71 -42
- data/lib/evolvable/point_crossover.rb +71 -0
- data/lib/evolvable/population.rb +202 -83
- data/lib/evolvable/rigid_count_gene.rb +17 -0
- data/lib/evolvable/search_space.rb +181 -0
- data/lib/evolvable/selection.rb +45 -0
- data/lib/evolvable/serializer.rb +21 -0
- data/lib/evolvable/uniform_crossover.rb +42 -0
- data/lib/evolvable/version.rb +1 -1
- data/lib/evolvable.rb +203 -56
- metadata +46 -24
- data/.rubocop.yml +0 -20
- data/lib/evolvable/crossover.rb +0 -35
- data/lib/evolvable/errors/not_implemented.rb +0 -5
- data/lib/evolvable/helper_methods.rb +0 -45
- data/lib/evolvable/hooks.rb +0 -9
data/README.md
CHANGED
@@ -1,373 +1,359 @@
|
|
1
|
-
# Evolvable
|
1
|
+
# Evolvable 🦎
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/evolvable.svg)](https://badge.fury.io/rb/evolvable) [![Maintainability](https://api.codeclimate.com/v1/badges/7faf84a6d467718b33c0/maintainability)](https://codeclimate.com/github/mattruzicka/evolvable/maintainability)
|
4
4
|
|
5
|
-
|
5
|
+
An evolutionary framework for writing programs that use operations such as selection, crossover, and mutation. Explore ideas generatively in any domain, discover novel solutions to complex problems, and build intuitions about intelligence, complexity, and the natural world.
|
6
6
|
|
7
|
-
|
8
|
-
- [Evolvable Equation](https://github.com/mattruzicka/evolvable_equation) - Evolves an equation of a specified length to evaluate to a given number
|
7
|
+
Subscribe to the [Evolvable Newsletter](https://www.evolvable.site/newsletter) to slowly learn more, or keep reading this contextualization of the [full documentation](https://rubydoc.info/github/mattruzicka/evolvable).
|
9
8
|
|
10
|
-
## Usage
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
## Table of Contents
|
11
|
+
* [Installation](#installation)
|
12
|
+
* [Getting Started](#getting-started)
|
13
|
+
* [Concepts](#concepts)
|
14
|
+
* [Genes](#genes)
|
15
|
+
* [Populations](#populations)
|
16
|
+
* [Evaluation](#evaluation)
|
17
|
+
* [Evolution](#evolution)
|
18
|
+
* [Selection](#selection)
|
19
|
+
* [Combination](#combination)
|
20
|
+
* [Mutation](#mutation)
|
21
|
+
* [Search Space](#search-space)
|
22
22
|
|
23
|
-
### Getting Started
|
24
23
|
|
25
|
-
|
24
|
+
## Installation
|
26
25
|
|
27
|
-
|
28
|
-
2. Define ```.evolvable_gene_pool``` ([documentation](#The-Gene-Pool))
|
29
|
-
3. Define ```#fitness``` ([documentation](#Fitness))
|
26
|
+
Add [gem "evolvable"](https://rubygems.org/gems/evolvable) to your Gemfile and run `bundle install` or install it yourself with: `gem install evolvable`
|
30
27
|
|
31
|
-
|
28
|
+
## Getting Started
|
32
29
|
|
33
|
-
|
34
|
-
|
30
|
+
The `Evolvable` module makes it possible to implement evolutionary behaviors for
|
31
|
+
any class by defining a `.search_space` class method and `#value` instance method.
|
32
|
+
Then to evolve instances, initialize a population with `.new_population` and invoke
|
33
|
+
the `#evolve` method on the resulting population object.
|
35
34
|
|
36
|
-
|
37
|
-
include Evolvable
|
35
|
+
### Implementation Steps
|
38
36
|
|
39
|
-
|
40
|
-
|
37
|
+
1. [Include the `Evolvable` module in the class you want to evolve.](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable)
|
38
|
+
2. [Define `.search_space` and any gene classes that you reference.](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/SearchSpace)
|
39
|
+
3. [Define `#value`.](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Evaluation)
|
40
|
+
4. [Initialize a population with `.new_population` and use `#evolve`.](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Population)
|
41
41
|
|
42
|
-
def self.evolvable_gene_pool
|
43
|
-
Array.new(DESIRED_WORDS.length) { |index| [index, DICTIONARY] }
|
44
|
-
end
|
45
|
-
|
46
|
-
def fitness
|
47
|
-
score = 0
|
48
|
-
@genes.values.each_with_index do |value, index|
|
49
|
-
score += 1 if value == DESIRED_WORDS[index]
|
50
|
-
end
|
51
|
-
score
|
52
|
-
end
|
53
|
-
end
|
54
|
-
```
|
55
42
|
|
56
|
-
|
43
|
+
To demonstrate these steps, we'll look at the [Hello World](#) example program.
|
57
44
|
|
58
|
-
|
59
|
-
# To play with a more interactive version, check out https://github.com/mattruzicka/evolvable_sentence
|
45
|
+
### Hello World
|
60
46
|
|
61
|
-
population
|
62
|
-
object = population.strongest_object
|
47
|
+
Let's build the evolvable hello world program using the above steps. It'll evolve a population of arbitrary strings to be more like a given target string. After installing this gem, run `evolvable hello` at the command line to see it in action.
|
63
48
|
|
64
|
-
|
65
|
-
words = object.genes.values.join
|
66
|
-
puts words
|
67
|
-
system("say #{words}")
|
68
|
-
population.evolve!(fitness_goal: Sentence::DESIRED_WORDS.length)
|
69
|
-
object = population.strongest_object
|
70
|
-
end
|
49
|
+
Below is example output from evolving a population of randomly initialized string objects to match "Hello World!", then "Hello Evolvable World".
|
71
50
|
|
72
51
|
```
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
52
|
+
❯ Enter a string to evolve: Hello World!
|
53
|
+
|
54
|
+
pp`W^jXG'_N`% Generation 0
|
55
|
+
H-OQXZ\a~{H* Generation 1 ...
|
56
|
+
HRv9X WorlNi Generation 50 ...
|
57
|
+
HRl6W World# Generation 100 ...
|
58
|
+
Hello World! Generation 165
|
59
|
+
|
60
|
+
❯ Enter a string to evolve: Hello Evolvable World
|
61
|
+
|
62
|
+
Helgo World!b+=1}3 Generation 165
|
63
|
+
Helgo Worlv!}:c(SoV Generation 166
|
64
|
+
Helgo WorlvsC`X(Joqs Generation 167
|
65
|
+
Helgo WorlvsC`X(So1RE Generation 168 ...
|
66
|
+
Hello Evolv#"l{ Wor*5 Generation 300 ...
|
67
|
+
Hello Evolvable World Generation 388
|
78
68
|
```
|
79
69
|
|
80
|
-
|
70
|
+
### Step 1
|
81
71
|
|
82
|
-
|
72
|
+
Let's begin by defining a `HelloWorld` class and have it **include the `Evolvable` module**.
|
83
73
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
74
|
+
```ruby
|
75
|
+
class HelloWorld
|
76
|
+
include Evolvable
|
77
|
+
end
|
78
|
+
```
|
88
79
|
|
89
|
-
###
|
80
|
+
### Step 2
|
90
81
|
|
91
|
-
|
82
|
+
Now we can **define the `.search_space`** class method with the types of [genes](#genes) that we want our our evolvable "hello world" instances to be able to have. We'll use `CharGene` instances to represent single characters within strings. So an instance with the string value of "Hello" would be composed of five `CharGene` instances.
|
92
83
|
|
93
84
|
```ruby
|
94
|
-
class
|
95
|
-
|
85
|
+
class HelloWorld
|
86
|
+
include Evolvable
|
96
87
|
|
97
|
-
def self.
|
98
|
-
[
|
99
|
-
[:piano_2, NOTES]]
|
88
|
+
def self.search_space
|
89
|
+
["CharGene", 1..40]
|
100
90
|
end
|
101
91
|
end
|
102
92
|
```
|
103
93
|
|
104
|
-
The
|
94
|
+
The [Search Space](#search-space) can be defined in a variety of ways. The above is shorthand that's useful for when there's only one type of gene. This method can also return an array of arrays or hash.
|
105
95
|
|
106
|
-
|
107
|
-
require 'evolvable'
|
96
|
+
The `1..40` specifies the range of possible genes for a particular HelloWorld instance. Evolvable translates this range or integer value into a `Evolvable::CountGene` object.
|
108
97
|
|
109
|
-
|
110
|
-
|
98
|
+
By specifying a range, an `Evolvable::CountGene` instance can change the number of genes that are present in an evovlable instance. Count genes undergo evolutionary operations like any other gene. Their effects can be seen in the letter changes from Generation 165 to 168 in the above example output.
|
99
|
+
|
100
|
+
To finish step 2, we'll **define the gene class** that we referenced in the above `.search_space` method. Gene classes should include the `Evolvable::Gene` module.
|
111
101
|
|
112
|
-
|
113
|
-
|
114
|
-
|
102
|
+
```ruby
|
103
|
+
class CharGene
|
104
|
+
include Evolvable::Gene
|
105
|
+
|
106
|
+
def self.chars
|
107
|
+
@chars ||= 32.upto(126).map(&:chr)
|
115
108
|
end
|
116
109
|
|
117
|
-
def
|
118
|
-
|
110
|
+
def to_s
|
111
|
+
@to_s ||= self.class.chars.sample
|
119
112
|
end
|
120
113
|
end
|
121
|
-
|
122
|
-
ComplexBot.evolvable_gene_pool.size # => 1_000_000
|
123
|
-
population = ComplexBot.evolvable_population
|
124
|
-
complex_bot = population.objects.first
|
125
|
-
complex_bot.genes.count # => 10_000
|
126
114
|
```
|
127
115
|
|
128
|
-
|
116
|
+
It's important that, once accessed, the data for a particular gene never change. When the `#to_s` method first runs, Ruby's `||=` operator memoizes the result of randomly picking a char, enabling this method to sample a char only once per gene.
|
129
117
|
|
130
|
-
|
118
|
+
After defining the search space, we can now initialize `HelloWorld` instances with random genes, but to actually evolve them, we need to **define the `#value` instance method**. It provides the basis for comparing different evolvable instances.
|
131
119
|
|
132
|
-
|
133
|
-
class TetrisBot
|
134
|
-
alias fitness tetris_score
|
135
|
-
end
|
136
|
-
```
|
120
|
+
### Step 3
|
137
121
|
|
138
|
-
|
122
|
+
In the next step, we'll set the goal value to 0, so that evolution favors evolvable HelloWorld instances with `#value` methods that return numbers closer to 0. That means we want instances that more closely match their targets to return scores nearer to 0. As an example, if our target is "hello world", an instance that returns "jello world" would have a value of 1 and "hello world" would have a value of 0.
|
139
123
|
|
140
|
-
|
141
|
-
class TetrisArtBot
|
142
|
-
def fitness
|
143
|
-
artist_friend_ratings.sum / artist_friend_ratings.count
|
144
|
-
end
|
145
|
-
end
|
146
|
-
```
|
124
|
+
For a working implementation, see the `#value` method in [examples/hello_world.rb](https://github.com/mattruzicka/evolvable/blob/main/examples/hello_world.rb)
|
147
125
|
|
148
|
-
|
126
|
+
### Step 4
|
149
127
|
|
150
|
-
|
128
|
+
Now it's time to **initialize a population with `.new_population`**. By default, evolvable populations seek to maximize numeric values. In this program, we always know the best possible value, so setting the goal to a concrete number makes sense. This is done by passing the evaluation params with equalize set to 0.
|
151
129
|
|
152
|
-
|
153
|
-
class GamerBot
|
154
|
-
def self.evolvable_evaluate!(objects)
|
155
|
-
objects.combination(2).each do |player_1, player_2|
|
156
|
-
winner = Game.play(player_1, player_2)
|
157
|
-
winner.win_count += 1
|
158
|
-
end
|
159
|
-
end
|
130
|
+
We'll also specify the number of instances in a population using the population's `size` parameter and change the mutation porbability from 0.03 (3%) to 0.6 (60%).
|
160
131
|
|
161
|
-
|
162
|
-
|
132
|
+
Experimentation has suggested that a large mutation probability tends to decrease the time it takes to evolve matches with short strings and has the opposite effect for long strings. This is demonstrated in the example output above by how many generations it took to go from "Hello World!" to "Hello Evolvable World". As an optimization, we could dynamically change the mutation probability using a population hook detailed below, but doing so will be left as an exercise for the reader. [Pull requests are welcome.](#contributing)
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
population = HelloWorld.new_population(size: 100,
|
136
|
+
evaluation: { equalize: 0 },
|
137
|
+
mutation: { probability: 0.6 }
|
163
138
|
```
|
164
139
|
|
165
|
-
### Evolvable Population
|
166
140
|
|
167
|
-
The
|
141
|
+
At this point, everything should work when we run `population.evolve`, but it'll look like nothing is happening. The next section will allow us to gain instight by hooking into the evolutionary process.
|
168
142
|
|
169
|
-
|
170
|
-
2. ```Evolvable::Population.new(evolvable_class: EvolvableBot)```
|
143
|
+
### Evolvable Population Hooks
|
171
144
|
|
145
|
+
The following class methods can be implemented on your Evolvable class, e.g. HelloWorld, to hook into the Population#evolve lifecycle. This is useful for logging evolutionary progress, optimizing parameters, and creating interactions with and between evolvable instances.
|
172
146
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
crossover: Crossover.new, # Any object that implements #call
|
179
|
-
mutation: Mutation.new, # Any object that implements #call!
|
180
|
-
generation_count: 0, # Useful when you want to re-initialize a previously saved population of objects
|
181
|
-
objects: [], # Ditto
|
182
|
-
log_progress: false } # See the "Monitoring Progress" section for details
|
183
|
-
```
|
147
|
+
1. `.before_evaluation(population)` - Runs before evaluation.
|
148
|
+
|
149
|
+
2. `.before_evolution(population)`- Runs after evaluation and before evolution.
|
150
|
+
|
151
|
+
3. `.after_evolution(population)` - Runs after evolution.
|
184
152
|
|
185
|
-
|
153
|
+
|
154
|
+
Let's define `.before_evolution` to print the best value for each generation. We'll also define `HelloWorld#to_s`, which implicitly delegates to `CharGene#to_s` during the string interpolation that happens.
|
186
155
|
|
187
156
|
```ruby
|
188
|
-
class
|
157
|
+
class HelloWorld
|
189
158
|
include Evolvable
|
190
159
|
|
191
|
-
def self.
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
log_progress: false }
|
160
|
+
def self.before_evolution(population)
|
161
|
+
best_evolvable = population.best_evolvable
|
162
|
+
evolutions_count = population.evolutions_count
|
163
|
+
puts "#{best_evolvable} - Generation #{evolutions_count}"
|
196
164
|
end
|
197
165
|
|
198
|
-
|
199
|
-
|
200
|
-
|
166
|
+
# ...
|
167
|
+
|
168
|
+
def to_s
|
169
|
+
@to_s ||= genes.join
|
201
170
|
end
|
202
|
-
end
|
203
171
|
|
204
|
-
|
205
|
-
|
206
|
-
population.selection_count # => 5
|
207
|
-
population.mutation.rate # => 0.3
|
208
|
-
population.log_progress # => true
|
172
|
+
# ...
|
173
|
+
end
|
209
174
|
```
|
210
175
|
|
211
|
-
|
176
|
+
Finally we can **evolve the population with the `Evolvable::Population#evolve` instance method**.
|
212
177
|
|
213
178
|
```ruby
|
214
|
-
|
215
|
-
def self.evolvable_initialize(genes, population, object_index)
|
216
|
-
friend = new(name: BABY_NAMES.sample)
|
217
|
-
friend.genes = genes
|
218
|
-
friend.population = population
|
219
|
-
friend
|
220
|
-
end
|
221
|
-
end
|
179
|
+
population.evolve
|
222
180
|
```
|
223
181
|
|
224
|
-
|
182
|
+
**You now know the fundamental steps to building evolvable programs of endless complexity in any domain!** 🐸 The exact implementation for the command line demo can be found in [exe/hello](https://github.com/mattruzicka/evolvable/blob/main/exe/hello) and [examples/hello_world.rb](https://github.com/mattruzicka/evolvable/blob/main/examples/hello_world.rb).
|
225
183
|
|
226
|
-
|
227
|
-
friend.name == "#{name} #{population.generation_count}.#{object_index}" # => "ImaginaryFriend 0.11"
|
228
|
-
```
|
184
|
+
## Concepts
|
229
185
|
|
230
|
-
|
186
|
+
[Populations](#populations) are composed of evolvables which are composed of genes. Evolvables orchestrate behaviors by delegating to gene objects. Collections of genes are organized into genomes and constitute the [search space](#search-space). [Evaluation](#evaluation) and [evolution](#evolution) objects are used to evolve populations. By default, evolution is composed of [selection](#selection), [combination](#combination), and [mutation](#mutation).
|
231
187
|
|
232
|
-
|
233
|
-
population = load_chat_bot_population
|
234
|
-
Evolvable::Population.new(evolvable_class: population.evolvable_class,
|
235
|
-
size: population.size,
|
236
|
-
selection_count: population.selection_count,
|
237
|
-
crossover: population.crossover,
|
238
|
-
mutation: population.mutation,
|
239
|
-
generation_count: population.generation_count,
|
240
|
-
objects: population.objects)
|
241
|
-
```
|
188
|
+
The following concept map depicts how genes flow through populations.
|
242
189
|
|
243
|
-
|
190
|
+
![Concept Map](https://github.com/mattruzicka/evolvable/raw/main/examples/images/diagram.png)
|
244
191
|
|
245
|
-
|
192
|
+
Evolvable is designed with extensibility in mind. Evolvable objects such as [evaluation](#evaluation), [evolution](#evolution), [selection](#selection), [combination](#combination), and [mutation](#mutation) can be extended and swapped, potentially in ways that alter the above graph.
|
246
193
|
|
247
|
-
|
194
|
+
## Genes
|
195
|
+
For evolution to be effective, an evolvable's genes must be able to influence
|
196
|
+
its behavior. Evolvables are composed of genes that can be used to run simple
|
197
|
+
functions or orchestrate complex interactions. The level of abstraction is up
|
198
|
+
to you.
|
248
199
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
population.log_progress = true
|
258
|
-
```
|
200
|
+
Defining gene classes requires encapsulating some "sample space" and returning
|
201
|
+
a sample outcome when a gene attribute is accessed. For evolution to proceed
|
202
|
+
in a non-random way, the same sample outcome should be returned every time
|
203
|
+
a particular gene is accessed with a particular set of parameters.
|
204
|
+
Memoization is a useful technique for doing just this. The
|
205
|
+
[memo_wise](https://github.com/panorama-ed/memo_wise) gem may be useful for
|
206
|
+
more complex memoizations.
|
259
207
|
|
260
|
-
Hooks can also be used to monitor progress.
|
261
208
|
|
262
|
-
|
209
|
+
```ruby
|
210
|
+
# This gene generates a random hexidecimal color code for use by evolvables.
|
263
211
|
|
264
|
-
|
212
|
+
require 'securerandom'
|
265
213
|
|
266
|
-
|
214
|
+
class ColorGene
|
215
|
+
include Evolvable::Gene
|
216
|
+
|
217
|
+
def hex_code
|
218
|
+
@hex_code ||= SecureRandom.hex(3)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
```
|
267
222
|
|
268
|
-
```.evolvable_after_select(population)```
|
269
223
|
|
270
|
-
|
224
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Gene)
|
271
225
|
|
272
|
-
|
226
|
+
## Populations
|
227
|
+
Population objects are responsible for generating and evolving instances.
|
228
|
+
They orchestrate all the other Evolvable objects to do so.
|
229
|
+
|
230
|
+
Populations can be initialized and re-initialized with a number of useful
|
231
|
+
parameters.
|
273
232
|
|
274
|
-
The [Evolvable::Mutation](https://github.com/mattruzicka/evolvable/blob/master/lib/evolvable/mutation.rb) class defines the default mutation implementation with a default mutation rate of 0.03. It can be initialized with a custom mutation rate like so:
|
275
233
|
|
276
234
|
```ruby
|
277
|
-
|
278
|
-
population = Evolvable::Population.new(mutation: mutation)
|
279
|
-
population.evolve!
|
235
|
+
# TODO: initialize a population with all supported parameters
|
280
236
|
```
|
281
237
|
|
282
|
-
Any Ruby object that implements ```#call!(objects)``` can be used as a mutation object. The default implementation is specialized to work with evolvable objects that define a ```evolvable_genes_count``` that is less than ```evolvable_gene_pool.size```. For more information on this, see [The Gene Pool](#The-Gene-Pool)
|
283
238
|
|
284
|
-
|
239
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Population)
|
240
|
+
|
241
|
+
## Evaluation
|
242
|
+
For selection to be effective in the context of evolution, there needs to be
|
243
|
+
a way to compare evolvables. In the genetic algorithm, this is often
|
244
|
+
referred to as the "fitness function".
|
245
|
+
|
246
|
+
The `Evolvable::Evaluation` object expects evolvable instances to define a `#value` method that
|
247
|
+
returns some numeric value. Values are used to evaluate instances relative to each
|
248
|
+
other and with regards to some goal. Out of the box, the goal can be set
|
249
|
+
to maximize, minimize, or equalize numeric values.
|
285
250
|
|
286
|
-
The [Evolvable::Crossover](https://github.com/mattruzicka/evolvable/blob/master/lib/evolvable/crossover.rb) class defines the default crossover implementation.
|
287
251
|
|
288
252
|
```ruby
|
289
|
-
|
290
|
-
population = Evolvable::Population.new(crossover: crossover)
|
291
|
-
population.evolve!
|
292
|
-
```
|
253
|
+
# TODO: Show how to add/change population's evaluation object
|
293
254
|
|
294
|
-
|
255
|
+
# The goal value can also be assigned via as argument to `Evolvable::Population#evolve`
|
256
|
+
population.evolve(goal_value: 1000)
|
257
|
+
```
|
295
258
|
|
296
|
-
### Helper Methods
|
297
259
|
|
298
|
-
|
260
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Evaluation)
|
299
261
|
|
300
|
-
|
262
|
+
## Evolution
|
263
|
+
After a population's instances are evaluated, they undergo evolution.
|
264
|
+
The default evolution object is composed of selection,
|
265
|
+
crossover, and mutation objects and applies them as operations to
|
266
|
+
a population's evolvables in that order.
|
301
267
|
|
302
|
-
```ruby
|
303
|
-
class FortuneCookie
|
304
|
-
include Evolvable
|
305
268
|
|
306
|
-
|
307
|
-
EYE_COLORS = ['blue', 'brown', 'gray', 'green']
|
308
|
-
|
309
|
-
FORTUNES = ['You will prosper',
|
310
|
-
'You will endure hardship',
|
311
|
-
'You are about to eat a crisp and sugary cookie']
|
269
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Evolution)
|
312
270
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
271
|
+
## Selection
|
272
|
+
The selection object assumes that a population's evolvables have already
|
273
|
+
been sorted by the evaluation object. It selects "parent" evolvables to
|
274
|
+
undergo combination and thereby produce the next generation of evolvables.
|
317
275
|
|
318
|
-
|
319
|
-
|
320
|
-
eye_color = find_eater_eye_color
|
321
|
-
fortune = @genes[[hair_color, eye_color]]
|
322
|
-
eater.give_fortune(fortune)
|
323
|
-
sleep 2.weeks
|
324
|
-
eater.how_true?(fortune)
|
325
|
-
end
|
326
|
-
end
|
327
|
-
```
|
276
|
+
Only two evolvables are selected as parents for each generation by default.
|
277
|
+
The selection size is configurable.
|
328
278
|
|
329
|
-
In this not-at-all-contrived example, ```Evolvable.combine_dimensions([HAIR_COLOR, EYE_COLORS])``` returns
|
330
279
|
|
331
280
|
```ruby
|
332
|
-
|
281
|
+
# TODO: Show how to add/change population's selection object
|
333
282
|
```
|
334
283
|
|
335
|
-
which is useful for composing genes made up of various dimensions and accessing gene values by these dimensions in the ```#fitness``` and ```.evolvable_evaluate!(objects)``` methods.
|
336
284
|
|
337
|
-
```Evolvable.combine_dimensions(dimensions)``` can take an array containing any number of arrays as an argument. One item from each given array will be in each output array and the item's index will be the same as the index of the argument array it belongs to. All combinations of items from the various arrays that follow this rule will be returned as arrays. The number of output arrays is equal to the product of multiplying the sizes of each given array. This method was difficult to write as was this description. I'd be really interested to see other people's implementations :)
|
338
285
|
|
339
|
-
|
286
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Selection)
|
340
287
|
|
341
|
-
|
288
|
+
## Combination
|
289
|
+
Combination generates new evolvable instances by combining the genes of selected instances.
|
290
|
+
You can think of it as a mixing of parent genes from one generation to
|
291
|
+
produce the next generation.
|
342
292
|
|
343
|
-
|
293
|
+
You may choose from a selection of combination objects or implement your own.
|
294
|
+
The default combination object is `Evolvable::GeneCombination`.
|
344
295
|
|
345
|
-
Add this line to your application's Gemfile:
|
346
296
|
|
347
|
-
|
348
|
-
gem 'evolvable'
|
349
|
-
```
|
297
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Combination)
|
350
298
|
|
351
|
-
|
299
|
+
## Mutation
|
300
|
+
Mutation serves the role of increasing genetic variation. When an evolvable
|
301
|
+
undergoes a mutation, one or more of its genes are replaced by newly
|
302
|
+
initialized ones. In effect, a gene mutation invokes a new random outcome
|
303
|
+
from the genetic search space.
|
352
304
|
|
353
|
-
|
305
|
+
Mutation frequency is configurable using the `probability` and `rate`
|
306
|
+
parameters.
|
354
307
|
|
355
|
-
Or install it yourself as:
|
356
308
|
|
357
|
-
|
309
|
+
```ruby
|
310
|
+
# Show how to initialize/assign population with a specific mutation object
|
311
|
+
```
|
358
312
|
|
359
|
-
## Development
|
360
313
|
|
361
|
-
|
314
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Mutation)
|
362
315
|
|
363
|
-
|
316
|
+
## Search Space
|
317
|
+
The search space encapsulates the range of possible genes
|
318
|
+
for a particular evolvable. You can think of it as the boundaries of
|
319
|
+
genetic variation. It is configured via the
|
320
|
+
[.search_space](#evolvableclasssearch_space) method that you define
|
321
|
+
on your evolvable class. It's used by populations to initialize
|
322
|
+
new evolvables.
|
364
323
|
|
365
|
-
|
324
|
+
Evolvable provides flexibility in how you define your search space.
|
325
|
+
The below example implementations for `.search_space` produce the
|
326
|
+
exact same search space for the
|
327
|
+
[Hello World](https://github.com/mattruzicka/evolvable#hello-world)
|
328
|
+
demo program. The different styles arguably vary in suitability for
|
329
|
+
different contexts, perhaps depending on how programs are loaded and
|
330
|
+
the number of different gene types.
|
366
331
|
|
367
|
-
I have a general sense of how I want to move forward on these features, but feel free to message me with ideas or implementations.
|
368
332
|
|
369
|
-
|
333
|
+
```ruby
|
334
|
+
# All 9 of these example definitions are equivalent
|
335
|
+
|
336
|
+
# Hash syntax
|
337
|
+
{ chars: { type: 'CharGene', max_count: 100 } }
|
338
|
+
{ chars: { type: 'CharGene', min_count: 1, max_count: 100 } }
|
339
|
+
{ chars: { type: 'CharGene', count: 1..100 } }
|
340
|
+
|
341
|
+
# Array of arrays syntax
|
342
|
+
[[:chars, 'CharGene', 1..100]]
|
343
|
+
[['chars', 'CharGene', 1..100]]
|
344
|
+
[['CharGene', 1..100]]
|
345
|
+
|
346
|
+
# A single array works when there's only one type of gene
|
347
|
+
['CharGene', 1..100]
|
348
|
+
[:chars, 'CharGene', 1..100]
|
349
|
+
['chars', 'CharGene', 1..100]
|
350
|
+
```
|
351
|
+
|
352
|
+
|
353
|
+
[Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/SearchSpace)
|
370
354
|
|
371
355
|
## Contributing
|
372
356
|
|
373
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
357
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mattruzicka/evolvable.
|
358
|
+
|
359
|
+
If you're interested in contributing, but don't know where to get started, message me on twitter at [@mattruzicka](https://twitter.com/mattruzicka).
|