evolvable 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +118 -7
- data/evolvable.gemspec +1 -2
- data/lib/evolvable/{callbacks.rb → hooks.rb} +1 -1
- data/lib/evolvable/mutation.rb +10 -10
- data/lib/evolvable/population.rb +28 -24
- data/lib/evolvable/version.rb +1 -1
- data/lib/evolvable.rb +10 -10
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 833387bfa4abc039a65f181a9a31cf582075d8e90a7bef1f483ca4a9fa53c699
|
4
|
+
data.tar.gz: f2b32861840ae9c7fe89f5353ab535b2966da0592a1c619f01adecc2be1629e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 667ab941471ab3b949ec631b2d94115667447d43736502862439c574e1c8e1d8231cd37c64aea2bc2900ea750054a17e3e58d7064c030250c5e0792c4019f9b5
|
7
|
+
data.tar.gz: a5c40439db48c8205bd12749ecf103e70266079ecde14eedbc45b180a10154b9c4e025a323411ff6c79b28603a744b4f59f9f5516785e90d0e3fccef9cc8ad05
|
data/README.md
CHANGED
@@ -1,8 +1,119 @@
|
|
1
1
|
# Evolvable
|
2
2
|
|
3
|
-
|
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.
|
4
4
|
|
5
|
-
|
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:
|
6
|
+
|
7
|
+
- evolve!
|
8
|
+
- evaluate_objects!
|
9
|
+
- select_objects!
|
10
|
+
- crossover_objects!
|
11
|
+
- mutate_objects!
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
To introduce evolvable behavior to any Ruby object, do the following:
|
16
|
+
|
17
|
+
1. Include the Evolvable module
|
18
|
+
2. Define a class method named "evolvable_gene_pool"
|
19
|
+
3. Define an instance method named "fitness"
|
20
|
+
|
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.
|
22
|
+
|
23
|
+
```Ruby
|
24
|
+
require 'evolvable'
|
25
|
+
|
26
|
+
class Sentence
|
27
|
+
include Evolvable
|
28
|
+
|
29
|
+
DICTIONARY = ('a'..'z').to_a << ' '
|
30
|
+
DESIRED_WORDS = 'colorless green ideas sleep furiously'
|
31
|
+
|
32
|
+
def self.evolvable_gene_pool
|
33
|
+
Array.new(DESIRED_WORDS.length) { |index| [index, DICTIONARY] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def fitness
|
37
|
+
score = 0
|
38
|
+
@genes.values.each_with_index do |value, index|
|
39
|
+
score += 1 if value == DESIRED_WORDS[index]
|
40
|
+
end
|
41
|
+
score
|
42
|
+
end
|
43
|
+
|
44
|
+
def words
|
45
|
+
@genes.values.join
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
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
|
+
|
52
|
+
```ruby
|
53
|
+
population = Sentence.evolvable_population
|
54
|
+
object = population.strongest_object
|
55
|
+
|
56
|
+
until object.fitness == Sentence::DESIRED_WORDS.length
|
57
|
+
puts object.words
|
58
|
+
system("say #{object.words}")
|
59
|
+
population.evolve!(fitness_goal: Sentence::DESIRED_WORDS.length)
|
60
|
+
object = population.strongest_object
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
To play with a more interactive version, check out https://github.com/mattruzicka/evolvable_sentence
|
65
|
+
|
66
|
+
### The Gene Pool
|
67
|
+
|
68
|
+
TODO: add descriptions and examples for following
|
69
|
+
|
70
|
+
*.evolvable_gene_pool*
|
71
|
+
*.evolvable_genes_count*
|
72
|
+
|
73
|
+
### Fitness
|
74
|
+
|
75
|
+
TODO: add description and example
|
76
|
+
|
77
|
+
### Custom Evolvable Class Methods
|
78
|
+
|
79
|
+
TODO: add descriptions and example implementations for the following
|
80
|
+
|
81
|
+
*.evolvable_evaluate!(objects)*
|
82
|
+
*.evolvable_population(args = {})*
|
83
|
+
*.evolvable_population_attrs*
|
84
|
+
*.evolvable_initialize(genes, population, object_index)*
|
85
|
+
|
86
|
+
### Hooks
|
87
|
+
|
88
|
+
TODO: add description
|
89
|
+
|
90
|
+
*.evolvable_before_evolution(population)*
|
91
|
+
*.evolvable_after_select(population)*
|
92
|
+
*.evolvable_after_evolution(population)*
|
93
|
+
|
94
|
+
### Custom Mutations
|
95
|
+
|
96
|
+
TODO: Show how to define and use a custom mutation object
|
97
|
+
|
98
|
+
### Custom Crossover
|
99
|
+
|
100
|
+
TODO: Show how to define and use a custom crossover object
|
101
|
+
|
102
|
+
### Helper Methods
|
103
|
+
|
104
|
+
TODO: add description
|
105
|
+
|
106
|
+
*Evolvable.combine_dimensions(dimensions)*
|
107
|
+
|
108
|
+
### Configuration
|
109
|
+
|
110
|
+
TODO: Make logger configurable and make it smarter about picking a default
|
111
|
+
|
112
|
+
## Demos
|
113
|
+
|
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.
|
6
117
|
|
7
118
|
## Installation
|
8
119
|
|
@@ -20,15 +131,15 @@ Or install it yourself as:
|
|
20
131
|
|
21
132
|
$ gem install evolvable
|
22
133
|
|
23
|
-
##
|
134
|
+
## Development
|
24
135
|
|
25
|
-
|
136
|
+
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.
|
26
137
|
|
27
|
-
|
138
|
+
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.
|
28
139
|
|
29
|
-
|
140
|
+
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".
|
30
141
|
|
31
|
-
|
142
|
+
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.
|
32
143
|
|
33
144
|
## Contributing
|
34
145
|
|
data/evolvable.gemspec
CHANGED
@@ -7,8 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.version = Evolvable::VERSION
|
8
8
|
spec.authors = ['Matt Ruzicka']
|
9
9
|
|
10
|
-
spec.summary = '
|
11
|
-
spec.summary = 'Make Ruby objects evolvable with genetic algorithms'
|
10
|
+
spec.summary = 'Add evolutionary behavior to any Ruby object'
|
12
11
|
spec.homepage = 'https://github.com/mattruzicka/evolvable'
|
13
12
|
spec.license = 'MIT'
|
14
13
|
|
data/lib/evolvable/mutation.rb
CHANGED
@@ -11,18 +11,18 @@ module Evolvable
|
|
11
11
|
:evolvable_gene_pool_size,
|
12
12
|
:evolvable_random_genes
|
13
13
|
|
14
|
-
def call!(
|
15
|
-
@evolvable_class =
|
16
|
-
mutations_count = find_mutations_count(
|
14
|
+
def call!(objects)
|
15
|
+
@evolvable_class = objects.first.class
|
16
|
+
mutations_count = find_mutations_count(objects)
|
17
17
|
return if mutations_count.zero?
|
18
18
|
|
19
19
|
mutant_genes = generate_mutant_genes(mutations_count)
|
20
|
-
|
21
|
-
|
20
|
+
object_mutations_count = mutations_count / objects.count
|
21
|
+
object_mutations_count = 1 if object_mutations_count.zero?
|
22
22
|
|
23
|
-
mutant_genes.each_slice(
|
24
|
-
|
25
|
-
genes =
|
23
|
+
mutant_genes.each_slice(object_mutations_count).with_index do |m_genes, index|
|
24
|
+
object = objects[index] || objects.sample
|
25
|
+
genes = object.genes
|
26
26
|
genes.merge!(m_genes.to_h)
|
27
27
|
rm_genes_count = genes.count - evolvable_genes_count
|
28
28
|
genes.keys.sample(rm_genes_count).each { |key| genes.delete(key) }
|
@@ -40,10 +40,10 @@ module Evolvable
|
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
-
def find_mutations_count(
|
43
|
+
def find_mutations_count(objects)
|
44
44
|
return 0 if @rate.zero?
|
45
45
|
|
46
|
-
count = (
|
46
|
+
count = (objects.count * evolvable_genes_count * @rate)
|
47
47
|
return count.to_i if count >= 1
|
48
48
|
|
49
49
|
rand <= count ? 1 : 0
|
data/lib/evolvable/population.rb
CHANGED
@@ -11,7 +11,7 @@ module Evolvable
|
|
11
11
|
mutation: Mutation.new,
|
12
12
|
generation_count: 0,
|
13
13
|
log_progress: false,
|
14
|
-
|
14
|
+
objects: [])
|
15
15
|
@evolvable_class = evolvable_class
|
16
16
|
@size = size
|
17
17
|
@selection_count = selection_count
|
@@ -19,7 +19,7 @@ module Evolvable
|
|
19
19
|
@mutation = mutation
|
20
20
|
@generation_count = generation_count
|
21
21
|
@log_progress = log_progress
|
22
|
-
|
22
|
+
assign_objects(objects)
|
23
23
|
end
|
24
24
|
|
25
25
|
attr_reader :evolvable_class,
|
@@ -28,7 +28,7 @@ module Evolvable
|
|
28
28
|
:crossover,
|
29
29
|
:mutation,
|
30
30
|
:generation_count,
|
31
|
-
:
|
31
|
+
:objects
|
32
32
|
|
33
33
|
def_delegators :@evolvable_class,
|
34
34
|
:evolvable_evaluate!,
|
@@ -43,49 +43,53 @@ module Evolvable
|
|
43
43
|
generations_count.times do
|
44
44
|
@generation_count += 1
|
45
45
|
evolvable_before_evolution(self)
|
46
|
-
|
46
|
+
evaluate_objects!
|
47
47
|
log_progress if @log_progress
|
48
48
|
break if fitness_goal_met?
|
49
49
|
|
50
|
-
|
50
|
+
select_objects!
|
51
51
|
evolvable_after_select(self)
|
52
|
-
|
53
|
-
|
52
|
+
crossover_objects!
|
53
|
+
mutate_objects!
|
54
54
|
evolvable_after_evolution(self)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
58
|
+
def strongest_object
|
59
|
+
objects.max_by(&:fitness)
|
60
|
+
end
|
61
|
+
|
62
|
+
def evaluate_objects!
|
63
|
+
evolvable_evaluate!(@objects)
|
60
64
|
if @fitness_goal
|
61
|
-
@
|
65
|
+
@objects.sort_by! { |i| -(i.fitness - @fitness_goal).abs }
|
62
66
|
else
|
63
|
-
@
|
67
|
+
@objects.sort_by!(&:fitness)
|
64
68
|
end
|
65
69
|
end
|
66
70
|
|
67
71
|
def log_progress
|
68
|
-
@
|
72
|
+
@objects.last.evolvable_progress
|
69
73
|
end
|
70
74
|
|
71
75
|
def fitness_goal_met?
|
72
|
-
@fitness_goal && @
|
76
|
+
@fitness_goal && @objects.last.fitness >= @fitness_goal
|
73
77
|
end
|
74
78
|
|
75
|
-
def
|
76
|
-
@
|
79
|
+
def select_objects!
|
80
|
+
@objects.slice!(0..-1 - @selection_count)
|
77
81
|
end
|
78
82
|
|
79
|
-
def
|
80
|
-
parent_genes = @
|
83
|
+
def crossover_objects!
|
84
|
+
parent_genes = @objects.map(&:genes)
|
81
85
|
offspring_genes = @crossover.call(parent_genes, @size)
|
82
|
-
@
|
86
|
+
@objects = offspring_genes.map.with_index do |genes, i|
|
83
87
|
evolvable_initialize(genes, self, i)
|
84
88
|
end
|
85
89
|
end
|
86
90
|
|
87
|
-
def
|
88
|
-
@mutation.call!(@
|
91
|
+
def mutate_objects!
|
92
|
+
@mutation.call!(@objects)
|
89
93
|
end
|
90
94
|
|
91
95
|
def inspect
|
@@ -103,11 +107,11 @@ module Evolvable
|
|
103
107
|
|
104
108
|
private
|
105
109
|
|
106
|
-
def
|
107
|
-
@
|
108
|
-
(@size -
|
110
|
+
def assign_objects(objects)
|
111
|
+
@objects = objects || []
|
112
|
+
(@size - objects.count).times do |n|
|
109
113
|
genes = evolvable_random_genes
|
110
|
-
@
|
114
|
+
@objects << evolvable_initialize(genes, self, n)
|
111
115
|
end
|
112
116
|
end
|
113
117
|
end
|
data/lib/evolvable/version.rb
CHANGED
data/lib/evolvable.rb
CHANGED
@@ -8,14 +8,14 @@ require 'evolvable/population'
|
|
8
8
|
require 'evolvable/crossover'
|
9
9
|
require 'evolvable/mutation'
|
10
10
|
require 'evolvable/helper_methods'
|
11
|
-
require 'evolvable/
|
11
|
+
require 'evolvable/hooks'
|
12
12
|
require 'evolvable/errors/not_implemented'
|
13
13
|
|
14
14
|
module Evolvable
|
15
15
|
extend HelperMethods
|
16
16
|
|
17
17
|
def self.included(base)
|
18
|
-
base.extend
|
18
|
+
base.extend Hooks
|
19
19
|
|
20
20
|
def base.evolvable_gene_pool
|
21
21
|
raise Errors::NotImplemented, __method__
|
@@ -25,14 +25,7 @@ module Evolvable
|
|
25
25
|
evolvable_gene_pool_size
|
26
26
|
end
|
27
27
|
|
28
|
-
def base.evolvable_evaluate!(
|
29
|
-
|
30
|
-
def base.evolvable_initialize(genes, population, _individual_index)
|
31
|
-
evolvable = new
|
32
|
-
evolvable.genes = genes
|
33
|
-
evolvable.population = population
|
34
|
-
evolvable
|
35
|
-
end
|
28
|
+
def base.evolvable_evaluate!(_objects); end
|
36
29
|
|
37
30
|
def base.evolvable_population_attrs
|
38
31
|
{}
|
@@ -45,6 +38,13 @@ module Evolvable
|
|
45
38
|
Population.new(args)
|
46
39
|
end
|
47
40
|
|
41
|
+
def base.evolvable_initialize(genes, population, _object_index)
|
42
|
+
evolvable = new
|
43
|
+
evolvable.genes = genes
|
44
|
+
evolvable.population = population
|
45
|
+
evolvable
|
46
|
+
end
|
47
|
+
|
48
48
|
def base.evolvable_gene_pool_cache
|
49
49
|
@evolvable_gene_pool_cache ||= evolvable_gene_pool
|
50
50
|
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.
|
4
|
+
version: 0.1.2
|
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-
|
11
|
+
date: 2019-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,10 +68,10 @@ files:
|
|
68
68
|
- bin/setup
|
69
69
|
- evolvable.gemspec
|
70
70
|
- lib/evolvable.rb
|
71
|
-
- lib/evolvable/callbacks.rb
|
72
71
|
- lib/evolvable/crossover.rb
|
73
72
|
- lib/evolvable/errors/not_implemented.rb
|
74
73
|
- lib/evolvable/helper_methods.rb
|
74
|
+
- lib/evolvable/hooks.rb
|
75
75
|
- lib/evolvable/mutation.rb
|
76
76
|
- lib/evolvable/population.rb
|
77
77
|
- lib/evolvable/version.rb
|
@@ -100,5 +100,5 @@ requirements: []
|
|
100
100
|
rubygems_version: 3.0.3
|
101
101
|
signing_key:
|
102
102
|
specification_version: 4
|
103
|
-
summary:
|
103
|
+
summary: Add evolutionary behavior to any Ruby object
|
104
104
|
test_files: []
|