answer-factory 0.0.9 → 0.0.10

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/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