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.
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
+