genetic_framework 0.0.1
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.
- data/genetic.rb +136 -0
- metadata +65 -0
data/genetic.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
def random_element
|
4
|
+
self[Random.rand(self.size)]
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class GenePool
|
9
|
+
#TODO: Elite children, custom crossover point (min point at which cross begins)
|
10
|
+
class Spawn
|
11
|
+
|
12
|
+
attr_reader :string, :fitness
|
13
|
+
|
14
|
+
def initialize(fitness, string)
|
15
|
+
@fitness = fitness
|
16
|
+
@string = string
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(other)
|
20
|
+
return self.fitness <=> other.fitness
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(gen_size, encoding_map, mutation_prob, cull_threshold = nil, elite = 0, initial_gen = nil, &fitness)
|
26
|
+
@gen_size = gen_size
|
27
|
+
@encoding_map = encoding_map
|
28
|
+
@mutation_prob = mutation_prob
|
29
|
+
@cull_threshold = cull_threshold
|
30
|
+
@elite = elite
|
31
|
+
@fitness = fitness
|
32
|
+
if !initial_gen
|
33
|
+
@gen = initialize_gen.map {|str| Spawn.new(yield(str), str)}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :gen_size, :cull_threshold, :mutation_prob, :elite
|
38
|
+
attr_reader :gen, :encoding_map
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
#creates a string encoding based on the character map
|
43
|
+
#randomly selects a char for each position
|
44
|
+
def build_spawn
|
45
|
+
spawn = []
|
46
|
+
@encoding_map.each do |position|
|
47
|
+
spawn << position.random_element
|
48
|
+
end
|
49
|
+
return spawn.join
|
50
|
+
end
|
51
|
+
|
52
|
+
#char_map should be an array with length identical to string encoding length
|
53
|
+
#and each position contains a list of chars possible for that position
|
54
|
+
def initialize_gen
|
55
|
+
pop = []
|
56
|
+
1.upto(@gen_size) do |spawn_num|
|
57
|
+
pop << build_spawn
|
58
|
+
end
|
59
|
+
return pop
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def random_sexual_selection
|
64
|
+
selected = []
|
65
|
+
candidates = []
|
66
|
+
|
67
|
+
#create a list of candidates who meet the requirements (all if there is no cull)
|
68
|
+
#a candidate shows up in the list once for each fitness level. This may be memory heavy,
|
69
|
+
#so TODO improve the selection based on fitness
|
70
|
+
strong = @gen.select {|spawn| !@cull_threshold or spawn.fitness >= @cull_threshold}
|
71
|
+
if strong.uniq.size < 2
|
72
|
+
pool = @gen
|
73
|
+
else
|
74
|
+
pool = strong
|
75
|
+
end
|
76
|
+
pool.each do |candidate|
|
77
|
+
1.upto(candidate.fitness + 1) do #if this is 0, you have significant problems without the +1
|
78
|
+
candidates << candidate
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#select from amount candidates a generation_size number of parents. Candidates can be selected
|
83
|
+
#more than once, and have a higher chance of being selected for a higher fitness score
|
84
|
+
1.upto(@gen_size) do
|
85
|
+
selected << candidates.random_element
|
86
|
+
end
|
87
|
+
return selected
|
88
|
+
end
|
89
|
+
|
90
|
+
#mates two parent strings to create two child strings
|
91
|
+
def sexually_reproduce(parent1, parent2)
|
92
|
+
crossover = Random.rand(parent1.size)
|
93
|
+
child1 = parent1[0, crossover] + parent2[crossover, parent2.size]
|
94
|
+
child2 = parent2[0, crossover] + parent1[crossover, parent2.size]
|
95
|
+
return [child1, child2]
|
96
|
+
end
|
97
|
+
|
98
|
+
def mutate(string)
|
99
|
+
0.upto(string.size - 1) do |str_index|
|
100
|
+
if Random.rand < @mutation_prob
|
101
|
+
string[str_index] = @encoding_map[str_index].random_element
|
102
|
+
end
|
103
|
+
end
|
104
|
+
return string
|
105
|
+
end
|
106
|
+
|
107
|
+
public
|
108
|
+
|
109
|
+
#takes generation and evolves it one generation. if generation is nil, returns an initial, random generation
|
110
|
+
#the char_map is a list of lists - each position has a list of chars that are valid for that position.
|
111
|
+
#it tells us both how long each string is and what sort of encodings are valid for creation.
|
112
|
+
#Resulting generation is sorted according to fitness from least to greatest.
|
113
|
+
def evolve
|
114
|
+
#select spawn who will breed
|
115
|
+
parents = random_sexual_selection
|
116
|
+
#generation size should be even... or the last parent selected will be ignored
|
117
|
+
#breed new spawn
|
118
|
+
new_gen = []
|
119
|
+
|
120
|
+
if @elite
|
121
|
+
1.upto(@elite) do |offset|
|
122
|
+
if offset > @gen.size then break end
|
123
|
+
new_gen << @gen[-offset]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
start_count = new_gen.size
|
127
|
+
(start_count..@gen_size - 1).step(2) do |index|
|
128
|
+
new_gen += sexually_reproduce(parents[index].string, parents[index + 1].string).map do |child_string|
|
129
|
+
s = mutate(child_string)
|
130
|
+
Spawn.new(@fitness.call(s), s)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
@gen = new_gen.sort
|
135
|
+
end
|
136
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: genetic_framework
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Brian Fults
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-03-06 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email:
|
23
|
+
- xclite@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- genetic.rb
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: https://github.com/xclite/genetic_framework
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.7
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: Facilitates use of genetic evolution to find a solution to a defined problem.
|
64
|
+
test_files: []
|
65
|
+
|