evolvable 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|