genetic_framework 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/genetic.rb +136 -0
  2. 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
+