evolvable 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 046213e8982bac4913f713e33942e9ef5ead7bd655871f995cbbe36797ce64f9
4
- data.tar.gz: 2980cf144a9ed46e3abea9d0d395ee582a1207002fa2ab88bb2c967580f3bd6e
3
+ metadata.gz: 7f1a067e6b2b71bbbfb1ddd936a14401262a6ec59dce36305500fcabebcde47e
4
+ data.tar.gz: 943d7a9efe2f50a387efac9f3f48115beacc0a9550d6ec14f0765f4a17d4e2a4
5
5
  SHA512:
6
- metadata.gz: 934eb04d489153638b00c002a2315e35140867f36ebfd9ecd8b3eb30fc0b6f249991a7b5f7b8f83a848bc3de9c36d9e060a89e16e7492bdf7d59f18d63aff405
7
- data.tar.gz: ed1cac7f1c24265c29488d2e58304fc81b57be1a2891b87ab4b229218da63f3961cc1ea9dfda926df7226b083db0f2262cbb343ec667f3aa603831887b70e0bd
6
+ metadata.gz: 64db467632fdfedbc7671229a80e8bad20ac8c5495ec731ec1111991bdbf50616e12091886c142fc44eaac5de1a5c4073e1c5f1ff8e5a304bc6272632e85411f
7
+ data.tar.gz: 3d8fd1aee8c638e521e6d5535cb3f00a5ca85064dec909790827dffad21ec9d9a13658b99d1ec0078e61d113356c2b950caefa84ffd86737f42f4ed8d71ee2a9
data/.gitignore CHANGED
@@ -9,5 +9,4 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
-
13
12
  .byebug_history
@@ -17,4 +17,4 @@ Naming/VariableNumber:
17
17
  EnforcedStyle: 'snake_case'
18
18
 
19
19
  Metrics/LineLength:
20
- Max: 90
20
+ Max: 90
@@ -0,0 +1,14 @@
1
+ # Release 1.0.0
2
+
3
+ Hello 🌎 🌍 🌏
4
+
5
+ Check out the [README](https://github.com/mattruzicka/evolvable/blob/master/README.md) for everything
6
+
7
+ ___
8
+
9
+
10
+ 🧬 🧬 🧬
11
+
12
+ Please [open an issue](https://github.com/mattruzicka/evolvable/issues/new) if you cannot find what you're looking for
13
+
14
+ 🧬 🧬 🧬
@@ -1,34 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- evolvable (0.1.0)
4
+ evolvable (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ast (2.4.0)
10
- byebug (11.0.0)
11
- diff-lcs (1.3)
12
- jaro_winkler (1.5.2)
13
- parallel (1.13.0)
14
- parser (2.6.0.0)
15
- ast (~> 2.4.0)
9
+ ast (2.4.1)
10
+ byebug (11.1.3)
11
+ jaro_winkler (1.5.4)
12
+ parallel (1.19.2)
13
+ parser (2.7.1.4)
14
+ ast (~> 2.4.1)
16
15
  powerpack (0.1.2)
17
16
  rainbow (3.0.0)
18
- rake (10.5.0)
19
- rspec (3.8.0)
20
- rspec-core (~> 3.8.0)
21
- rspec-expectations (~> 3.8.0)
22
- rspec-mocks (~> 3.8.0)
23
- rspec-core (3.8.0)
24
- rspec-support (~> 3.8.0)
25
- rspec-expectations (3.8.2)
26
- diff-lcs (>= 1.2.0, < 2.0)
27
- rspec-support (~> 3.8.0)
28
- rspec-mocks (3.8.0)
29
- diff-lcs (>= 1.2.0, < 2.0)
30
- rspec-support (~> 3.8.0)
31
- rspec-support (3.8.0)
32
17
  rubocop (0.64.0)
33
18
  jaro_winkler (~> 1.5.1)
34
19
  parallel (~> 1.10)
@@ -37,7 +22,7 @@ GEM
37
22
  rainbow (>= 2.2.2, < 4.0)
38
23
  ruby-progressbar (~> 1.7)
39
24
  unicode-display_width (~> 1.4.0)
40
- ruby-progressbar (1.10.0)
25
+ ruby-progressbar (1.10.1)
41
26
  unicode-display_width (1.4.1)
42
27
 
43
28
  PLATFORMS
@@ -47,9 +32,7 @@ DEPENDENCIES
47
32
  bundler (~> 2.0)
48
33
  byebug (~> 11.0)
49
34
  evolvable!
50
- rake (~> 10.0)
51
- rspec (~> 3.0)
52
35
  rubocop (~> 0.64.0)
53
36
 
54
37
  BUNDLED WITH
55
- 2.0.1
38
+ 2.1.4
data/README.md CHANGED
@@ -1,373 +1,286 @@
1
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
+ A framework for building evolutionary behaviors in Ruby.
4
4
 
5
- ## Demos
5
+ [Evolutionary algorithms](https://en.wikipedia.org/wiki/Evolutionary_algorithm) build upon ideas such as natural selection, crossover, and mutation to construct relatively simple solutions to complex problems. This gem has been used to implement evolutionary behaviors for [visual, textual, and auditory experiences](https://projectpag.es/evolvable) as well as a variety of AI agents.
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
+ With a straightforward and extensible API, Evolvable aims to make building simple as well as complex evolutionary algorithms fun and relatively easy.
9
8
 
10
- ## Usage
9
+ ### The Evolvable Abstraction
10
+ Population objects are composed of instances that include the `Evolvable` module. Instances are composed of gene objects that include the `Evolvable::Gene` module. Evaluation and evolution objects are used by population objects to evolve your instances. An evaluation object has one goal object and the evolution object is composed of selection, crossover, and mutation objects by default.
11
11
 
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)
12
+ All classes exposed by Evolvable are prefixed with `Evolvable::` and can be configured, inherited, removed, and extended. You can also choose between various Evolvable implementations documented below or substitute your own. Don't worry if none of this made sense, this will become clearer as you continue reading.
22
13
 
23
- ### Getting Started
14
+ ## Installation
24
15
 
25
- To build an object with evolvable behavior, do the following:
16
+ Add `gem 'evolvable'` to your application's Gemfile and run `bundle install` or install it yourself with `gem install evolvable`
26
17
 
27
- 1. Add ```include Evolvable``` to your class
28
- 2. Define ```.evolvable_gene_pool``` ([documentation](#The-Gene-Pool))
29
- 3. Define ```#fitness``` ([documentation](#Fitness))
18
+ ## Getting Started
30
19
 
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:
20
+ After installing and requiring the "evolvable" Ruby gem:
32
21
 
33
- ```Ruby
34
- require 'evolvable'
22
+ 1. Include the `Evolvable` module in the class for the instances you want to evolve. (See [Configuration](#Configuration)).
23
+ 2. Implement `.gene_space`, define any gene classes referenced by it, and include the `Evolvable::Gene` module for each. (See [Genes](#Genes)).
24
+ 3. Implement `#value`. (See [Evaluation](#Evaluation)).
25
+ 4. Initialize a population and start evolving. (See [Populations](#Populations)).
35
26
 
36
- class Sentence
37
- include Evolvable
27
+ Visit the [Evolvable Strings Tutorial](https://github.com/mattruzicka/evolvable/wiki/Evolvable-Strings-Tutorial) to see these steps in action. It walks through a simplified implementation of the [evolve string](https://github.com/mattruzicka/evolve_string) command-line program. Here's the [example source code](https://github.com/mattruzicka/evolvable/blob/master/examples/evolvable_string.rb) for the tutorial.
38
28
 
39
- DICTIONARY = ('a'..'z').to_a << ' '
40
- DESIRED_WORDS = 'colorless green ideas sleep furiously'
29
+ If you’d like to quickly play around with an evolvable string Population object, you can do so by cloning this repo and running the command `bin/console` in this project's directory.
41
30
 
42
- def self.evolvable_gene_pool
43
- Array.new(DESIRED_WORDS.length) { |index| [index, DICTIONARY] }
31
+ ## Usage
32
+ - [Configuration](#Configuration)
33
+ - [Genes](#Genes)
34
+ - [Evaluation](#Evaluation)
35
+ - [Populations](#Populations)
36
+ - [Evolution](#Evolution)
37
+ - [Selection](#Selection)
38
+ - [Crossover](#Crossover)
39
+ - [Mutation](#Mutation)
40
+
41
+ ## Configuration
42
+
43
+ You'll need to define a class for the instances you want to evolve and include the `Evolvable` module. Let's say you want to evolve a melody. You might do something like this:
44
+
45
+ ```ruby
46
+ class Melody
47
+ include Evolvable
48
+
49
+ def self.gene_space
50
+ # Expected
44
51
  end
45
52
 
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
53
+ def value
54
+ # Required
52
55
  end
53
56
  end
54
57
  ```
55
58
 
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:
59
+ The `Evolvable` module exposes the following class and instance methods for you.
57
60
 
58
- ```ruby
59
- # To play with a more interactive version, check out https://github.com/mattruzicka/evolvable_sentence
61
+ #### .gene_space
60
62
 
61
- population = Sentence.evolvable_population
62
- object = population.strongest_object
63
+ You're expected to override this and return a gene space configuration hash or GeneSpace object.
63
64
 
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
65
+ Here's a sample definition for the melody class defined above. It configures each instance to have 16 note genes and 1 instrument gene.
71
66
 
67
+ ```ruby
68
+ def self.gene_space
69
+ { instrument: { type: 'InstrumentGene', count: 1 },
70
+ notes: { type: 'NoteGene', count: 16 } }
71
+ end
72
72
  ```
73
73
 
74
- The ```Evolvable::Population#evolve!``` method evolves the population from one generation to the next. It accepts two optional keyword arguments:
74
+ See the section on [Genes](#genes) for more details.
75
75
 
76
- ```ruby
77
- { generations_count: 1, fitness_goal: nil }
78
- ```
76
+ #### .new_population(keyword_args = {})
79
77
 
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.
78
+ Initializes a new population. Example: `population = Melody.new_population(size: 100)`
81
79
 
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:
80
+ Accepts the same arguments as `Population.new`
83
81
 
84
- - ```evaluate_objects!```
85
- - ```select_objects!```
86
- - ```crossover_objects!```
87
- - ```mutate_objects!```
82
+ #### .new_instance(population: nil, genes: [], population_index: nil)
88
83
 
89
- ### The Gene Pool
84
+ Initializes a new instance. Accepts a population object, an array of gene objects, and the instance's population index.
90
85
 
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:
86
+ This method is useful for re-initializing instances and populations that have been saved.
92
87
 
93
- ```ruby
94
- class DualingPianos
95
- NOTES = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B']
88
+ _It is not recommended that you override this method_ as it is used by Evolvable internals. If you need to customize how your instances are initialized you can override either of the following two "initialize_instance" methods.
96
89
 
97
- def self.evolvable_gene_pool
98
- [[:piano_1, NOTES],
99
- [:piano_2, NOTES]]
100
- end
101
- end
102
- ```
90
+ #### .initialize_instance
103
91
 
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.
92
+ The default implementation simply delegates to `.new` and is useful for instances with custom initialize methods.
105
93
 
106
- ```ruby
107
- require 'evolvable'
94
+ #### #initialize_instance
108
95
 
109
- class ComplexBot
110
- include Evolvable
96
+ Runs after Evolvable finishes building your instance. It's useful for stuff like implementing custom gene initialization logic. For example, the Evolvable Strings web demo (coming soon) uses it to read from a "length gene" and add or remove "char genes" accordingly.
111
97
 
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] }
115
- end
98
+ #### #population, #population=
116
99
 
117
- def self.evolvable_genes_count
118
- 5_000
119
- end
120
- end
100
+ The population object being used to evolve this instance.
121
101
 
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
- ```
102
+ #### #genes, #genes=
127
103
 
128
- ### Fitness
104
+ An array of all an instance's genes. You can find specific types of genes with the following two methods.
129
105
 
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:
106
+ #### #find_genes(key)
131
107
 
132
- ```ruby
133
- class TetrisBot
134
- alias fitness tetris_score
135
- end
136
- ```
108
+ Returns an array of genes that have the given key. Gene keys are defined in the [.gene_space](####.gene_space) method. In the Melody example above, the key for the note genes would be `:notes`. The following would return an array of them: `note_genes = melody.find_genes(:notes)`
137
109
 
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.
110
+ #### #find_gene(key)
139
111
 
140
- ```ruby
141
- class TetrisArtBot
142
- def fitness
143
- artist_friend_ratings.sum / artist_friend_ratings.count
144
- end
145
- end
146
- ```
112
+ Returns the first gene with the given key. In the Melody example above, the instrument gene has the key `:instrument` so we might write something like: `instrument_gene = melody.find_gene(instrument)`
147
113
 
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.
114
+ #### #population_index, #population_index=
149
115
 
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:
116
+ Returns an instance's population index - an integer representing the order in which it was initialized in a population. It's the most basic way to distinguish instances in a population.
151
117
 
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
118
+ #### #value
160
119
 
161
- alias fitness win_count
162
- end
163
- ```
120
+ It is required that you implement this method. It is used when evaluating instances before undergoing evolution.
164
121
 
165
- ### Evolvable Population
122
+ Technically, this method can return any object that implements Ruby's [Comparable](https://ruby-doc.org/core-2.7.1/Comparable.html). See the section on [Evaluation](#Evaluation) for details.
166
123
 
167
- The Evolvable::Population class can be initialized in two ways
124
+ #### Evolvable Hooks
168
125
 
169
- 1. ```EvolvableBot.evolvable_population```
170
- 2. ```Evolvable::Population.new(evolvable_class: EvolvableBot)```
126
+ The following class methods can be overridden in order to hook into a population's evolutions. The hooks run for each evolution in the following order:
171
127
 
128
+ **.before_evaluation(population)**
172
129
 
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
- ```
130
+ **.before_evolution(population)**
184
131
 
185
- ```.evolvable_population(args = {})``` merges any given args with with any keyword arguments defined in ```.evolvable_population_attrs``` Example:
132
+ **.after_evolution(population)**
186
133
 
187
- ```ruby
188
- class Plant
189
- include Evolvable
134
+ To use our Melody example from above, you could override the `.before_evolution` method to play the best melody from each generation with something like this:
190
135
 
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 }
136
+ ```ruby
137
+ class Melody
138
+ def self.before_evolution(population)
139
+ best_melody = population.best_instance
140
+ best_melody.play
196
141
  end
197
142
 
198
- def self.evolvable_gene_pool
199
- [[:leaf_count, (1..100).to_a],
200
- [:root_type, ['fibrous', 'taproot']]]
143
+ def play
144
+ note_genes = melody.find_genes(:notes)
145
+ note_values = note_genes.map(&:value)
146
+ find_gene(:instrument).play(note_values)
201
147
  end
202
148
  end
203
-
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
209
149
  ```
210
150
 
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:
151
+ ## Genes
152
+
153
+ Instances rely on gene objects to compose behaviors. In other words, a gene can be thought of as an object that in some way affects the behavior of an instance. They are used to encapsulate a "sample space" and return a sample outcome when accessed.
154
+
155
+ #### The Evolvable::Gene module
156
+
157
+ Gene objects must include the `Evolvable::Gene` module which enables them to undergo evolutionary operations such as crossover and mutation.
158
+
159
+ To continue with the melody example, we might encode a NoteGene like so:
212
160
 
213
161
  ```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
162
+ class NoteGene
163
+ include Evolvable::Gene
164
+
165
+ NOTES = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B']
166
+
167
+ def value
168
+ @value ||= NOTES.sample
220
169
  end
221
170
  end
222
171
  ```
172
+ Here, the "sample space" for the NoteGene class has twelve notes, but each individual object will have only one note. The note is randomly chosen when the "value" method is invoked for the first time.
223
173
 
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:
225
-
226
- ```ruby
227
- friend.name == "#{name} #{population.generation_count}.#{object_index}" # => "ImaginaryFriend 0.11"
228
- ```
174
+ _It is important that the data for a particular gene never change._ Ruby's or-equals operator `||=` is super useful for memoizing gene attributes like this. It is used above to randomly pick a note only once and return the same note for the lifetime of the object.
229
175
 
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:
176
+ A melody instance with multiple note genes might use the `NoteGene#value` method to compose the notes of its melody like so: `melody.find_genes(:note).map(&:value)`. Let's keep going with this example and implement the `InstrumentGene` too:
231
177
 
232
178
  ```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
- ```
242
-
243
- ### Monitoring Progress
179
+ class InstrumentGene
180
+ include Evolvable::Gene
244
181
 
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.
182
+ def instrument_class
183
+ @instrument_class ||= [Guitar, Synth, Trumpet].sample
184
+ end
246
185
 
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.
186
+ def volume
187
+ @volume ||= rand(1..100)
188
+ end
248
189
 
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
190
+ def play(notes)
191
+ instrument_class.play(notes: notes, volume: volume)
256
192
  end
257
- population.log_progress = true
193
+ end
258
194
  ```
259
195
 
260
- Hooks can also be used to monitor progress.
196
+ You can model your sample space however you like. Ruby's [Array](https://ruby-doc.org/core-2.7.1/Array.html), [Hash](https://ruby-doc.org/core-2.7.1/Hash.html), [Range](https://ruby-doc.org/core-2.7.1/Range.html), and [Random](https://ruby-doc.org/core-2.7.1/Random.html) classes may be useful. This InstrumentGene implementation has 300 possible outcomes (3 instruments * 100 volumes) and uses Ruby's Array, Range, and Random classes.
261
197
 
262
- ### Hooks
198
+ Now that its genes are implemented, a melody instance can use them:
263
199
 
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!```
200
+ ```ruby
201
+ class Melody
202
+ include Evolvable
265
203
 
266
- ```.evolvable_before_evolution(population)```
204
+ # ...
267
205
 
268
- ```.evolvable_after_select(population)```
206
+ def play
207
+ note_genes = melody.find_genes(:notes)
208
+ note_values = note_genes.map(&:value)
209
+ find_gene(:instrument).play(note_values)
210
+ end
211
+ end
212
+ ```
269
213
 
270
- ```.evolvable_after_evolution(population)```
214
+ In this way, instances can express behaviors via genes and even orchestrate interactions between them. Genes can also interact with each other during an instance's initialization process via the [#initialize_instance](#initialize_instance-1) method
271
215
 
272
- ### Mutation Objects
216
+ #### The Evolvable::GeneSpace object
273
217
 
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:
218
+ TODO
275
219
 
276
- ```ruby
277
- mutation = Evolvable::Mutation.new(rate: 0.05)
278
- population = Evolvable::Population.new(mutation: mutation)
279
- population.evolve!
280
- ```
220
+ ## Evaluation
281
221
 
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)
222
+ TODO
283
223
 
284
- ### Crossover Objects
224
+ #### The Evolvable::Evaluation object
285
225
 
286
- The [Evolvable::Crossover](https://github.com/mattruzicka/evolvable/blob/master/lib/evolvable/crossover.rb) class defines the default crossover implementation.
226
+ TODO
287
227
 
288
- ```ruby
289
- crossover = Evolvable::Crossover.new
290
- population = Evolvable::Population.new(crossover: crossover)
291
- population.evolve!
292
- ```
228
+ #### The Evolvable::Goal::Maximize object
293
229
 
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)
230
+ TODO
295
231
 
296
- ### Helper Methods
232
+ #### The Evolvable::Goal::Minimize object
297
233
 
298
- ```Evolvable.combine_dimensions(dimensions)```
234
+ TODO
299
235
 
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.
236
+ #### The Evolvable::Goal::Equalize object
301
237
 
302
- ```ruby
303
- class FortuneCookie
304
- include Evolvable
238
+ TODO
305
239
 
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']
240
+ ## Populations
312
241
 
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
242
+ TODO
317
243
 
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
- ```
244
+ #### The Evolvable::Population object
328
245
 
329
- In this not-at-all-contrived example, ```Evolvable.combine_dimensions([HAIR_COLOR, EYE_COLORS])``` returns
246
+ TODO
330
247
 
331
- ```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"]]
333
- ```
248
+ ## Evolution
334
249
 
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.
250
+ TODO
336
251
 
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 :)
252
+ #### The Evolvable::Evolution object
338
253
 
339
- ### Configuration
254
+ TODO
340
255
 
341
- TODO: Make logger configurable and make it smarter about picking a default
256
+ ## Selection
342
257
 
343
- ## Installation
258
+ TODO
344
259
 
345
- Add this line to your application's Gemfile:
260
+ #### The Evolvable::Selection object
346
261
 
347
- ```ruby
348
- gem 'evolvable'
349
- ```
262
+ TODO
350
263
 
351
- And then execute:
264
+ ## Crossover
352
265
 
353
- $ bundle
266
+ TODO
354
267
 
355
- Or install it yourself as:
268
+ #### The Evolvable::GeneCrossover object
356
269
 
357
- $ gem install evolvable
270
+ TODO
358
271
 
359
- ## Development
272
+ #### The Evolvable::UniformCrossover object
360
273
 
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.
274
+ TODO
362
275
 
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.
276
+ #### The Evolvable::PointCrossover object
364
277
 
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".
278
+ TODO
366
279
 
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.
280
+ ## Mutation
368
281
 
369
- If you see a TODO in this README, feel free to do it :)
282
+ TODO
370
283
 
371
- ## Contributing
284
+ #### The Evolvable::Mutation object
372
285
 
373
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/evolvable.
286
+ TODO