answer-factory 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/LICENSE.txt +21 -0
  2. data/Rakefile +29 -0
  3. data/Thorfile +79 -0
  4. data/VERSION +1 -0
  5. data/_spikes/old_vs_new_dominated_by?.rb +45 -0
  6. data/config/database.yml +9 -0
  7. data/lib/answer-factory.rb +14 -0
  8. data/lib/answers/answer.rb +126 -0
  9. data/lib/answers/batch.rb +49 -0
  10. data/lib/factories/factory.rb +53 -0
  11. data/lib/factories/workstation.rb +33 -0
  12. data/lib/operators/basic_operators.rb +240 -0
  13. data/lib/operators/evaluators.rb +113 -0
  14. data/lib/operators/samplers_and_selectors.rb +131 -0
  15. data/pkg/nudgegp-0.0.1.gem +0 -0
  16. data/readme.md +29 -0
  17. data/spec/answer_spec.rb +412 -0
  18. data/spec/batch_spec.rb +98 -0
  19. data/spec/config_spec.rb +94 -0
  20. data/spec/factories/factory_spec.rb +86 -0
  21. data/spec/factories/workstation_spec.rb +139 -0
  22. data/spec/operators/any_one_sampler_spec.rb +39 -0
  23. data/spec/operators/dominated_quantile_spec.rb +111 -0
  24. data/spec/operators/duplicate_genomes_spec.rb +35 -0
  25. data/spec/operators/evaluators/program_point_evaluator_spec.rb +43 -0
  26. data/spec/operators/evaluators/test_case_evaluator_spec.rb +129 -0
  27. data/spec/operators/infrastructure_spec.rb +45 -0
  28. data/spec/operators/most_dominated_subset_spec.rb +47 -0
  29. data/spec/operators/nondominated_subset_spec.rb +103 -0
  30. data/spec/operators/pointCrossover_spec.rb +60 -0
  31. data/spec/operators/pointDeletion_spec.rb +62 -0
  32. data/spec/operators/pointMutation_spec.rb +77 -0
  33. data/spec/operators/random_guess_spec.rb +77 -0
  34. data/spec/operators/resample_and_clone_spec.rb +60 -0
  35. data/spec/operators/resample_values_spec.rb +135 -0
  36. data/spec/operators/uniformBackboneCrossover_spec.rb +67 -0
  37. data/spec/spec_helper.rb +14 -0
  38. metadata +201 -0
@@ -0,0 +1,240 @@
1
+ #encoding: utf-8
2
+ module NudgeGP
3
+
4
+ # Abstract class that from which specific SearchOperator subclasses inherit initialization
5
+
6
+ class SearchOperator
7
+ attr_accessor :incoming_options
8
+
9
+ def initialize(options={})
10
+ @incoming_options = options
11
+ end
12
+ end
13
+
14
+
15
+
16
+
17
+ class RandomGuessOperator < SearchOperator
18
+
19
+ # returns an Array of random Answers
20
+ #
21
+ # the first (optional) parameter specifies how many to make, and defaults to 1
22
+ # the second (also optional) parameter is a hash that
23
+ # can temporarily override those set in the initialization
24
+ #
25
+ # For example, if
26
+ # <tt>myRandomGuesser = RandomGuessOperator.new(:randomIntegerLowerBound => -90000)</tt>
27
+ #
28
+ # [<tt>myRandomGuesser.generate()</tt>]
29
+ # produces a list of 1 Answer, and if it has any IntType samples they will be in [-90000,100]
30
+ # (since the default +:randomIntegerLowerBound+ is 100)
31
+ # [<tt>myRandomGuesser.generate(1,:randomIntegerLowerBound => 0)</tt>]
32
+ # makes one Answer whose IntType samples (if any) will be between [0,100]
33
+
34
+ def generate(howMany = 1, overridden_options ={})
35
+ result = Batch.new
36
+ howMany.times do
37
+ newGenome = CodeType.any_value(@incoming_options.merge(overridden_options))
38
+ newDude = Answer.new(newGenome, progress:0)
39
+ result << newDude
40
+ end
41
+ result
42
+ end
43
+ end
44
+
45
+
46
+
47
+
48
+ class ResampleAndCloneOperator < SearchOperator
49
+
50
+ # returns an Array of clones of Answers randomly selected from the crowd passed in
51
+ #
52
+ # the first (required) parameter is an Array of Answers
53
+ # the second (optional) parameter is how many samples to take, and defaults to 1
54
+ #
55
+ # For example, if
56
+ # <tt>@currentPopulation = [a list of 300 Answers]</tt> and
57
+ # <tt>myRandomSampler = ResampleAndCloneOperator.new(@currentPopulation)</tt>
58
+ # [<tt>myRandomSampler.generate()</tt>]
59
+ # produces a list of 1 Answer, which is a clone of somebody from <tt>@currentPopulation</tt>
60
+ # [<tt>myRandomGuesser.generate(11)</tt>]
61
+ # returns a list of 11 Answers cloned from <tt>@currentPopulation</tt>,
62
+ # possibly including repeats
63
+
64
+ def generate(crowd, howMany = 1)
65
+ result = Batch.new
66
+ howMany.times do
67
+ donor = crowd.sample
68
+ clone = Answer.new(donor.blueprint, progress:donor.progress + 1)
69
+ result << clone
70
+ end
71
+ return result
72
+ end
73
+ end
74
+
75
+
76
+
77
+
78
+ class ResampleValuesOperator < SearchOperator
79
+
80
+ def generate(crowd, howManyCopies = 1, overridden_options = {})
81
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
82
+
83
+ result = Batch.new
84
+ regenerating_options = @incoming_options.merge(overridden_options)
85
+ crowd.each do |dude|
86
+ howManyCopies.times do
87
+ wildtype_program = dude.program
88
+ starting_footnotes = wildtype_program.footnote_section.split( /^(?=«)/ )
89
+ breaker = /^«([a-zA-Z][a-zA-Z0-9_]*)»\s*(.*)\s*/m
90
+ type_value_pairs = starting_footnotes.collect {|fn| fn.match(breaker)[1..2]}
91
+
92
+ mutant_blueprint = wildtype_program.code_section
93
+
94
+ type_value_pairs.each do |pair|
95
+
96
+ begin
97
+ type_name = pair[0]
98
+ type_class = "#{type_name}_type".camelize.constantize
99
+ reduced_size = regenerating_options[:target_size_in_points] || rand(dude.points/2)
100
+ reduced_option = {target_size_in_points:reduced_size}
101
+ resampled_value = type_class.any_value(regenerating_options.merge(reduced_option)).to_s
102
+ rescue NameError
103
+ resampled_value = pair[1]
104
+ end
105
+ mutant_blueprint << "\n«#{pair[0].strip}» #{resampled_value.strip}"
106
+ end
107
+ mutant = Answer.new(mutant_blueprint, progress:dude.progress + 1)
108
+ result << mutant
109
+ end
110
+ end
111
+ return result
112
+ end
113
+ end
114
+
115
+
116
+
117
+
118
+ class UniformBackboneCrossoverOperator < SearchOperator
119
+
120
+ # Returns a Batch of new Answers whose programs are made by stitching together
121
+ # the programs of pairs of 'parents'. The incoming Batch is divided into pairs based on
122
+ # adjacency (modulo the Batch.length), one pair for each 'offspring' to be made. To make
123
+ # an offspring, the number of backbone program points is determined in each parent; 'backbone'
124
+ # refers to the number of branches directly within the root of the program, not the entire tree.
125
+ #
126
+ # To construct an offspring's program, program points are copied from the first parent with
127
+ # probability p, or the second parent with probability (1-p), for each point in the first
128
+ # parent's backbone. So if there are 13 and 6 points, respectively, the first six points are
129
+ # selected randomly, but the last 7 are copied from the first parent. If there are 8 and 11
130
+ # respectively, then the last 3 will be ignored from the second parent in any case.
131
+ #
132
+ # the first (required) parameter is an Array of Answers
133
+ # the second (optional) parameter is how many crossovers to make,
134
+ # which defaults to the number of Answers in the incoming Batch
135
+
136
+ def generate(crowd, howMany = crowd.length, prob = 0.5)
137
+ result = Batch.new
138
+ howMany.times do
139
+ where = rand(crowd.length)
140
+ mom = crowd[where]
141
+ dad = crowd[ (where+1) % crowd.length ]
142
+ mom_backbone_length = mom.program[1].contents.length
143
+ dad_backbone_length = dad.program[1].contents.length
144
+
145
+ baby_blueprint_parts = ["",""]
146
+ (0..mom_backbone_length-1).each do |backbone_point|
147
+ if rand() < prob
148
+ next_chunks = mom.program[1].contents[backbone_point].blueprint_parts || ["",""]
149
+ else
150
+ if backbone_point < dad_backbone_length
151
+ next_chunks = (dad.program[1].contents[backbone_point].blueprint_parts || ["", ""])
152
+ else
153
+ next_chunks = ["",""]
154
+ end
155
+ end
156
+ baby_blueprint_parts[0] << " #{next_chunks[0]}"
157
+ baby_blueprint_parts[1] << " \n#{next_chunks[1]}"
158
+ end
159
+ mom.program.unused_footnotes.each {|fn| baby_blueprint_parts[1] += "\n#{fn}"}
160
+
161
+ baby_blueprint = "block {#{baby_blueprint_parts[0]}} #{baby_blueprint_parts[1]}"
162
+ baby = Answer.new(baby_blueprint, progress:[mom.progress,dad.progress].max + 1)
163
+
164
+ result << baby
165
+ end
166
+ return result
167
+ end
168
+ end
169
+
170
+
171
+
172
+
173
+ class PointCrossoverOperator < SearchOperator
174
+ def generate(crowd, howManyBabies = 1)
175
+ raise(ArgumentError) if !crowd.kind_of?(Array)
176
+ raise(ArgumentError) if crowd.empty?
177
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
178
+
179
+ result = Batch.new
180
+ production = crowd.length*howManyBabies
181
+ production.times do
182
+ mom = crowd.sample
183
+ dad = crowd.sample
184
+ mom_receives = rand(mom.points) + 1
185
+ dad_donates = rand(dad.points) + 1
186
+
187
+ baby_blueprint = mom.replace_point_or_clone(mom_receives,dad.program[dad_donates])
188
+ baby = Answer.new(baby_blueprint,
189
+ progress:[mom.progress,dad.progress].max + 1)
190
+ result << baby
191
+ end
192
+ return result
193
+ end
194
+ end
195
+
196
+
197
+
198
+
199
+ class PointDeleteOperator < SearchOperator
200
+ def generate(crowd, howManyCopies = 1)
201
+ raise(ArgumentError) if !crowd.kind_of?(Array)
202
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
203
+
204
+ result = Batch.new
205
+ crowd.each do |dude|
206
+ howManyCopies.times do
207
+ where = rand(dude.points)+1
208
+ variant = dude.delete_point_or_clone(where)
209
+ baby = Answer.new(variant, progress:dude.progress + 1)
210
+ result << baby
211
+ end
212
+ end
213
+ return result
214
+ end
215
+ end
216
+
217
+
218
+
219
+
220
+ class PointMutationOperator < SearchOperator
221
+
222
+ def generate(crowd, howManyCopies = 1, overridden_options ={})
223
+ raise(ArgumentError) if !crowd.kind_of?(Array)
224
+ raise(ArgumentError) if crowd.empty?
225
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
226
+
227
+ result = Batch.new
228
+ crowd.each do |dude|
229
+ howManyCopies.times do
230
+ where = rand(dude.points)+1
231
+ newCode = CodeType.any_value(@incoming_options.merge(overridden_options))
232
+ variant = dude.replace_point_or_clone(where,newCode)
233
+ baby = Answer.new(variant, progress:dude.progress + 1)
234
+ result << baby
235
+ end
236
+ end
237
+ return result
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,113 @@
1
+ module NudgeGP
2
+
3
+ class Evaluator < SearchOperator
4
+ attr_accessor :name
5
+
6
+ def initialize(params = {})
7
+ raise(ArgumentError, "Evaluators must be initialized with names") if params[:name] == nil
8
+ @name = params[:name]
9
+ end
10
+ end
11
+
12
+
13
+
14
+
15
+
16
+ class ProgramPointEvaluator < Evaluator
17
+ def evaluate(batch)
18
+ raise(ArgumentError, "Can only evaluate a Batch of Answers") if !batch.kind_of?(Batch)
19
+ batch.each do |i|
20
+ if i.parses?
21
+ i.scores[@name] = i.program.points
22
+ else
23
+ raise(ArgumentError, "Program is not parseable")
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+
30
+
31
+
32
+
33
+ class TestCase
34
+ attr_accessor :bindings, :expectations, :gauges
35
+
36
+ def initialize(args = {})
37
+ @bindings = args[:bindings] || Hash.new
38
+ @expectations = args[:expectations] || Hash.new
39
+ @gauges = args[:gauges] || Hash.new
40
+
41
+ if (@expectations.keys - @gauges.keys).length > 0
42
+ raise ArgumentError, "One or more expectations have no defined gauge"
43
+ end
44
+ end
45
+ end
46
+
47
+
48
+
49
+
50
+
51
+ class TestCaseEvaluator < Evaluator
52
+ attr_accessor :interpreter_settings
53
+
54
+ def evaluate(batch, cases = [], params = {})
55
+ raise(ArgumentError, "Can only evaluate a Batch of Answers") if !batch.kind_of?(Batch)
56
+
57
+ instructions = params[:instructions] || Instruction.all_instructions
58
+ types = params[:types] || [IntType, BoolType, FloatType]
59
+ variable_names = params[:references] || []
60
+
61
+ batch.each do |dude|
62
+ if !params[:deterministic] || !dude.scores[@name]
63
+ score = 0
64
+ readings = {}
65
+ cases.each do |example|
66
+ difference = 0
67
+
68
+ # make an Interpreter
69
+ workspace = Interpreter.new("",
70
+ :instruction_names => instructions,
71
+ :type_names => types,
72
+ :references => variable_names)
73
+
74
+ # set up the program
75
+ workspace.reset(dude.blueprint)
76
+
77
+ # set up the bindings
78
+ example.bindings.each do |key,value|
79
+ workspace.bind_variable(key, value)
80
+ end
81
+
82
+ # run it
83
+ workspace.run
84
+
85
+ # apply the gauge(s) for each expectation
86
+ example.gauges.each do |variable_name,the_gauge|
87
+ readings[variable_name] = the_gauge.call(workspace)
88
+ end
89
+
90
+ # synthesize readings into a single scalar difference
91
+ # FIXME this should be a settable Proc
92
+ example.gauges.each do |variable_name,the_gauge|
93
+ begin
94
+ difference = (readings[variable_name].value - example.expectations[variable_name])
95
+ rescue
96
+ difference = 100000
97
+ end
98
+ end
99
+
100
+ score += difference.abs
101
+ end
102
+ # aggregate differences
103
+ dude.scores[@name] = score.to_f / cases.length
104
+
105
+ puts "#{score.to_f / cases.length}" if params[:feedback]
106
+ else
107
+ puts dude.scores[@name] if params[:feedback]
108
+ end
109
+ end
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,131 @@
1
+ module NudgeGP
2
+
3
+ class Sampler < SearchOperator
4
+ def initialize (params = {})
5
+ super
6
+ end
7
+
8
+ def all_known_criteria(crowd)
9
+ union = []
10
+ crowd.each do |dude|
11
+ union |= dude.known_criteria
12
+ end
13
+ return union
14
+ end
15
+
16
+
17
+ def all_shared_scores(crowd)
18
+ intersection = self.all_known_criteria(crowd)
19
+ crowd.each do |dude|
20
+ intersection = intersection & dude.known_criteria
21
+ end
22
+ return intersection
23
+ end
24
+
25
+
26
+ def domination_classes(crowd, template = all_shared_scores(crowd))
27
+ result = Hash.new()
28
+
29
+ crowd.each_index do |i|
30
+ dominated_by = 0
31
+
32
+ crowd.each_index do |j|
33
+ dominated_by += 1 if crowd[i].dominated_by?(crowd[j], template)
34
+ end
35
+
36
+ result[dominated_by] ||= []
37
+ result[dominated_by].push crowd[i]
38
+ end
39
+
40
+ return result
41
+ end
42
+
43
+
44
+ def diversity_classes(crowd)
45
+ result = Hash.new()
46
+ crowd.each do |dude|
47
+ result[dude.program.tidy] ||= []
48
+ result[dude.program.tidy] << dude
49
+ end
50
+ return result
51
+ end
52
+ end
53
+
54
+
55
+
56
+
57
+
58
+ class NondominatedSubsetSelector < Sampler
59
+
60
+ def generate(crowd, template = all_shared_scores(crowd))
61
+ result = Batch.new
62
+ crowd.each do |answer|
63
+ dominated = false
64
+ crowd.each do |other_answer|
65
+ dominated ||= answer.dominated_by?(other_answer, template)
66
+ end
67
+ result << answer unless dominated
68
+ end
69
+ return result
70
+ end
71
+ end
72
+
73
+
74
+
75
+
76
+
77
+ class DominatedQuantileSampler < Sampler
78
+ def generate(crowd, proportion = 0.5, template = all_shared_scores(crowd))
79
+ classified = domination_classes(crowd, template)
80
+ increasing_grades = classified.keys.sort {|a,b| b <=> a}
81
+ partial_ordering = []
82
+ increasing_grades.each {|grade| partial_ordering += classified[grade]}
83
+ how_many = (crowd.length * proportion).ceil
84
+
85
+ result = Batch.new
86
+ partial_ordering[0...how_many].each {|dude| result << dude} unless how_many == 0
87
+ return result
88
+ end
89
+ end
90
+
91
+
92
+
93
+
94
+
95
+ class MostDominatedSubsetSampler < Sampler
96
+ def generate(crowd, template = all_shared_scores(crowd))
97
+ result = Batch.new
98
+ classified = domination_classes(crowd, template)
99
+ worst_key = classified.keys.sort[-1]
100
+ classified[worst_key].each {|bad_dude| result.push bad_dude}
101
+ return result
102
+ end
103
+ end
104
+
105
+
106
+
107
+
108
+
109
+ class AnyOneSampler < Sampler
110
+ def generate(crowd)
111
+ result = Batch[crowd.sample]
112
+ end
113
+ end
114
+
115
+
116
+
117
+
118
+
119
+ class AllDuplicatedGenomesSampler < Sampler
120
+ def generate(crowd)
121
+ result = Batch.new
122
+ clustered = diversity_classes(crowd)
123
+ clustered.each do |blueprint, array|
124
+ if array.length > 1
125
+ result.concat array
126
+ end
127
+ end
128
+ return result
129
+ end
130
+ end
131
+ end