evolvable 0.1.2 → 0.1.3

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: 833387bfa4abc039a65f181a9a31cf582075d8e90a7bef1f483ca4a9fa53c699
4
- data.tar.gz: f2b32861840ae9c7fe89f5353ab535b2966da0592a1c619f01adecc2be1629e6
3
+ metadata.gz: 046213e8982bac4913f713e33942e9ef5ead7bd655871f995cbbe36797ce64f9
4
+ data.tar.gz: 2980cf144a9ed46e3abea9d0d395ee582a1207002fa2ab88bb2c967580f3bd6e
5
5
  SHA512:
6
- metadata.gz: 667ab941471ab3b949ec631b2d94115667447d43736502862439c574e1c8e1d8231cd37c64aea2bc2900ea750054a17e3e58d7064c030250c5e0792c4019f9b5
7
- data.tar.gz: a5c40439db48c8205bd12749ecf103e70266079ecde14eedbc45b180a10154b9c4e025a323411ff6c79b28603a744b4f59f9f5516785e90d0e3fccef9cc8ad05
6
+ metadata.gz: 934eb04d489153638b00c002a2315e35140867f36ebfd9ecd8b3eb30fc0b6f249991a7b5f7b8f83a848bc3de9c36d9e060a89e16e7492bdf7d59f18d63aff405
7
+ data.tar.gz: ed1cac7f1c24265c29488d2e58304fc81b57be1a2891b87ab4b229218da63f3961cc1ea9dfda926df7226b083db0f2262cbb343ec667f3aa603831887b70e0bd
data/README.md CHANGED
@@ -1,24 +1,34 @@
1
1
  # Evolvable
2
2
 
3
- Genetic algorithms mimic biological processes such as natural selection, crossover, and mutation to model evolutionary behaviors in code. The "evolvable" gem can add evolutionary behavior to any Ruby object.
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.
4
4
 
5
- If you're interested in seeing exactly how the "evolvable" gem evolves populations of Ruby objects, I suggest opening up the [Population](https://github.com/mattruzicka/evolvable/blob/master/lib/evolvable/population.rb) class, specifically check out the following methods:
5
+ ## Demos
6
6
 
7
- - evolve!
8
- - evaluate_objects!
9
- - select_objects!
10
- - crossover_objects!
11
- - mutate_objects!
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
12
9
 
13
10
  ## Usage
14
11
 
15
- To introduce evolvable behavior to any Ruby object, do the following:
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)
22
+
23
+ ### Getting Started
16
24
 
17
- 1. Include the Evolvable module
18
- 2. Define a class method named "evolvable_gene_pool"
19
- 3. Define an instance method named "fitness"
25
+ To build an object with evolvable behavior, do the following:
20
26
 
21
- For example, let's say we want to make a text-to-speech command evolve from saying random nonsense to saying whatever you desire.
27
+ 1. Add ```include Evolvable``` to your class
28
+ 2. Define ```.evolvable_gene_pool``` ([documentation](#The-Gene-Pool))
29
+ 3. Define ```#fitness``` ([documentation](#Fitness))
30
+
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:
22
32
 
23
33
  ```Ruby
24
34
  require 'evolvable'
@@ -40,80 +50,295 @@ class Sentence
40
50
  end
41
51
  score
42
52
  end
43
-
44
- def words
45
- @genes.values.join
46
- end
47
53
  end
48
54
  ```
49
55
 
50
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:
51
57
 
52
58
  ```ruby
59
+ # To play with a more interactive version, check out https://github.com/mattruzicka/evolvable_sentence
60
+
53
61
  population = Sentence.evolvable_population
54
62
  object = population.strongest_object
55
63
 
56
64
  until object.fitness == Sentence::DESIRED_WORDS.length
57
- puts object.words
58
- system("say #{object.words}")
65
+ words = object.genes.values.join
66
+ puts words
67
+ system("say #{words}")
59
68
  population.evolve!(fitness_goal: Sentence::DESIRED_WORDS.length)
60
69
  object = population.strongest_object
61
70
  end
71
+
72
+ ```
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 }
62
78
  ```
63
79
 
64
- To play with a more interactive version, check out https://github.com/mattruzicka/evolvable_sentence
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.
81
+
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:
83
+
84
+ - ```evaluate_objects!```
85
+ - ```select_objects!```
86
+ - ```crossover_objects!```
87
+ - ```mutate_objects!```
65
88
 
66
89
  ### The Gene Pool
67
90
 
68
- TODO: add descriptions and examples for following
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:
92
+
93
+ ```ruby
94
+ class DualingPianos
95
+ NOTES = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B']
96
+
97
+ def self.evolvable_gene_pool
98
+ [[:piano_1, NOTES],
99
+ [:piano_2, NOTES]]
100
+ end
101
+ end
102
+ ```
103
+
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.
105
+
106
+ ```ruby
107
+ require 'evolvable'
108
+
109
+ class ComplexBot
110
+ include Evolvable
111
+
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
116
+
117
+ def self.evolvable_genes_count
118
+ 5_000
119
+ end
120
+ end
69
121
 
70
- *.evolvable_gene_pool*
71
- *.evolvable_genes_count*
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
+ ```
72
127
 
73
128
  ### Fitness
74
129
 
75
- TODO: add description and example
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:
131
+
132
+ ```ruby
133
+ class TetrisBot
134
+ alias fitness tetris_score
135
+ end
136
+ ```
137
+
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.
139
+
140
+ ```ruby
141
+ class TetrisArtBot
142
+ def fitness
143
+ artist_friend_ratings.sum / artist_friend_ratings.count
144
+ end
145
+ end
146
+ ```
147
+
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.
149
+
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:
151
+
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
160
+
161
+ alias fitness win_count
162
+ end
163
+ ```
164
+
165
+ ### Evolvable Population
166
+
167
+ The Evolvable::Population class can be initialized in two ways
168
+
169
+ 1. ```EvolvableBot.evolvable_population```
170
+ 2. ```Evolvable::Population.new(evolvable_class: EvolvableBot)```
171
+
172
+
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
+ ```
184
+
185
+ ```.evolvable_population(args = {})``` merges any given args with with any keyword arguments defined in ```.evolvable_population_attrs``` Example:
186
+
187
+ ```ruby
188
+ class Plant
189
+ include Evolvable
190
+
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 }
196
+ end
197
+
198
+ def self.evolvable_gene_pool
199
+ [[:leaf_count, (1..100).to_a],
200
+ [:root_type, ['fibrous', 'taproot']]]
201
+ end
202
+ end
76
203
 
77
- ### Custom Evolvable Class Methods
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
+ ```
210
+
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:
78
212
 
79
- TODO: add descriptions and example implementations for the following
213
+ ```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
222
+ ```
80
223
 
81
- *.evolvable_evaluate!(objects)*
82
- *.evolvable_population(args = {})*
83
- *.evolvable_population_attrs*
84
- *.evolvable_initialize(genes, population, object_index)*
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
+ ```
229
+
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:
231
+
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
+ ```
242
+
243
+ ### Monitoring Progress
244
+
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.
246
+
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.
248
+
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
+ ```
259
+
260
+ Hooks can also be used to monitor progress.
85
261
 
86
262
  ### Hooks
87
263
 
88
- TODO: add description
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!```
265
+
266
+ ```.evolvable_before_evolution(population)```
89
267
 
90
- *.evolvable_before_evolution(population)*
91
- *.evolvable_after_select(population)*
92
- *.evolvable_after_evolution(population)*
268
+ ```.evolvable_after_select(population)```
93
269
 
94
- ### Custom Mutations
270
+ ```.evolvable_after_evolution(population)```
95
271
 
96
- TODO: Show how to define and use a custom mutation object
272
+ ### Mutation Objects
97
273
 
98
- ### Custom Crossover
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:
99
275
 
100
- TODO: Show how to define and use a custom crossover object
276
+ ```ruby
277
+ mutation = Evolvable::Mutation.new(rate: 0.05)
278
+ population = Evolvable::Population.new(mutation: mutation)
279
+ population.evolve!
280
+ ```
281
+
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
+
284
+ ### Crossover Objects
285
+
286
+ The [Evolvable::Crossover](https://github.com/mattruzicka/evolvable/blob/master/lib/evolvable/crossover.rb) class defines the default crossover implementation.
287
+
288
+ ```ruby
289
+ crossover = Evolvable::Crossover.new
290
+ population = Evolvable::Population.new(crossover: crossover)
291
+ population.evolve!
292
+ ```
293
+
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)
101
295
 
102
296
  ### Helper Methods
103
297
 
104
- TODO: add description
298
+ ```Evolvable.combine_dimensions(dimensions)```
105
299
 
106
- *Evolvable.combine_dimensions(dimensions)*
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.
107
301
 
108
- ### Configuration
302
+ ```ruby
303
+ class FortuneCookie
304
+ include Evolvable
109
305
 
110
- TODO: Make logger configurable and make it smarter about picking a default
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']
111
312
 
112
- ## Demos
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
113
317
 
114
- - https://github.com/mattruzicka/evolvable_sentence - A more interactive version of the evolvable sentence code above.
115
- - https://github.com/mattruzicka/evolvable_equation - Evolves an equation of a specified length to evaluate to a given number.
116
- - More demos to come.
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
+ ```
328
+
329
+ In this not-at-all-contrived example, ```Evolvable.combine_dimensions([HAIR_COLOR, EYE_COLORS])``` returns
330
+
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
+ ```
334
+
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
+
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
+
339
+ ### Configuration
340
+
341
+ TODO: Make logger configurable and make it smarter about picking a default
117
342
 
118
343
  ## Installation
119
344
 
@@ -141,6 +366,8 @@ I would also like to make more obvious how an evolvable object's genes can influ
141
366
 
142
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.
143
368
 
369
+ If you see a TODO in this README, feel free to do it :)
370
+
144
371
  ## Contributing
145
372
 
146
373
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/evolvable.
data/bin/console CHANGED
@@ -10,5 +10,41 @@ require 'evolvable'
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
+
14
+ class BandMember
15
+ include Evolvable
16
+
17
+ SYNTHS = [:synth_1, :synth_2]
18
+
19
+ # SYNTH_OPTIONS = { synth_1: [[:cutoff, (1..100).to_a],
20
+ # [:reverb: (1..5).to_a]],
21
+ # synth_2: []}
22
+
23
+ SAMPLES = [:sample_1, :sample_2]
24
+
25
+ # SAMPLE_OPTIONS = { sample_1: [[:cutoff, (1..100).to_a],
26
+ # [:reverb: (1..5).to_a]],
27
+ # sample_2: []}
28
+
29
+ def self.evolvable_gene_pool
30
+
31
+ end
32
+
33
+ def base.evolvable_random_genes(count = nil)
34
+ gene_pool = evolvable_gene_pool_cache
35
+ count ||= evolvable_genes_count
36
+ gene_pool = gene_pool.sample(count) if count < gene_pool.size
37
+ genes = {}
38
+ gene_pool.each { |name, potentials| genes[name] = potentials.sample }
39
+ genes
40
+ end
41
+
42
+ def fitness
43
+ 0
44
+ end
45
+ end
46
+
47
+
48
+
13
49
  require 'irb'
14
50
  IRB.start(__FILE__)
@@ -6,6 +6,8 @@ module Evolvable
6
6
  @rate = rate
7
7
  end
8
8
 
9
+ attr_accessor :rate
10
+
9
11
  def_delegators :@evolvable_class,
10
12
  :evolvable_genes_count,
11
13
  :evolvable_gene_pool_size,
@@ -22,13 +22,14 @@ module Evolvable
22
22
  assign_objects(objects)
23
23
  end
24
24
 
25
- attr_reader :evolvable_class,
26
- :size,
27
- :selection_count,
28
- :crossover,
29
- :mutation,
30
- :generation_count,
31
- :objects
25
+ attr_accessor :evolvable_class,
26
+ :size,
27
+ :selection_count,
28
+ :crossover,
29
+ :mutation,
30
+ :generation_count,
31
+ :log_progress,
32
+ :objects
32
33
 
33
34
  def_delegators :@evolvable_class,
34
35
  :evolvable_evaluate!,
@@ -44,7 +45,7 @@ module Evolvable
44
45
  @generation_count += 1
45
46
  evolvable_before_evolution(self)
46
47
  evaluate_objects!
47
- log_progress if @log_progress
48
+ log_evolvable_progress if log_progress
48
49
  break if fitness_goal_met?
49
50
 
50
51
  select_objects!
@@ -68,7 +69,7 @@ module Evolvable
68
69
  end
69
70
  end
70
71
 
71
- def log_progress
72
+ def log_evolvable_progress
72
73
  @objects.last.evolvable_progress
73
74
  end
74
75
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evolvable
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evolvable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Ruzicka
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-10 00:00:00.000000000 Z
11
+ date: 2019-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler