answer-factory 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 (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