answer-factory 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/Rakefile +1 -0
  2. data/Thorfile +9 -6
  3. data/VERSION +1 -1
  4. data/answer-factory.gemspec +20 -5
  5. data/lib/answer-factory.rb +14 -2
  6. data/lib/answers/answer.rb +17 -3
  7. data/lib/answers/batch.rb +15 -3
  8. data/lib/factories/workstation.rb +50 -1
  9. data/lib/operators/all_duplicated_genomes_sampler.rb +14 -0
  10. data/lib/operators/any_one_sampler.rb +7 -0
  11. data/lib/operators/dominated_quantile_selector.rb +16 -0
  12. data/lib/operators/infrastructure.rb +74 -0
  13. data/lib/operators/most_dominated_subset_sampler.rb +13 -0
  14. data/lib/operators/nondominated_subset_selector.rb +17 -0
  15. data/lib/operators/point_crossover_operator.rb +24 -0
  16. data/lib/operators/point_delete_operator.rb +19 -0
  17. data/lib/operators/point_mutation_operator.rb +22 -0
  18. data/lib/operators/program_point_count_evaluator.rb +14 -0
  19. data/lib/operators/random_guess_operator.rb +30 -0
  20. data/lib/operators/resample_and_clone_operator.rb +28 -0
  21. data/lib/operators/resample_values_operator.rb +40 -0
  22. data/lib/operators/{evaluators.rb → test_case_evaluator.rb} +3 -34
  23. data/lib/operators/uniform_backbone_crossover_operator.rb +53 -0
  24. data/readme.md +28 -3
  25. data/spec/answer_spec.rb +33 -1
  26. data/spec/batch_spec.rb +25 -12
  27. data/spec/factories/factory_spec.rb +53 -36
  28. data/spec/factories/workstation_spec.rb +194 -20
  29. data/spec/operators/evaluators/program_point_evaluator_spec.rb +1 -1
  30. data/spec/operators/evaluators/test_case_evaluator_spec.rb +2 -2
  31. data/spec/operators/nondominated_subset_spec.rb +8 -8
  32. data/spec/operators/random_guess_spec.rb +16 -11
  33. data/spec/operators/resample_and_clone_spec.rb +8 -8
  34. data/spec/operators/uniformBackboneCrossover_spec.rb +7 -7
  35. data/spec/spec_helper.rb +1 -0
  36. metadata +38 -12
  37. data/lib/operators/basic_operators.rb +0 -240
  38. data/lib/operators/samplers_and_selectors.rb +0 -131
@@ -0,0 +1,19 @@
1
+ module AnswerFactory
2
+ class PointDeleteOperator < SearchOperator
3
+ def generate(crowd, howManyCopies = 1)
4
+ raise(ArgumentError) if !crowd.kind_of?(Array)
5
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
6
+
7
+ result = Batch.new
8
+ crowd.each do |dude|
9
+ howManyCopies.times do
10
+ where = rand(dude.points)+1
11
+ variant = dude.delete_point_or_clone(where)
12
+ baby = Answer.new(variant, progress:dude.progress + 1)
13
+ result << baby
14
+ end
15
+ end
16
+ return result
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module AnswerFactory
2
+ class PointMutationOperator < SearchOperator
3
+
4
+ def generate(crowd, howManyCopies = 1, overridden_options ={})
5
+ raise(ArgumentError) if !crowd.kind_of?(Array)
6
+ raise(ArgumentError) if crowd.empty?
7
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
8
+
9
+ result = Batch.new
10
+ crowd.each do |dude|
11
+ howManyCopies.times do
12
+ where = rand(dude.points)+1
13
+ newCode = CodeType.any_value(@incoming_options.merge(overridden_options))
14
+ variant = dude.replace_point_or_clone(where,newCode)
15
+ baby = Answer.new(variant, progress:dude.progress + 1)
16
+ result << baby
17
+ end
18
+ end
19
+ return result
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module AnswerFactory
2
+ class ProgramPointEvaluator < Evaluator
3
+ def evaluate(batch)
4
+ raise(ArgumentError, "Can only evaluate a Batch of Answers") if !batch.kind_of?(Batch)
5
+ batch.each do |i|
6
+ if i.parses?
7
+ i.scores[@score_label] = i.program.points
8
+ else
9
+ raise(ArgumentError, "Program is not parseable")
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ module AnswerFactory
2
+ class RandomGuessOperator < SearchOperator
3
+
4
+ # returns an Array of random Answers
5
+ #
6
+ # the first (optional) parameter specifies how many to make, and defaults to 1
7
+ # the second (also optional) parameter is a hash that
8
+ # can temporarily override those set in the initialization
9
+ #
10
+ # For example, if
11
+ # <tt>myRandomGuesser = RandomGuessOperator.new(:randomIntegerLowerBound => -90000)</tt>
12
+ #
13
+ # [<tt>myRandomGuesser.generate()</tt>]
14
+ # produces a list of 1 Answer, and if it has any IntType samples they will be in [-90000,100]
15
+ # (since the default +:randomIntegerLowerBound+ is 100)
16
+ # [<tt>myRandomGuesser.generate(1,:randomIntegerLowerBound => 0)</tt>]
17
+ # makes one Answer whose IntType samples (if any) will be between [0,100]
18
+
19
+ def generate(crowd, overridden_options = {})
20
+ every_option = @incoming_options.merge(overridden_options)
21
+ how_many = every_option[:how_many] || 1
22
+ how_many.times do
23
+ newGenome = CodeType.any_value(every_option)
24
+ newDude = Answer.new(newGenome, progress:0)
25
+ crowd << newDude
26
+ end
27
+ return crowd
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module AnswerFactory
2
+ class ResampleAndCloneOperator < SearchOperator
3
+
4
+ # returns an Array of clones of Answers randomly selected from the crowd passed in
5
+ #
6
+ # the first (required) parameter is an Array of Answers
7
+ # the second (optional) parameter is how many samples to take, and defaults to 1
8
+ #
9
+ # For example, if
10
+ # <tt>@currentPopulation = [a list of 300 Answers]</tt> and
11
+ # <tt>myRandomSampler = ResampleAndCloneOperator.new(@currentPopulation)</tt>
12
+ # [<tt>myRandomSampler.generate()</tt>]
13
+ # produces a list of 1 Answer, which is a clone of somebody from <tt>@currentPopulation</tt>
14
+ # [<tt>myRandomGuesser.generate(11)</tt>]
15
+ # returns a list of 11 Answers cloned from <tt>@currentPopulation</tt>,
16
+ # possibly including repeats
17
+
18
+ def generate(crowd, howMany = 1)
19
+ result = Batch.new
20
+ howMany.times do
21
+ donor = crowd.sample
22
+ clone = Answer.new(donor.blueprint, progress:donor.progress + 1)
23
+ result << clone
24
+ end
25
+ return result
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ #encoding: utf-8
2
+ module AnswerFactory
3
+
4
+ class ResampleValuesOperator < SearchOperator
5
+
6
+ def generate(crowd, howManyCopies = 1, overridden_options = {})
7
+ crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) }
8
+
9
+ result = Batch.new
10
+ regenerating_options = @incoming_options.merge(overridden_options)
11
+ crowd.each do |dude|
12
+ howManyCopies.times do
13
+ wildtype_program = dude.program
14
+ starting_footnotes = wildtype_program.footnote_section.split( /^(?=«)/ )
15
+ breaker = /^«([a-zA-Z][a-zA-Z0-9_]*)»\s*(.*)\s*/m
16
+ type_value_pairs = starting_footnotes.collect {|fn| fn.match(breaker)[1..2]}
17
+
18
+ mutant_blueprint = wildtype_program.code_section
19
+
20
+ type_value_pairs.each do |pair|
21
+
22
+ begin
23
+ type_name = pair[0]
24
+ type_class = "#{type_name}_type".camelize.constantize
25
+ reduced_size = regenerating_options[:target_size_in_points] || rand(dude.points/2)
26
+ reduced_option = {target_size_in_points:reduced_size}
27
+ resampled_value = type_class.any_value(regenerating_options.merge(reduced_option)).to_s
28
+ rescue NameError
29
+ resampled_value = pair[1]
30
+ end
31
+ mutant_blueprint << "\n«#{pair[0].strip}» #{resampled_value.strip}"
32
+ end
33
+ mutant = Answer.new(mutant_blueprint, progress:dude.progress + 1)
34
+ result << mutant
35
+ end
36
+ end
37
+ return result
38
+ end
39
+ end
40
+ end
@@ -1,35 +1,4 @@
1
1
  module AnswerFactory
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
2
  class TestCase
34
3
  attr_accessor :bindings, :expectations, :gauges
35
4
 
@@ -59,7 +28,7 @@ module AnswerFactory
59
28
  variable_names = params[:references] || []
60
29
 
61
30
  batch.each do |dude|
62
- if !params[:deterministic] || !dude.scores[@name]
31
+ if !params[:deterministic] || !dude.scores[@score_label]
63
32
  score = 0
64
33
  readings = {}
65
34
  cases.each do |example|
@@ -100,11 +69,11 @@ module AnswerFactory
100
69
  score += difference.abs
101
70
  end
102
71
  # aggregate differences
103
- dude.scores[@name] = score.to_f / cases.length
72
+ dude.scores[@score_label] = score.to_f / cases.length
104
73
 
105
74
  puts "#{score.to_f / cases.length}" if params[:feedback]
106
75
  else
107
- puts dude.scores[@name] if params[:feedback]
76
+ puts dude.scores[@score_label] if params[:feedback]
108
77
  end
109
78
  end
110
79
  end
@@ -0,0 +1,53 @@
1
+ module AnswerFactory
2
+ class UniformBackboneCrossoverOperator < SearchOperator
3
+
4
+ # Returns a Batch of new Answers whose programs are made by stitching together
5
+ # the programs of pairs of 'parents'. The incoming Batch is divided into pairs based on
6
+ # adjacency (modulo the Batch.length), one pair for each 'offspring' to be made. To make
7
+ # an offspring, the number of backbone program points is determined in each parent; 'backbone'
8
+ # refers to the number of branches directly within the root of the program, not the entire tree.
9
+ #
10
+ # To construct an offspring's program, program points are copied from the first parent with
11
+ # probability p, or the second parent with probability (1-p), for each point in the first
12
+ # parent's backbone. So if there are 13 and 6 points, respectively, the first six points are
13
+ # selected randomly, but the last 7 are copied from the first parent. If there are 8 and 11
14
+ # respectively, then the last 3 will be ignored from the second parent in any case.
15
+ #
16
+ # the first (required) parameter is an Array of Answers
17
+ # the second (optional) parameter is how many crossovers to make,
18
+ # which defaults to the number of Answers in the incoming Batch
19
+
20
+ def generate(crowd, howMany = crowd.length, prob = 0.5)
21
+ result = Batch.new
22
+ howMany.times do
23
+ where = rand(crowd.length)
24
+ mom = crowd[where]
25
+ dad = crowd[ (where+1) % crowd.length ]
26
+ mom_backbone_length = mom.program[1].contents.length
27
+ dad_backbone_length = dad.program[1].contents.length
28
+
29
+ baby_blueprint_parts = ["",""]
30
+ (0..mom_backbone_length-1).each do |backbone_point|
31
+ if rand() < prob
32
+ next_chunks = mom.program[1].contents[backbone_point].blueprint_parts || ["",""]
33
+ else
34
+ if backbone_point < dad_backbone_length
35
+ next_chunks = (dad.program[1].contents[backbone_point].blueprint_parts || ["", ""])
36
+ else
37
+ next_chunks = ["",""]
38
+ end
39
+ end
40
+ baby_blueprint_parts[0] << " #{next_chunks[0]}"
41
+ baby_blueprint_parts[1] << " \n#{next_chunks[1]}"
42
+ end
43
+ mom.program.unused_footnotes.each {|fn| baby_blueprint_parts[1] += "\n#{fn}"}
44
+
45
+ baby_blueprint = "block {#{baby_blueprint_parts[0]}} #{baby_blueprint_parts[1]}"
46
+ baby = Answer.new(baby_blueprint, progress:[mom.progress,dad.progress].max + 1)
47
+
48
+ result << baby
49
+ end
50
+ return result
51
+ end
52
+ end
53
+ end
data/readme.md CHANGED
@@ -22,10 +22,35 @@ The AnswerFactory infrastructure has been designed to help _regular people_ buil
22
22
 
23
23
  ## Getting started
24
24
 
25
- You'll need a working (and running) installation of CouchDB.
25
+ The `answer-factory` gem depends on Ruby 1.9 or higher. We recommend [rvm](http://rvm.beginrescueend.com/) if you'd like to maintain several Ruby installations.
26
26
 
27
- Then:
27
+ You'll also need a working installation of [CouchDB](http://couchdb.apache.org/) available. This can be a remote instance, as long as you have the necessary permissions to create and manage databases.
28
28
 
29
+ Then:
29
30
  gem install answer-factory
30
31
 
31
- This should automatically load dependent gems, including nudge, rspec, and many others.
32
+ This will automatically install several dependencies, including [nudge](http://github.com/Vaguery/Nudge), rspec, and others.
33
+
34
+ ### Creating a new AnswerFactory project
35
+
36
+ Use this command line script to build an AnswerFactory project folder:
37
+ answer-factory your-project-name-here
38
+
39
+ This will create a new directory called 'your-project-name-here', and install a rudimentary subtree of folders and files. Perhaps most important is the `Thorfile`, which contains most of the generators you can use to simplify project creation and management.
40
+
41
+ ### Replicating a pre-existing project or demo
42
+
43
+ TBD
44
+
45
+ ### Generating new Nudge type defintitions
46
+
47
+ The Nudge language gem installed along with `answer-factory` includes a full-featured programming language designed for genetic programming projects, with integer, floating-point, boolean, and code types.
48
+
49
+ Often your project's domain model will call for additional types. To generate some basic infrastructure for a new NudgeType subclass, navigate to the root of your project folder and invoke the thor script
50
+ thor new_nudge_type your-nudge-type-name
51
+ This will create a template for your class definition in the `/lib/nudge/types` subdirectory (which you should edit as indicated in the comments to use), several standard nudge instruction classes in `/lib/nudge/instructions`, and rspec files.
52
+
53
+ ### Activating the AnswerFactory daemon
54
+
55
+ Make sure CouchDB is running and available, navigate to your project's root folder, and invoke
56
+ ruby activate.rb
data/spec/answer_spec.rb CHANGED
@@ -78,7 +78,7 @@ describe "Answer" do
78
78
  describe "serialization" do
79
79
  describe "writing" do
80
80
  before(:each) do
81
- @a1 = Answer.new("block {do a}")
81
+ @a1 = Answer.new("block {do a}", progress:12)
82
82
  end
83
83
 
84
84
  it "should contain the blueprint" do
@@ -94,12 +94,44 @@ describe "Answer" do
94
94
  @a1.data['scores'].should == @a1.scores
95
95
  end
96
96
 
97
+ it "should contain the progress" do
98
+ @a1.data['progress'].should == @a1.progress
99
+ end
100
+
101
+
97
102
  it "should contain the timestamp" do
98
103
  @a1.data['timestamp'].should == @a1.timestamp
99
104
  end
100
105
  end
101
106
 
102
107
  describe "reading" do
108
+ before(:each) do
109
+ @couchified = {"id"=>"0f60c293ad736abfdb083d33f71ef9ab", "key"=>"ws1", "value"=>{"_id"=>"0f60c293ad736abfdb083d33f71ef9ab", "_rev"=>"1-473467b6dc1a4cba3498dd6eeb8e3206", "blueprint"=>"do bar", "tags"=>["quux", "whatevz"], "scores"=>{"badness" => 12.345}, "progress" => 12, "timestamp"=>"2010/04/14 17:09:14 +0000"}}
110
+ @my_a = Answer.from_serial_hash(@couchified)
111
+ end
112
+
113
+ it "should record the couch_doc_id" do
114
+ @my_a.couch_id.should == "0f60c293ad736abfdb083d33f71ef9ab"
115
+ end
116
+
117
+ it "should accept a blueprint string" do
118
+ @my_a.blueprint.should == "do bar"
119
+ end
120
+
121
+ it "should collect the tag Array into a Set of symbols" do
122
+ @my_a.tags.should be_a_kind_of(Set)
123
+ @my_a.tags.should include(:quux)
124
+ @my_a.tags.should include(:whatevz)
125
+ end
126
+
127
+ it "should gather up the scores Hash" do
128
+ @my_a.scores.should be_a_kind_of(Hash)
129
+ @my_a.scores.should include(:badness)
130
+ end
131
+
132
+ it "should read the progress" do
133
+ @my_a.progress.should == 12
134
+ end
103
135
 
104
136
  end
105
137
  end
data/spec/batch_spec.rb CHANGED
@@ -74,25 +74,38 @@ describe "Batches" do
74
74
 
75
75
  describe "reading" do
76
76
  before(:each) do
77
- @uri = "http://127.0.0.1/baz:5984"
77
+ @uri = "http://127.0.0.1:5984/my_factory"
78
+ @design_doc = "ws1/current" # we'll assume this has been set up!
79
+ @view_uri = "http://127.0.0.1:5984/my_factory/_design/ws1/_view/current"
80
+ FakeWeb.allow_net_connect = false
81
+ @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"}}]}'
82
+
78
83
  end
79
84
 
80
- it "create a new Batch" do
81
- Batch.load_tagged_answers(@uri,"foo").should be_a_kind_of(Batch)
85
+ it "should connect to the right view in the right design doc in the persistent store" do
86
+ FakeWeb.register_uri(:any, @view_uri, :body => @canned, :status => [200, "OK"])
87
+ lambda{Batch.load_from_couch(@uri,@design_doc)}.should_not raise_error
88
+ # because it hit the right URI!
82
89
  end
83
90
 
84
- it "should access the database" do
85
- CouchRest.should_receive(:database).with(@uri)
86
- Batch.load_tagged_answers(@uri, "workstation_1")
87
- end
91
+ it "should handle errors returned from CouchDB"
92
+
93
+ it "should handle db connection problems"
88
94
 
89
- it "should work"
95
+ it "should create an Answer for every row received" do
96
+ FakeWeb.register_uri(:any, @view_uri, :body => @canned, :status => [200, "OK"])
97
+ little_batch = Batch.load_from_couch(@uri,@design_doc)
98
+ little_batch.length.should == 1
99
+ little_batch[0].blueprint.should == "do bar"
100
+ end
101
+
102
+ it "should raise an warning and create an empty Batch if it can't parse the result" do
103
+ FakeWeb.register_uri(:any, @view_uri, :body => "some random crap", :status => [200, "OK"])
104
+ little_batch = Batch.load_from_couch(@uri,@design_doc)
105
+ little_batch.length.should == 0
106
+ end
90
107
  end
91
108
 
92
109
  end
93
-
94
-
95
- it "should have a Batch.load_from_couch method that reads a bunch of Answers from the db"
96
-
97
110
  end
98
111
  end
@@ -11,6 +11,59 @@ describe "Factory" do
11
11
  Factory.new.name.should == "my_factory"
12
12
  end
13
13
 
14
+
15
+ describe "databases" do
16
+ describe "#couch_available?" do
17
+ it "should have a method to check that couchDB is accessible" do
18
+ f1 = Factory.new("boo")
19
+ lambda{f1.couch_available?}.should_not raise_error
20
+ end
21
+
22
+ it "should return true if the uri is reachable" do
23
+ uri = "http://mycouch.db/boo"
24
+ f1 = Factory.new("boo")
25
+ f1.configatron.couchdb_uri = uri
26
+ FakeWeb.register_uri(:any, uri, :body => "We are here!", :status => [200, "OK"])
27
+ f1.couch_available?.should == true
28
+ end
29
+
30
+ it "should return false if the uri is offline or 404's out" do
31
+ uri = "http://mycouch.db/boo"
32
+ f1 = Factory.new("boo")
33
+ f1.configatron.couchdb_uri = uri
34
+ f1.configatron.couchdb_uri.should == uri
35
+ FakeWeb.register_uri(:any, uri, :body => "Go away!", :status => [404, "Not Found"])
36
+ f1.couch_available?.should == false
37
+
38
+ f1 = Factory.new("boo") # depends on this being wrong
39
+ f1.configatron.couchdb_uri = "http://127.0.0.1:9991/place"
40
+ f1.couch_available?.should == false
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ describe "build" do
47
+ it "should read the config files"
48
+
49
+ it "should #reset"
50
+ end
51
+
52
+
53
+ describe "reset" do
54
+ it "should erase the couchdb"
55
+
56
+ it "should set up a new couchdb"
57
+
58
+ it "should set up the necessary design documents in the db"
59
+ end
60
+
61
+
62
+ describe "activate" do
63
+ it "should check the config files"
64
+ end
65
+
66
+
14
67
  describe "ontology" do
15
68
  it "should have a master Instruction list" do
16
69
  Factory.new("foo").instruction_library.should == Instruction.all_instructions
@@ -44,43 +97,7 @@ describe "Factory" do
44
97
  Factory.new.workstation_names.should == []
45
98
  end
46
99
 
47
- describe "build_workstation" do
48
- it "should create a new workstation"
49
- it "should set up all the interior dynamics of the workstation"
50
- it "should use the master config for defaults of the new workstation"
51
- it "should suffice to create a pass-through workstation just to name it"
52
- end
53
100
  end
54
101
 
55
- describe "activate" do
56
- it "should have an #activate method"
57
- end
58
102
 
59
- describe "databases" do
60
-
61
- describe "#couch_available?" do
62
- it "should have a method to check that couchDB is accessible" do
63
- f1 = Factory.new("boo")
64
- lambda{f1.couch_available?}.should_not raise_error
65
- end
66
-
67
- it "should return true if the uri is reachable" do
68
- uri = "http://mycouch.db/boo"
69
- f1 = Factory.new("boo")
70
- f1.configatron.couchdb_uri = uri
71
- FakeWeb.register_uri(:any, uri, :body => "We are here!", :status => [200, "OK"])
72
- f1.couch_available?.should == true
73
- end
74
-
75
- it "should return false if the uri is offline or 404's out" do
76
- uri = "http://mycouch.db/boo"
77
- f1 = Factory.new("boo", couchdb_uri:uri)
78
- FakeWeb.register_uri(:any, uri, :body => "Go away!", :status => [404, "Not Found"])
79
- f1.couch_available?.should == false
80
-
81
- f1 = Factory.new("boo", couchdb_uri:"http://127.0.0.1:9991/place") # depends on this being wrong
82
- f1.couch_available?.should == false
83
- end
84
- end
85
- end
86
103
  end