answer-factory 0.1.2 → 0.1.3.4

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.
@@ -0,0 +1,296 @@
1
+ require File.join(File.dirname(__FILE__), "./../spec_helper")
2
+ include Machines
3
+
4
+ FakeWeb.allow_net_connect = false
5
+
6
+
7
+ describe "Machines::TestCase" do
8
+ describe "inputs Array" do
9
+ before(:each) do
10
+ @tc = TestCase.new
11
+ end
12
+
13
+ describe "bindings" do
14
+ it "should have an attribute called #inputs" do
15
+ @tc.should respond_to(:inputs)
16
+ end
17
+
18
+ it "should accept a Hash as an initialization argument for #inputs" do
19
+ lambda{TestCase.new(inputs:{})}.should_not raise_error
20
+ TestCase.new(inputs:{"x1" => [:int, 12]}).inputs.should ==
21
+ {"x1" => [:int, 12]}
22
+ end
23
+
24
+ it "should default :inputs to an empty Hash" do
25
+ TestCase.new.inputs.should == {}
26
+ end
27
+
28
+ it "should have an attribute called #outputs" do
29
+ @tc.should respond_to(:outputs)
30
+ end
31
+
32
+ it "should accept a Hash as an initialization argument for #outputs" do
33
+ lambda{TestCase.new(outputs:{})}.should_not raise_error
34
+ TestCase.new(outputs:{"y1" => [:bool, false]}).outputs.should ==
35
+ {"y1" => [:bool, false]}
36
+ end
37
+
38
+ it "should default :inputs to an empty Hash" do
39
+ TestCase.new.outputs.should == {}
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ describe "EvaluateWithTestCases" do
47
+
48
+ before(:each) do
49
+ @canned = '{"total_rows":1,"offset":0,"rows":[{"id":"0f60c293ad736abfdb083d33f71ef9ab","key":"ws1","value":{"_id":"0f60c293ad736abfdb083d33f71ef9ab","_rev":"1-473467b6dc1a4cba3498dd6eeb8e3206","blueprint":"do bar","tags":[],"scores":{},"progress":12,"timestamp":"2010/04/14 17:09:14 +0000"}}]}'
50
+
51
+ FakeWeb.register_uri(:any, "http://127.0.0.1:5984/foo_training/_design/tester/_view/test_cases",
52
+ :body => @canned, :status => [200, "OK"])
53
+ @factory = Factory.new(name:"foo")
54
+ @tester = EvaluateWithTestCases.new(name: :tester)
55
+ end
56
+
57
+ describe "initialization" do
58
+
59
+ describe "name" do
60
+ it "should have a name" do
61
+ @tester.should respond_to(:name)
62
+ end
63
+ end
64
+
65
+ describe "data" do
66
+ it "should call #load_training_data! if" do
67
+ EvaluateWithTestCases.new(name: :tester)
68
+ end
69
+ end
70
+
71
+ describe "sensors" do
72
+ it "should have a #sensors attribute" do
73
+ EvaluateWithTestCases.new(name: :tester).should respond_to(:sensors)
74
+ end
75
+
76
+ it "should default to an empty Hash" do
77
+ EvaluateWithTestCases.new(name: :tester).sensors.should == {}
78
+ end
79
+
80
+ describe ":build_sensor" do
81
+ before(:each) do
82
+ @m1 = EvaluateWithTestCases.new(name: :tester)
83
+ end
84
+
85
+ it "should respond to :build_sensor" do
86
+ @m1.should respond_to(:build_sensor)
87
+ end
88
+
89
+ it "should use the name argument as the Hash key in #sensors" do
90
+ @m1.build_sensor("harbor_master_score")
91
+ @m1.sensors.keys.should include("harbor_master_score")
92
+ end
93
+
94
+ it "should take a block and store it as a Proc as the value in #sensors" do
95
+ block = lambda {|a| 9}
96
+ @m1.build_sensor("harbor_master_score", &block)
97
+ @m1.sensors["harbor_master_score"].should == block
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+
105
+ describe "#score method" do
106
+ it "should respond to :score" do
107
+ @tester.should respond_to(:score)
108
+ end
109
+
110
+ it "should only accept a Batch" do
111
+ lambda{@tester.score(99)}.should raise_error(ArgumentError)
112
+ lambda{@tester.score(Batch.new)}.should_not raise_error(ArgumentError)
113
+ end
114
+
115
+ describe "running Interpreter" do
116
+ before(:each) do
117
+ @tester.test_cases = [TestCase.new(inputs: {"x1:int" => 1}, outputs: {"y1" => 2})]
118
+ @batch = Batch.[](Answer.new("do a"))
119
+ end
120
+
121
+ it "should create one Interpreter for each TestCase of #data_in_hand" do
122
+ i = Interpreter.new("")
123
+ Interpreter.should_receive(:new).at_least(1).times.and_return(i)
124
+ @tester.score(@batch)
125
+ end
126
+
127
+ it "should register all the sensors" do
128
+ @tester.sensors = {"y1" => Proc.new{|interpreter| interpreter.peek_value(:int)} }
129
+ i = Interpreter.new
130
+ Interpreter.should_receive(:new).and_return(i)
131
+ i.should_receive(:register_sensor).exactly(1).times
132
+ @tester.score(@batch)
133
+ end
134
+
135
+ it "should set up Interpreters correctly" do
136
+ i = Interpreter.new
137
+ Interpreter.should_receive(:new).with(
138
+ "do a", {:name=>:tester, :target_size_in_points=>99}).and_return(i)
139
+ @tester.score(@batch,target_size_in_points:99)
140
+ end
141
+
142
+
143
+
144
+ end
145
+
146
+ describe "install_training_data_from_csv!" do
147
+ before(:each) do
148
+ FakeWeb.register_uri(:any,
149
+ "http://127.0.0.1:5984/dammit_training/_bulk_docs",
150
+ :body => @canned, :status => [200, "OK"])
151
+ FakeWeb.register_uri(:any,
152
+ "http://127.0.0.1:5984/dammit_training/_design/tester/_view/test_cases",
153
+ :body => @canned, :status => [200, "OK"])
154
+
155
+ @f1 = Factory.new(name: "dammit")
156
+ @my_csv = "./spec/fixtures/my_data_source.csv"
157
+ @m1 = EvaluateWithTestCases.new(name: :tester, training_data_csv: @my_csv)
158
+ @training_db = "http://127.0.0.1:5984/dammit_training"
159
+
160
+ end
161
+
162
+ it "should get the filename as an initialization parameter" do
163
+ EvaluateWithTestCases.new(name: :tester, training_data_csv: "foo.csv").
164
+ csv_filename.should == "foo.csv"
165
+ EvaluateWithTestCases.new(name: :tester).csv_filename.should == nil
166
+ end
167
+
168
+ it "should open a csv file" do
169
+ f = File.open(@my_csv)
170
+ File.should_receive(:open).and_return(f)
171
+ c = CSV.new(f, headers: true)
172
+ CSV.should_receive(:new).with(f, headers: true).and_return(c)
173
+ @m1.install_training_data_from_csv(@my_csv)
174
+ end
175
+
176
+ it "should be the training_data default db" do
177
+ db = CouchRest.database!(@training_db)
178
+ CouchRest.should_receive(:database!).with(@training_db).and_return(db)
179
+ @m1.install_training_data_from_csv(@my_csv)
180
+ end
181
+
182
+ it "makes one doc for every row" do
183
+ db = CouchRest.database!(@training_db)
184
+ CouchRest.should_receive(:database!).with(@training_db).and_return(db)
185
+ db.should_receive(:bulk_save_doc).exactly(3).times
186
+ @m1.install_training_data_from_csv(@my_csv)
187
+ end
188
+
189
+
190
+ it "should raise an error if every header doesn't contain a colon and a type string" do
191
+ lambda{@m1.header_prep("x1")}.should raise_error ArgumentError
192
+ lambda{@m1.header_prep("x1:")}.should raise_error ArgumentError
193
+ lambda{@m1.header_prep("x1:int")}.should_not raise_error ArgumentError
194
+ lambda{@m1.header_prep(":int")}.should raise_error ArgumentError
195
+ end
196
+
197
+ end
198
+
199
+ describe "load_training_data!" do
200
+
201
+ before(:each) do
202
+ @factoreee = Factory.new(name:"dammit")
203
+ @m1 = EvaluateWithTestCases.new(name: :tester)
204
+ @design_doc = "tester/test_cases" # we'll assume this has been set up!
205
+ @expected = {"total_rows"=>1, "offset"=>0, "rows"=>[{"id"=>"05d195b7bb436743ee36b4223008c4ce", "key"=>"05d195b7bb436743ee36b4223008c4ce", "value"=>{"_id"=>"05d195b7bb436743ee36b4223008c4ce", "_rev"=>"1-c9fae927001a1d4789d6396bcf0cd5a7", "inputs"=>{"x1:int"=>7}, "outputs"=>{"y1:grault"=>12}}}]}
206
+
207
+ end
208
+
209
+ it "should get the couch_db uri from configatron" do
210
+ @m1.training_data_view.should ==
211
+ "http://127.0.0.1:5984/dammit_training/_design/#{@m1.name}/_view/test_cases"
212
+ end
213
+
214
+ it "should respond to :load_training_data!" do
215
+ @m1.should respond_to(:load_training_data!)
216
+ end
217
+
218
+ it "should access the couch_db uri" do
219
+ FakeWeb.register_uri(:any,
220
+ "http://127.0.0.1:5984/dammit_training/_design/tester/_view/test_cases",
221
+ :body => @canned, :status => [200, "OK"])
222
+ db = CouchRest.database!(@m1.training_datasource)
223
+ CouchRest.should_receive(:database!).and_return(db)
224
+ db.should_receive(:view).with(@design_doc).and_return(@expected)
225
+ @m1.load_training_data!
226
+ end
227
+
228
+ it "should throw a useful error if the view isn't available"
229
+
230
+ it "should ask for the view document" do
231
+ db = CouchRest.database!(@m1.training_datasource)
232
+ CouchRest.should_receive(:database!).and_return(db)
233
+ db.should_receive(:view).with(@design_doc).and_return(@expected)
234
+ @m1.load_training_data!
235
+ end
236
+
237
+ it "should store Array of TestCases in @test_cases" do
238
+ db = CouchRest.database!(@m1.training_datasource)
239
+ CouchRest.should_receive(:database!).and_return(db)
240
+ db.should_receive(:view).with(@design_doc).and_return(@expected)
241
+
242
+ @m1.load_training_data!
243
+ @m1.test_cases.should be_a_kind_of(Array)
244
+ @m1.test_cases.length.should == 1
245
+ end
246
+ end
247
+
248
+ describe "scoring" do
249
+ before(:each) do
250
+ @m1 = EvaluateWithTestCases.new(name: :tester)
251
+ @m1.test_cases = (0...10).collect do |i|
252
+ TestCase.new(inputs: {"x1:int" => i}, outputs: {"y1" => 2*i, "y2" => 3*i})
253
+ end
254
+ @m1.build_sensor("y1") {|a| 777}
255
+ @m1.build_sensor("y2") {|a| 666}
256
+
257
+ @batch = Batch.[](Answer.new("do a"))
258
+ @i1 = Interpreter.new
259
+ end
260
+
261
+ it "should make an Interpreter for each row of training data" do
262
+ @m1.stub!(:load_training_data!)
263
+ Interpreter.should_receive(:new).exactly(10).times.and_return(@i1)
264
+ @m1.score(@batch)
265
+ end
266
+
267
+ it "should run all the Interpreters" do
268
+ @m1.stub!(:load_training_data!)
269
+ Interpreter.should_receive(:new).exactly(10).times.and_return(@i1)
270
+ @i1.should_receive(:run).at_least(1).times.and_return({})
271
+ @m1.score(@batch)
272
+ end
273
+
274
+ it "should register its sensors before each Interpreter run" do
275
+ Interpreter.stub!(:new).and_return(@i1)
276
+ @i1.should_receive(:register_sensor).at_least(1).times
277
+ @m1.score(@batch)
278
+ end
279
+
280
+ it "should have a score for each sensor" do
281
+ @m1.score(@batch)
282
+ @batch.first.scores["y1"].should_not == nil
283
+ @batch.first.scores["y2"].should_not == nil
284
+ end
285
+
286
+ it "should return sum of absolute errors" do
287
+ @m1.stub!(:load_training_data!)
288
+ @m1.score(@batch)
289
+ @batch[0].scores["y1"].should == 777+775+773+771+769+767+765+763+761+759
290
+ @batch[0].scores["y2"].should == 666+663+660+657+654+651+648+645+642+639
291
+ end
292
+ end
293
+
294
+
295
+ end
296
+ end
@@ -0,0 +1,134 @@
1
+ require File.join(File.dirname(__FILE__), "./../spec_helper")
2
+
3
+ describe "Machines::SelectBySummedRank" do
4
+ before(:each) do
5
+ @best = Machines::SelectBySummedRank.new
6
+ @highlander = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
7
+ @highlander[0].scores = {e1:15, e2:12} # 3,3 = 6
8
+ @highlander[1].scores = {e1:5, e2:5} # 2,2 = 4
9
+ @highlander[2].scores = {e1:1, e2:1} # 1,1 = 2
10
+
11
+ @lowlander = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
12
+ @lowlander[0].scores = {e1:1, e2:3} # 1,3 = 4
13
+ @lowlander[1].scores = {e1:2, e2:2} # 2,2 = 4
14
+ @lowlander[2].scores = {e1:3, e2:1} # 3,1 = 4
15
+
16
+ @allover = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
17
+ @allover[0].scores = {e1:1, e2:3} # 1,2,-
18
+ @allover[1].scores = {e1:2, e2:2, e3:2} # 2,1,1
19
+ @allover[2].scores = {e1:3} # 3,-,-
20
+
21
+ @separate = Batch.[](Answer.new("foo"), Answer.new("bar"))
22
+ @separate[0].scores = {e1:1}
23
+ @separate[1].scores = {e2:2, e3:2}
24
+ end
25
+
26
+
27
+ describe "#screen method" do
28
+ it "should respond to :screen" do
29
+ @best.should respond_to(:screen)
30
+ end
31
+
32
+ it "should only accept a Batch as its argument" do
33
+ lambda{@best.screen(129)}.should raise_error
34
+ lambda{@best.screen([Answer.new("foo")])}.should raise_error
35
+ lambda{@best.screen(@highlander, comparison_criteria:[:e1])}.should_not raise_error
36
+ end
37
+
38
+ it "should produce a Batch" do
39
+ @best.screen(@highlander, comparison_criteria:[:e2]).should be_a_kind_of(Batch)
40
+ end
41
+
42
+ it "should use the :comparison_criteria option as a template Array of score keys" do
43
+ ignore_most = @best.screen(@lowlander, comparison_criteria:[:e1])
44
+ ignore_most.length.should == 1
45
+ ignore_most.should include(@lowlander[0])
46
+
47
+ ignore_most = @best.screen(@lowlander, comparison_criteria:[:e2])
48
+ ignore_most.length.should == 1
49
+ ignore_most.should include(@lowlander[2])
50
+
51
+ ignore_most = @best.screen(@allover, comparison_criteria:[:e1])
52
+ ignore_most.length.should == 1
53
+ ignore_most.should include(@allover[0])
54
+ end
55
+
56
+ it "should use the intersection of all the score keys in the :batch as a default for criteria" do
57
+ @best.should_receive(:shared_goals).and_return([:e1])
58
+ @best.screen(@highlander)
59
+ end
60
+
61
+ it "should accept (and store) an initialization :comparison_criteria option" do
62
+ just_one = Machines::SelectBySummedRank.new(comparison_criteria:[:e2]).
63
+ screen(@lowlander)
64
+ just_one.length.should == 1
65
+ just_one.should include(@lowlander[2])
66
+ end
67
+
68
+ it "should include all Answers lacking a given score (since they can't be ranked)" do
69
+ dunno = @best.screen(@allover, comparison_criteria:[:e2])
70
+ dunno.length.should == 2
71
+ dunno.should include(@allover[1])
72
+ dunno.should include(@allover[2])
73
+ dunno.should_not include(@allover[0])
74
+ end
75
+
76
+ it "should return the entire batch if no scores are shared" do
77
+ @best.screen(@separate).length.should == 2
78
+ end
79
+
80
+ it "should override its initialization if given a #build option" do
81
+ overridden = Machines::SelectBySummedRank.new(comparison_criteria:[:e2]).
82
+ screen(@lowlander,comparison_criteria:[:e1])
83
+ overridden.length.should == 1
84
+ overridden[0].scores.should == {e1:1, e2:3}
85
+ end
86
+
87
+ it "should return the lowest-ranking subset of the argument Batch" do
88
+ @best.screen(@lowlander).length.should == 3
89
+ end
90
+
91
+ it "should return objects from the argument, not clones" do
92
+ (@highlander.collect {|a| a.object_id}).should include(@best.screen(@highlander)[0].object_id)
93
+ end
94
+
95
+ it "should return a new Batch object" do
96
+ @best.screen(@highlander).object_id.should_not == @highlander.object_id
97
+ end
98
+
99
+ it "should not change the :progress of the Answers" do
100
+ @best.screen(@highlander).each {|a| a.progress.should == 0}
101
+ end
102
+
103
+ end
104
+
105
+
106
+ it "should respond to :generate as an alias to :screen" do
107
+ Machines::SelectBySummedRank.new.should respond_to(:generate)
108
+ end
109
+
110
+
111
+ describe "all_goals" do
112
+ it "should return an Array of score keys" do
113
+ @best.all_goals(@highlander).should == [:e1,:e2]
114
+ end
115
+
116
+ it "should include every score key in the batch passed in" do
117
+ @best.all_goals(@allover).should == [:e1,:e2, :e3]
118
+ end
119
+
120
+ it "should have one copy of each score name" do
121
+ @best.all_goals(@highlander).find_all {|e| e == :e1}.length.should == 1
122
+ end
123
+ end
124
+
125
+
126
+ describe "shared_goals" do
127
+ it "should return an Array of only the shared score keys from the Batch passed in" do
128
+ @best.shared_goals(@highlander).should == [:e1,:e2]
129
+ @best.shared_goals(@allover).should == [:e1]
130
+ @best.shared_goals(@separate).should == []
131
+ end
132
+ end
133
+ end
134
+
@@ -4,6 +4,7 @@ describe "Machines::SelectNondominated" do
4
4
  describe "#screen method" do
5
5
  before(:each) do
6
6
  @best = Machines::SelectNondominated.new
7
+
7
8
  @highlander = Batch.[](Answer.new("foo"), Answer.new("bar"), Answer.new("baz"))
8
9
  @highlander[0].scores = {e1:5, e2:15}
9
10
  @highlander[1].scores = {e1:15,e2:5}
@@ -18,6 +19,10 @@ describe "Machines::SelectNondominated" do
18
19
  @allover[0].scores = {e1:1, e2:3}
19
20
  @allover[1].scores = {e1:2, e2:2, e3:2}
20
21
  @allover[2].scores = {e1:3}
22
+
23
+ @separate = Batch.[](Answer.new("foo"), Answer.new("bar"))
24
+ @separate[0].scores = {e1:1}
25
+ @separate[1].scores = {e2:2, e3:2}
21
26
  end
22
27
 
23
28
  it "should respond to :screen" do
@@ -37,6 +42,7 @@ describe "Machines::SelectNondominated" do
37
42
  it "should accept a template Array of score keys" do
38
43
  @best.screen(@lowlander).length.should == 3
39
44
  @best.screen(@lowlander,comparison_criteria:[:e2]).length.should == 1
45
+ @best.screen(@lowlander,comparison_criteria:[:e2]).should include(@lowlander[2])
40
46
  end
41
47
 
42
48
  it "should use an initialization template as well" do
@@ -56,6 +62,8 @@ describe "Machines::SelectNondominated" do
56
62
  @best.screen(@allover, comparison_criteria:[:e1]).length.should == 1
57
63
  @best.screen(@allover, comparison_criteria:[:e2]).length.should == 2
58
64
  @best.screen(@allover, comparison_criteria:[:e3]).length.should == 3
65
+
66
+ @best.screen(@separate).should == @separate
59
67
  end
60
68
 
61
69
  it "should return the nondominated subset of the argument" do