evolvable 0.1.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.yardopts +4 -0
  4. data/CHANGELOG.md +63 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +39 -39
  7. data/LICENSE +21 -0
  8. data/README.md +234 -248
  9. data/README_YARD.md +237 -0
  10. data/bin/console +20 -43
  11. data/evolvable.gemspec +2 -3
  12. data/examples/ascii_art.rb +62 -0
  13. data/examples/ascii_gene.rb +9 -0
  14. data/examples/hello_world.rb +91 -0
  15. data/examples/images/diagram.png +0 -0
  16. data/examples/stickman.rb +77 -0
  17. data/exe/hello +16 -0
  18. data/lib/evolvable/count_gene.rb +42 -0
  19. data/lib/evolvable/equalize_goal.rb +29 -0
  20. data/lib/evolvable/error/undefined_method.rb +7 -0
  21. data/lib/evolvable/evaluation.rb +74 -0
  22. data/lib/evolvable/evolution.rb +58 -0
  23. data/lib/evolvable/gene.rb +67 -0
  24. data/lib/evolvable/gene_combination.rb +69 -0
  25. data/lib/evolvable/genome.rb +86 -0
  26. data/lib/evolvable/goal.rb +52 -0
  27. data/lib/evolvable/maximize_goal.rb +30 -0
  28. data/lib/evolvable/minimize_goal.rb +29 -0
  29. data/lib/evolvable/mutation.rb +71 -42
  30. data/lib/evolvable/point_crossover.rb +71 -0
  31. data/lib/evolvable/population.rb +202 -83
  32. data/lib/evolvable/rigid_count_gene.rb +17 -0
  33. data/lib/evolvable/search_space.rb +181 -0
  34. data/lib/evolvable/selection.rb +45 -0
  35. data/lib/evolvable/serializer.rb +21 -0
  36. data/lib/evolvable/uniform_crossover.rb +42 -0
  37. data/lib/evolvable/version.rb +1 -1
  38. data/lib/evolvable.rb +203 -56
  39. metadata +46 -24
  40. data/.rubocop.yml +0 -20
  41. data/lib/evolvable/crossover.rb +0 -35
  42. data/lib/evolvable/errors/not_implemented.rb +0 -5
  43. data/lib/evolvable/helper_methods.rb +0 -45
  44. data/lib/evolvable/hooks.rb +0 -9
data/README.md CHANGED
@@ -1,373 +1,359 @@
1
- # Evolvable
1
+ # Evolvable 🦎
2
2
 
3
- Genetic algorithms mimic biological processes such as natural selection, crossover, and mutation to model evolutionary behaviors in code. This gem can add evolutionary behaviors to any Ruby object.
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
- ## Demos
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
- - [Evolvable Sentence](https://github.com/mattruzicka/evolvable_sentence) - An interactive version of the evolvable sentence example in "Getting Started"
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
- - [Getting Started](#Getting-Started)
13
- - [The Gene Pool](#The-Gene-Pool)
14
- - [Fitness](#Fitness)
15
- - [Evolvable Population](#Evolvable-Population)
16
- - [Monitoring Progress](#Monitoring-Progress)
17
- - [Hooks](#Hooks)
18
- - [Mutation Objects](#Mutation-Objects)
19
- - [Crossover Objects](#Crossover-Objects)
20
- - [Helper Methods](#Helper-Methods)
21
- - [Configuration](#Configuration)
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
- To build an object with evolvable behavior, do the following:
24
+ ## Installation
26
25
 
27
- 1. Add ```include Evolvable``` to your class
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
- As an example, let's make a text-to-speech command evolve from saying random nonsense to whatever you desire. We'll start by defining a Sentence class and doing the three steps above:
28
+ ## Getting Started
32
29
 
33
- ```Ruby
34
- require 'evolvable'
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
- class Sentence
37
- include Evolvable
35
+ ### Implementation Steps
38
36
 
39
- DICTIONARY = ('a'..'z').to_a << ' '
40
- DESIRED_WORDS = 'colorless green ideas sleep furiously'
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
- Now let's listen to our computer evolve its speech. The following code assumes your system has a text-to-speech command named "say" installed. Run this code in irb:
43
+ To demonstrate these steps, we'll look at the [Hello World](#) example program.
57
44
 
58
- ```ruby
59
- # To play with a more interactive version, check out https://github.com/mattruzicka/evolvable_sentence
45
+ ### Hello World
60
46
 
61
- population = Sentence.evolvable_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
- until object.fitness == Sentence::DESIRED_WORDS.length
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
- The ```Evolvable::Population#evolve!``` method evolves the population from one generation to the next. It accepts two optional keyword arguments:
75
-
76
- ```ruby
77
- { generations_count: 1, fitness_goal: nil }
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
- Specifying the **fitness_goal** is useful when there's a known fitness you're trying to achieve. We use it in the evolvable sentence example above. If you want your population to keep evolving until it hits a particular fitness goal, set **generations_count** to a large number. The **generations_count** keyword argument tells ```#evolve!``` how many times to run.
70
+ ### Step 1
81
71
 
82
- If you're interested in seeing exactly how ```Evolvable::Population#evolve!``` works, Open up the [Population](https://github.com/mattruzicka/evolvable/blob/master/lib/evolvable/population.rb) class and check out the following methods:
72
+ Let's begin by defining a `HelloWorld` class and have it **include the `Evolvable` module**.
83
73
 
84
- - ```evaluate_objects!```
85
- - ```select_objects!```
86
- - ```crossover_objects!```
87
- - ```mutate_objects!```
74
+ ```ruby
75
+ class HelloWorld
76
+ include Evolvable
77
+ end
78
+ ```
88
79
 
89
- ### The Gene Pool
80
+ ### Step 2
90
81
 
91
- Currently, the gene pool needs to be an array of arrays. Each inner array contains a gene name and an array of possible values for the gene. Expect future releases to make defining the gene pool more straightforward. Check out [Development](#Development) below for details. Until then, here's an example of a simple ```.evolvable_gene_pool``` definition for evolvable dueling pianos:
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 DualingPianos
95
- NOTES = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B']
85
+ class HelloWorld
86
+ include Evolvable
96
87
 
97
- def self.evolvable_gene_pool
98
- [[:piano_1, NOTES],
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 number of potential genes in the gene pool can be extremely large. For these cases, "Evolvable" makes it possible for objects to contain only a subset of genes with the ```.evolvable_genes_count``` method. Any gene defined in the ```.evolvable_gene_pool``` can still manifest during mutations. In this way, we can limit the gene size of particular objects without limiting the genes available to a population.
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
- ```ruby
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
- class ComplexBot
110
- include Evolvable
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
- def self.evolvable_gene_pool
113
- potential_gene_values = (1..10_000).to_a
114
- Array.new(1_000_000) { |n| [n, potential_gene_values] }
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 self.evolvable_genes_count
118
- 5_000
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
- ### Fitness
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
- The ```#fitness``` method is responsible for measuring how well an object performs. It returns a value or "score" which influences whether an object will be selected to "breed" the next generation of objects. How fitness is defined depends on what you're trying to achieve. If you're trying to break a high score record in Tetris, for example, then the fitness function for your tetris bot might simply return its tetris score:
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
- ```ruby
133
- class TetrisBot
134
- alias fitness tetris_score
135
- end
136
- ```
120
+ ### Step 3
137
121
 
138
- If, however, your aim is to construct a bot that plays Tetris in an aesthetically pleasing way, maybe you'd define fitness by surveying your artist friends.
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
- ```ruby
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
- The result of ```#fitness``` can be any object that includes the ([Comparable](https://ruby-doc.org/core-2.6.1/Comparable.html)) mixin from Ruby and implements the ```<=>``` method. Many Ruby classes such as String and Integer have default implementations.
126
+ ### Step 4
149
127
 
150
- You may want to evaluate a whole generation of objects at once. For example, maybe you want each of your bots to play a game against each other and base your fitness score off their win records. For this case, use ```.evolvable_evaluate!(objects)``` like so:
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
- ```ruby
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
- alias fitness win_count
162
- end
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 Evolvable::Population class can be initialized in two ways
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
- 1. ```EvolvableBot.evolvable_population```
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
- Both ways accept the following keyword arguments as defined by ```Evolvable::Population#initialize```
174
- ```ruby
175
- { evolvable_class:, # Required. Inferred if you use the .evolvable_population method
176
- size: 20, # The number of objects in each generation
177
- selection_count: 2, # The number of objects to be selected as parents for crossover
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
- ```.evolvable_population(args = {})``` merges any given args with with any keyword arguments defined in ```.evolvable_population_attrs``` Example:
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 Plant
157
+ class HelloWorld
189
158
  include Evolvable
190
159
 
191
- def self.evolvable_population_attrs
192
- { size: 100,
193
- selection_count: 5,
194
- mutation: Evolvable::Mutation.new(rate: 0.3),
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
- def self.evolvable_gene_pool
199
- [[:leaf_count, (1..100).to_a],
200
- [:root_type, ['fibrous', 'taproot']]]
166
+ # ...
167
+
168
+ def to_s
169
+ @to_s ||= genes.join
201
170
  end
202
- end
203
171
 
204
- population = Plant.evolvable_population(log_progress: true)
205
- population.size # => 100
206
- population.selection_count # => 5
207
- population.mutation.rate # => 0.3
208
- population.log_progress # => true
172
+ # ...
173
+ end
209
174
  ```
210
175
 
211
- The ```.evolvable_initialize(genes, population, object_index)``` is used by Evolvable::Population to initialize new objects. You can override it to control how your objects are initialized. Make sure to assign the passed in **genes** to your initialized objects. Here's an example implementation for when you want your imaginary friends to have names:
176
+ Finally we can **evolve the population with the `Evolvable::Population#evolve` instance method**.
212
177
 
213
178
  ```ruby
214
- class ImaginaryFriend
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
- The third argument to ```.evolvable_initialize``` is the index of the object in the population before being evaluated. It is useful when you what to give your objects more utilitarian names:
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
- ```ruby
227
- friend.name == "#{name} #{population.generation_count}.#{object_index}" # => "ImaginaryFriend 0.11"
228
- ```
184
+ ## Concepts
229
185
 
230
- A time when you'd want to use Evolvable::Population initializer instead of ```EvolvableBot.evolvable_population``` is when you're re-initializing a population. For example, maybe you want to continue evolving a population of chat bots that you had previously saved to a database:
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
- ```ruby
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
- ### Monitoring Progress
190
+ ![Concept Map](https://github.com/mattruzicka/evolvable/raw/main/examples/images/diagram.png)
244
191
 
245
- ```#evolvable_progress``` is used by ```Evolvable::Population#evolve!``` to log the progress of the "strongest object" in each generation. That is, the object with the best fitness score. It runs just after objects are evaluated and the ```Evolvable::Population#strongest_object``` can be determined. ```Evolvable::Population#log_progress``` must equal true in order for the result of the ```#evolvable_progress``` to be logged.
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
- In the [evolvable sentence demo](https://github.com/mattruzicka/evolvable_sentence), ```evolvable_progress``` is implemented in order to output the strongest object's generation count, fitness score, and words. In this example, we also use the "say" text-to-speech command to pronounce the words.
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
- ```Ruby
250
- class Sentence
251
- def evolvable_progress
252
- words = @genes.values.join
253
- puts "Generation: #{population.generation_count} | Fitness: #{fitness} | #{words}"
254
- system("say #{words}") if say?
255
- end
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
- ### Hooks
209
+ ```ruby
210
+ # This gene generates a random hexidecimal color code for use by evolvables.
263
211
 
264
- You can define any the following class method hooks on any Evolvable class. They run during the evolution of each generation in ```Evolvable::Population#evolve!```
212
+ require 'securerandom'
265
213
 
266
- ```.evolvable_before_evolution(population)```
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
- ```.evolvable_after_evolution(population)```
224
+ [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Gene)
271
225
 
272
- ### Mutation Objects
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
- mutation = Evolvable::Mutation.new(rate: 0.05)
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
- ### Crossover Objects
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
- crossover = Evolvable::Crossover.new
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
- Any Ruby object that implements ```#call(parent_genes, offspring_count)``` can be used as a crossover 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)
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
- ```Evolvable.combine_dimensions(dimensions)```
260
+ [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Evaluation)
299
261
 
300
- This is helpful when you want to create, for lack of a better word, "multidimensional genes". In the following example, we want our fortune cookie to evolve fortunes based on its eater's hair and eye color because fortune cookies understand things that we aren't capable of knowing.
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
- HAIR_COLORS = ['black', 'blond', 'brown', 'gray', 'red', 'white']
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
- def self.evolvable_gene_pool
314
- gene_names_array = Evolvable.combine_dimensions([HAIR_COLORS, EYE_COLORS])
315
- gene_names_array.map! { |gene_name| [gene_name, FORTUNES] }
316
- end
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
- def fitness
319
- hair_color = find_eater_hair_color
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
- [["auburn", "blue"], ["auburn", "brown"], ["auburn", "gray"], ["auburn", "green"], ["black", "blue"], ["black", "brown"], ["black", "gray"], ["black", "green"], ["blond", "blue"], ["blond", "brown"], ["blond", "gray"], ["blond", "green"], ["brown", "blue"], ["brown", "brown"], ["brown", "gray"], ["brown", "green"], ["gray", "blue"], ["gray", "brown"], ["gray", "gray"], ["gray", "green"], ["red", "blue"], ["red", "brown"], ["red", "gray"], ["red", "green"], ["white", "blue"], ["white", "brown"], ["white", "gray"], ["white", "green"]]
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
- ### Configuration
286
+ [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Selection)
340
287
 
341
- TODO: Make logger configurable and make it smarter about picking a default
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
- ## Installation
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
- ```ruby
348
- gem 'evolvable'
349
- ```
297
+ [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Combination)
350
298
 
351
- And then execute:
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
- $ bundle
305
+ Mutation frequency is configurable using the `probability` and `rate`
306
+ parameters.
354
307
 
355
- Or install it yourself as:
356
308
 
357
- $ gem install evolvable
309
+ ```ruby
310
+ # Show how to initialize/assign population with a specific mutation object
311
+ ```
358
312
 
359
- ## Development
360
313
 
361
- After checking out the repo, run `bundle install` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
314
+ [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Mutation)
362
315
 
363
- I am looking to both simplify how genes are defined as well as support more complex gene types by way of a new Gene class. Currently, genes are represented as an array of arrays or hashes depending on the context. This will likely change.
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
- I would also like to make more obvious how an evolvable object's genes can influence its behavior/fitness with well-defined pattern for gene expression, probably via an instance method on a gene called "express".
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
- If you see a TODO in this README, feel free to do it :)
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/[USERNAME]/evolvable.
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).