fathom 0.3.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/.autotest +7 -5
  2. data/.document +2 -2
  3. data/Gemfile +9 -10
  4. data/{LICENSE → LICENSE.txt} +1 -1
  5. data/README.md +29 -90
  6. data/Rakefile +34 -32
  7. data/VERSION +1 -1
  8. data/fathom.gemspec +105 -0
  9. data/features/fathom.feature +26 -0
  10. data/features/step_definitions/fathom_steps.rb +23 -0
  11. data/features/support/env.rb +13 -0
  12. data/lib/ext/array.rb +6 -2
  13. data/lib/ext/string.rb +86 -7
  14. data/lib/fathom.rb +51 -88
  15. data/lib/fathom/behaviors/attribute_system.rb +91 -0
  16. data/lib/fathom/behaviors/context_behavior.rb +28 -0
  17. data/lib/fathom/behaviors/plugins.rb +16 -0
  18. data/lib/fathom/contexts/network_population.rb +47 -0
  19. data/lib/fathom/contexts/network_traversal.rb +4 -0
  20. data/lib/fathom/data/adjacency_matrix.rb +27 -0
  21. data/lib/fathom/data/definition.rb +22 -0
  22. data/lib/fathom/data/edge.rb +58 -0
  23. data/lib/fathom/data/network.rb +35 -0
  24. data/lib/fathom/data/outcome.rb +30 -0
  25. data/lib/fathom/data/property.rb +31 -0
  26. data/lib/fathom/data/variable.rb +59 -0
  27. data/lib/fathom/roles/general_graph_tools.rb +87 -0
  28. data/lib/fathom/roles/network_builder.rb +61 -0
  29. data/spec/fathom/behaviors/attribute_system_spec.rb +141 -0
  30. data/spec/fathom/behaviors/context_behavior_spec.rb +15 -0
  31. data/spec/fathom/behaviors/plugins_spec.rb +80 -0
  32. data/spec/fathom/contexts/network_population_spec.rb +55 -0
  33. data/spec/fathom/contexts/network_traversal_spec.rb +11 -0
  34. data/spec/fathom/data/adjacency_matrix_spec.rb +42 -0
  35. data/spec/fathom/data/definition_spec.rb +19 -0
  36. data/spec/fathom/data/edge_spec.rb +77 -0
  37. data/spec/fathom/data/network_spec.rb +72 -0
  38. data/spec/fathom/data/outcome_spec.rb +17 -0
  39. data/spec/fathom/data/property_spec.rb +17 -0
  40. data/spec/fathom/data/variable_spec.rb +101 -0
  41. data/spec/fathom/ext/array_spec.rb +17 -0
  42. data/spec/fathom/ext/string_spec.rb +90 -0
  43. data/spec/fathom/roles/general_graph_tools_spec.rb +95 -0
  44. data/spec/fathom/roles/network_builder_spec.rb +90 -0
  45. data/spec/fathom_spec.rb +28 -49
  46. data/spec/spec_helper.rb +7 -11
  47. data/spec/support/context_behavior.rb +14 -0
  48. data/spec/support/custom_matchers.rb +12 -0
  49. data/spec/support/files.rb +8 -0
  50. data/spec/support/network.yml +42 -0
  51. metadata +133 -174
  52. data/.bundle/config +0 -2
  53. data/.gitignore +0 -6
  54. data/Gemfile.lock +0 -42
  55. data/TODO.md +0 -127
  56. data/autotest/discover.rb +0 -1
  57. data/lib/ext/faster_csv.rb +0 -1
  58. data/lib/ext/open_struct.rb +0 -17
  59. data/lib/fathom/agent.rb +0 -48
  60. data/lib/fathom/agent/agent_cluster.rb +0 -23
  61. data/lib/fathom/agent/properties.rb +0 -48
  62. data/lib/fathom/archive/causal_graph.rb +0 -12
  63. data/lib/fathom/archive/concept.rb +0 -83
  64. data/lib/fathom/archive/conditional_probability_matrix.rb +0 -119
  65. data/lib/fathom/archive/inverter.rb +0 -20
  66. data/lib/fathom/archive/n2.rb +0 -198
  67. data/lib/fathom/archive/n3.rb +0 -119
  68. data/lib/fathom/archive/node.rb +0 -97
  69. data/lib/fathom/archive/noodle.rb +0 -136
  70. data/lib/fathom/archive/scratch.rb +0 -45
  71. data/lib/fathom/distributions.rb +0 -8
  72. data/lib/fathom/distributions/discrete_gaussian.rb +0 -44
  73. data/lib/fathom/distributions/discrete_uniform.rb +0 -25
  74. data/lib/fathom/distributions/gaussian.rb +0 -46
  75. data/lib/fathom/distributions/uniform.rb +0 -35
  76. data/lib/fathom/import.rb +0 -85
  77. data/lib/fathom/import/csv_import.rb +0 -59
  78. data/lib/fathom/import/import_node.rb +0 -17
  79. data/lib/fathom/import/yaml_import.rb +0 -74
  80. data/lib/fathom/knowledge_base.rb +0 -46
  81. data/lib/fathom/knowledge_base/search.rb +0 -19
  82. data/lib/fathom/monte_carlo_set.rb +0 -152
  83. data/lib/fathom/node.rb +0 -139
  84. data/lib/fathom/node/belief_node.rb +0 -121
  85. data/lib/fathom/node/cpm_node.rb +0 -100
  86. data/lib/fathom/node/data_collection.rb +0 -97
  87. data/lib/fathom/node/data_node.rb +0 -22
  88. data/lib/fathom/node/decision.rb +0 -11
  89. data/lib/fathom/node/discrete_node.rb +0 -41
  90. data/lib/fathom/node/fact.rb +0 -24
  91. data/lib/fathom/node/mc_node.rb +0 -70
  92. data/lib/fathom/node/node_extensions/enforced_name.rb +0 -12
  93. data/lib/fathom/node/node_extensions/numeric_methods.rb +0 -68
  94. data/lib/fathom/node/plausible_range.rb +0 -98
  95. data/lib/fathom/simulation.rb +0 -59
  96. data/lib/fathom/simulation/tick_methods.rb +0 -25
  97. data/lib/fathom/simulation/tick_simulation.rb +0 -12
  98. data/lib/fathom/value_description.rb +0 -79
  99. data/lib/options_hash.rb +0 -186
  100. data/spec/ext/array_spec.rb +0 -10
  101. data/spec/ext/faster_csv_spec.rb +0 -10
  102. data/spec/ext/open_struct_spec.rb +0 -20
  103. data/spec/ext/string_spec.rb +0 -7
  104. data/spec/fathom/agent/agent_cluster_spec.rb +0 -17
  105. data/spec/fathom/agent_spec.rb +0 -51
  106. data/spec/fathom/distributions/discrete_gaussian_spec.rb +0 -64
  107. data/spec/fathom/distributions/discrete_uniform_spec.rb +0 -0
  108. data/spec/fathom/distributions/gaussian_spec.rb +0 -64
  109. data/spec/fathom/distributions/uniform_spec.rb +0 -0
  110. data/spec/fathom/import/csv_import_spec.rb +0 -52
  111. data/spec/fathom/import/import_node_spec.rb +0 -10
  112. data/spec/fathom/import/yaml_import_spec.rb +0 -73
  113. data/spec/fathom/import_spec.rb +0 -36
  114. data/spec/fathom/knowledge_base_spec.rb +0 -20
  115. data/spec/fathom/monte_carlo_set_spec.rb +0 -149
  116. data/spec/fathom/node/belief_node_spec.rb +0 -180
  117. data/spec/fathom/node/cpm_node_spec.rb +0 -144
  118. data/spec/fathom/node/data_collection_spec.rb +0 -26
  119. data/spec/fathom/node/data_node_spec.rb +0 -102
  120. data/spec/fathom/node/decision_spec.rb +0 -15
  121. data/spec/fathom/node/discrete_node_spec.rb +0 -56
  122. data/spec/fathom/node/fact_spec.rb +0 -33
  123. data/spec/fathom/node/mc_node_spec.rb +0 -66
  124. data/spec/fathom/node/node_extensions/enforced_name_spec.rb +0 -15
  125. data/spec/fathom/node/node_extensions/numeric_methods_spec.rb +0 -124
  126. data/spec/fathom/node/plausible_range_spec.rb +0 -151
  127. data/spec/fathom/node_spec.rb +0 -172
  128. data/spec/fathom/simulation/tick_simulation_spec.rb +0 -32
  129. data/spec/fathom/simulation_spec.rb +0 -24
  130. data/spec/fathom/value_description_spec.rb +0 -70
  131. data/spec/support/demo.yml +0 -17
  132. data/spec/support/demo_agent.rb +0 -8
  133. data/spec/support/dummy_numeric_node.rb +0 -8
  134. data/spec/support/fact.yml +0 -11
@@ -1,64 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- include Fathom::Distributions
4
-
5
- describe Gaussian do
6
-
7
- before do
8
- @opts = {:mean => 0, :sd => 0.1, :confidence_interval => 0.05}
9
- end
10
-
11
- it "should provide a GSL::Rng through rng" do
12
- Gaussian.rng.should be_a(GSL::Rng)
13
- end
14
-
15
- it "should be able to generate a random variable through GSL::Rng#gaussian" do
16
- Gaussian.rng.should_receive(:gaussian).and_return(0.5)
17
- Gaussian.rand(1)
18
- end
19
-
20
- it "should be able to generate an inverse CDF" do
21
- Gaussian.inverse_cdf(@opts).should be_within(0.00001).of(-0.16448)
22
- end
23
-
24
- it "should require mean as an option for an inverse CDF" do
25
- @opts.delete(:mean)
26
- lambda{Gaussian.inverse_cdf(@opts)}.should raise_error
27
- end
28
-
29
- it "should require a Gaussian deviation for an inverse CDF" do
30
- @opts.delete(:sd)
31
- lambda{Gaussian.inverse_cdf(@opts)}.should raise_error
32
- end
33
-
34
- it "should take std as an alias for sd when creating an inverse CDF" do
35
- @opts.delete(:sd)
36
- Gaussian.inverse_cdf(@opts.merge(:std => 0.1)).should be_within(0.00001).of(-0.16448)
37
- end
38
-
39
- it "should take standard_deviation as an alias for sd when creating an inverse CDF" do
40
- @opts.delete(:sd)
41
- Gaussian.inverse_cdf(@opts.merge(:standard_deviation => 0.1)).should be_within(0.00001).of(-0.16448)
42
- end
43
-
44
- it "should be able to set upper to true and get Q instead of P" do
45
- Gaussian.inverse_cdf(@opts.merge(:upper =>true)).should be_within(0.00001).of(0.16448)
46
- end
47
-
48
- it "should be able to take a different confidence interval" do
49
- Gaussian.inverse_cdf(@opts.merge(:confidence_interval => 0.1)).should be_within(0.00001).of(-0.12815)
50
- end
51
-
52
- it "should have a lower_bound alias for inverse_cdf" do
53
- Gaussian.lower_bound(@opts).should eql(Gaussian.inverse_cdf(@opts))
54
- end
55
-
56
- it "should have an upper_bound shortcut for inverse_cdf(:upper => true, ...)" do
57
- Gaussian.upper_bound(@opts).should eql(Gaussian.inverse_cdf(@opts.merge(:upper => true)))
58
- end
59
-
60
- it "should provide interval values, an array of the lower and upper bounds" do
61
- Gaussian.interval_values(@opts.merge(:confidence_interval => 0.9)).should eql([Gaussian.lower_bound(@opts), Gaussian.upper_bound(@opts)])
62
- end
63
- end
64
-
File without changes
@@ -1,52 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe CSVImport do
6
-
7
- before do
8
- @content =<<-END
9
- this,and,that
10
- 1,2,3
11
- 4,5,6
12
- 7,8,9
13
- END
14
-
15
- @opts = {:content => @content}
16
- @ci = CSVImport.new(@opts)
17
- @result = @ci.import
18
- end
19
-
20
- it "should not work unless content is set" do
21
- lambda{CSVImport.new.import}.should raise_error(NoMethodError)
22
- lambda{CSVImport.new(@opts)}.should_not raise_error
23
- end
24
-
25
- it "should return the ImportNode as the result" do
26
- @result.should be_a(ImportNode)
27
- end
28
-
29
- it "should create as many data nodes as there are columns" do
30
- @result.children.size.should eql(3)
31
- @result.children.each {|dn| dn.should be_a(DataNode)}
32
- end
33
-
34
- it "should import the values from each column into each data node" do
35
- @result.this.values.should eql([1,4,7])
36
- @result.and.values.should eql([2,5,8])
37
- @result.that.values.should eql([3,6,9])
38
- end
39
-
40
- # KB Override
41
- # it "should store the imported values in the knowledge base" do
42
- # Fathom.knowledge_base[:this].should be_a(DataNode)
43
- # Fathom.kb[:this].values.should eql([1,4,7])
44
- # end
45
-
46
- # KB Override
47
- # it "should import from the class level" do
48
- # CSVImport.import(@opts)
49
- # Fathom.knowledge_base[:this].should be_a(DataNode)
50
- # Fathom.kb[:this].values.should eql([1,4,7])
51
- # end
52
- end
@@ -1,10 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe ImportNode do
6
- it "should record the time of the import" do
7
- i = ImportNode.new
8
- i.imported_at.should be_within(0.01).of(Time.now)
9
- end
10
- end
@@ -1,73 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe YAMLImport do
6
-
7
- before do
8
- @demo_yaml_location = File.expand_path(File.dirname(__FILE__) + "/../../support/demo.yml")
9
- @demo_yaml = open(@demo_yaml_location).read
10
- @opts = {:content => @demo_yaml}
11
- @yi = YAMLImport.new(@opts)
12
- @result = @yi.import
13
- end
14
-
15
- it "should not work unless content is set" do
16
- lambda{YAMLImport.new.import}.should raise_error
17
- lambda{YAMLImport.new(@opts)}.should_not raise_error
18
- end
19
-
20
- it "should create an ImportNode as a return value" do
21
- @result.should be_an(ImportNode)
22
- end
23
-
24
- it "should create PlausibleRange nodes for any hashes with at least a min and max key in it" do
25
- @result.co2_emissions.should_not be_nil
26
- end
27
-
28
- it "should not create a PlausibleRange for entries missing min and max" do
29
- @result.should_not respond_to(:invalid_hash)
30
- end
31
-
32
- it "should be able to create a PlausibleRange with more complete information" do
33
- @result.more_complete_range.ci.should eql(0.6)
34
- @result.more_complete_range.description.should eql('Some good description')
35
- end
36
-
37
- it "should create DataNodes for entries that have an array of information" do
38
- @result.co2_readings.should be_a(DataNode)
39
- @result.co2_readings.values.should eql([10,20,30])
40
- end
41
-
42
- # KB Override
43
- # it "should store the imported values in the knowledge base" do
44
- # Fathom.knowledge_base['CO2 Emissions'].should be_a(PlausibleRange)
45
- # Fathom.kb['CO2 Emissions'].min.should eql(1_000_000)
46
- # end
47
-
48
- # KB Override
49
- # it "should import from the class level" do
50
- # YAMLImport.import(@opts)
51
- # Fathom.knowledge_base['CO2 Emissions'].should be_a(PlausibleRange)
52
- # Fathom.kb['CO2 Emissions'].min.should eql(1_000_000)
53
- # end
54
-
55
- it "should not complain if there's an empty YAML file" do
56
- filename = '/tmp/empty_yaml_test.yml'
57
- File.open(filename, 'w') {|f| f.puts ''}
58
- lambda{YAMLImport.import(:content => filename)}.should_not raise_error
59
- `rm -rf #{filename}`
60
- end
61
-
62
- it "should create a Fact if it is an object named fact and has a name and value function" do
63
- filename = File.expand_path(File.dirname(__FILE__) + "/../../support/fact.yml")
64
- yaml = open(filename).read
65
- opts = {:content => yaml}
66
- yi = YAMLImport.new(opts)
67
- result = yi.import
68
- result.one.value.should eql(1)
69
- result.two.value.should eql(2)
70
- result.three.value.should eql(3)
71
- end
72
-
73
- end
@@ -1,36 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe Import do
6
-
7
- before do
8
- @content = 'some content'
9
- @options = OptionsHash.new({:content => @content})
10
- @i = Import.new(@options)
11
- @values = [1,2,3,4,5]
12
- end
13
-
14
- it "should initialize with optional content" do
15
- @i.content.should eql(@content)
16
- end
17
-
18
- it "should record the initialization options" do
19
- @i.options.should eql(@options)
20
- end
21
-
22
- it "should have a class-level import method" do
23
- Import.should be_respond_to(:import)
24
- end
25
-
26
- it "should create an import node to attach its imports to in the knowledge base" do
27
- @i.import_node.should be_a(ImportNode)
28
- end
29
-
30
- it "should pass its options to the import node for that node to record the parts it is interested in." do
31
- i = Import.new(:name => "New Import", :content => @content, :description => "This gets passed along too.")
32
- i.import_node.name.should eql("New Import")
33
- i.import_node.description.should eql("This gets passed along too.")
34
- end
35
-
36
- end
@@ -1,20 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe KnowledgeBase do
6
-
7
- before do
8
- @kb = KnowledgeBase.new
9
- end
10
-
11
- it "should have a find accessor" do
12
- KnowledgeBase.should respond_to(:find)
13
- end
14
-
15
- # it "should be able to add a node" do
16
- # @dn = DataNode.new(:name => :new_node, :values => [1,2,3])
17
- # @kb[:new_node] = @dn
18
- # @kb[:new_node].should eql(@dn)
19
- # end
20
- end
@@ -1,149 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe MonteCarloSet do
6
-
7
- before(:all) do
8
- @q1_sales = PlausibleRange.new(:min => 10, :max => 20, :hard_lower_bound => 0, :name => "First Quarter Sales")
9
- @q1_prices = PlausibleRange.new(:min => 10_000, :max => 12_000, :name => "First Quarter Prices")
10
- @q1_sales_commissions = PlausibleRange.new(:min => 0.2, :max => 0.2, :name => "Sales Commission Rate")
11
-
12
- @q1_gross_margins = ValueDescription.new(@q1_sales, @q1_prices, @q1_sales_commissions) do |random_sample|
13
- revenue = (random_sample.first_quarter_sales * random_sample.first_quarter_prices)
14
- commissions_paid = random_sample.sales_commission_rate * revenue
15
- gross_margins = revenue - commissions_paid
16
- {:revenue => revenue, :commissions_paid => commissions_paid, :gross_margins => gross_margins}
17
- end
18
-
19
- @fields = [:commissions_paid, :gross_margins, :revenue]
20
- @summary_fields = [:coefficient_of_variation, :lower_bound, :max, :mean, :min, :sd, :upper_bound]
21
- end
22
-
23
- before do
24
- @mcs = MonteCarloSet.new(@q1_gross_margins)
25
- end
26
-
27
- it "should initialize with a ValueDescription" do
28
- lambda{MonteCarloSet.new}.should raise_error
29
- lambda{MonteCarloSet.new(@q1_gross_margins)}.should_not raise_error
30
- end
31
-
32
- it "should expose the value_description" do
33
- @mcs.value_description.should eql(@q1_gross_margins)
34
- end
35
-
36
- it "should process with the default number of runs at 10,000", :slow => true do
37
- lambda{@mcs.process}.should_not raise_error
38
- @mcs.samples_taken.should eql(10_000)
39
- end
40
-
41
- it "should be able to process with a specified number of runs" do
42
- @mcs.process(3)
43
- @mcs.samples_taken.should eql(3)
44
- end
45
-
46
- it "should define lookup methods for all keys in the result set" do
47
- @mcs.process(1)
48
- @mcs.revenue.should be_a(GSL::Vector)
49
- @mcs.revenue.length.should eql(1)
50
- @mcs.commissions_paid.should be_a(GSL::Vector)
51
- @mcs.commissions_paid.length.should eql(1)
52
- @mcs.gross_margins.should be_a(GSL::Vector)
53
- @mcs.gross_margins.length.should eql(1)
54
- end
55
-
56
- it "should be resetable" do
57
- @mcs.process(1)
58
- @mcs.reset!
59
- lambda{@mcs.process(1)}.should_not raise_error
60
- end
61
-
62
- it "should expose the fields from the samples" do
63
- @mcs.process(1)
64
- sort_array_of_symbols(@mcs.fields).should eql(@fields)
65
- end
66
-
67
- it "should offer a summary of the fields" do
68
- @mcs.process(1)
69
- summary = @mcs.summary
70
- summary.should be_a(Hash)
71
- sort_array_of_symbols(summary.keys).should eql(@fields)
72
- summary.each do |key, value|
73
- value.should be_a(Hash)
74
- sort_array_of_symbols(value.keys).should eql(@summary_fields)
75
- end
76
- end
77
-
78
- it "should be able to summarize a single field" do
79
- @mcs.process(2)
80
- summary = @mcs.summary(:revenue)
81
- summary.should be_a(Hash)
82
- sort_array_of_symbols(summary.keys).should eql(@summary_fields)
83
- summary[:coefficient_of_variation].should eql(@mcs.revenue.sd / @mcs.revenue.mean)
84
- summary[:max].should eql(@mcs.revenue.max)
85
- summary[:min].should eql(@mcs.revenue.min)
86
- summary[:sd].should eql(@mcs.revenue.sd)
87
- end
88
-
89
- it "should define summary methods on the object" do
90
- @mcs.process(2)
91
- @mcs.revenue_summary.should eql(@mcs.summary(:revenue))
92
- end
93
-
94
- it "should allow the model to be able to produce non-array values" do
95
- margin_description = ValueDescription.new(@q1_sales) do |s|
96
- {
97
- :description => {:first_quarter_sales => s.first_quarter_sales, :first_quarter_prices => s.first_quarter_prices}
98
- }
99
- end
100
- mcs = MonteCarloSet.new(margin_description)
101
- lambda{mcs.process(2)}.should_not raise_error
102
- lambda{mcs.summary}.should_not raise_error
103
- end
104
-
105
- it "should set the lower bound in the summary to be no more than the minimum (when hard_lower_bound truncates the curve)" do
106
- @q1_sales = PlausibleRange.new(:min => 0, :max => 2, :hard_lower_bound => 0, :name => "First Quarter Sales")
107
- @q1_prices = PlausibleRange.new(:min => 1, :max => 1, :name => "First Quarter Prices")
108
- @q1_sales_commissions = PlausibleRange.new(:min => 0.2, :max => 0.2, :name => "Sales Commission Rate")
109
-
110
- @q1_gross_margins = ValueDescription.new(@q1_sales, @q1_prices, @q1_sales_commissions) do |random_sample|
111
- revenue = (random_sample.first_quarter_sales * random_sample.first_quarter_prices)
112
- commissions_paid = random_sample.sales_commission_rate * revenue
113
- gross_margins = revenue - commissions_paid
114
- {:revenue => revenue, :commissions_paid => commissions_paid, :gross_margins => gross_margins}
115
- end
116
- @mcs = MonteCarloSet.new(@q1_gross_margins)
117
- @mcs.process(5)
118
- # This is an environment where the lower bound would usually be below the minimum.
119
- # So, the minimum adheres to the hard_lower_bound constraints in the plausible range (tested elsewhere)
120
- # and now we're expecting the lower bound here to reflect an actual minimum, or a 5% confidence interval,
121
- # whichever is higher.
122
- (@mcs.summary[:revenue][:lower_bound] >= @mcs.revenue.min).should be_true
123
- end
124
-
125
- it "should set the upper bound in the summary to be no more than the minimum (when hard_upper_bound truncates the curve)" do
126
- @q1_sales = PlausibleRange.new(:min => 0, :max => 2, :hard_upper_bound => 2, :name => "First Quarter Sales")
127
- @q1_prices = PlausibleRange.new(:min => 1, :max => 1, :name => "First Quarter Prices")
128
- @q1_sales_commissions = PlausibleRange.new(:min => 0.2, :max => 0.2, :name => "Sales Commission Rate")
129
-
130
- @q1_gross_margins = ValueDescription.new(@q1_sales, @q1_prices, @q1_sales_commissions) do |random_sample|
131
- revenue = (random_sample.first_quarter_sales * random_sample.first_quarter_prices)
132
- commissions_paid = random_sample.sales_commission_rate * revenue
133
- gross_margins = revenue - commissions_paid
134
- {:revenue => revenue, :commissions_paid => commissions_paid, :gross_margins => gross_margins}
135
- end
136
- @mcs = MonteCarloSet.new(@q1_gross_margins)
137
- @mcs.process(5)
138
- # This is an environment where the upper bound would usually be above the maximum
139
- # So, the maximum adheres to the hard_upper_bound constraints in the plausible range (tested elsewhere)
140
- # and now we're expecting the upper bound here to reflect an actual maximum, or a 95% confidence interval,
141
- # whichever is higher.
142
- (@mcs.summary[:revenue][:upper_bound] <= @mcs.revenue.max).should be_true
143
- end
144
-
145
- end
146
-
147
- def sort_array_of_symbols(array)
148
- array.map {|e| e.to_s}.sort.map {|e| e.to_sym}
149
- end
@@ -1,180 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- include Fathom
4
-
5
- describe BeliefNode do
6
-
7
- before do
8
- @bn = BeliefNode.new(:labels => [:red, :green, :blue])
9
- end
10
-
11
- it "should be a Node" do
12
- BeliefNode.ancestors.should be_include(Node)
13
- end
14
-
15
- it "should be a DiscreteNode as well" do
16
- BeliefNode.ancestors.should be_include(DiscreteNode)
17
- end
18
-
19
- it "should initialize with a uniform probability distribution" do
20
- @bn.probabilities.should ==(GSL::Vector[1.0/3, 1.0/3, 1.0/3])
21
- end
22
-
23
- it "should allow a pre-defined probability distribution" do
24
- @bn = BeliefNode.new(:labels => [:red, :green, :blue], :probabilities => [0.1, 0.2, 0.7])
25
- @bn.probabilities.should ==(GSL::Vector[0.1, 0.2, 0.7])
26
- end
27
-
28
- it "should raise an error if the supplied probability distribution is of the wrong size" do
29
- lambda{BeliefNode.new(:labels => [:red, :green, :blue], :probabilities => [0.1, 0.2, 0.7, 0.1])}.should raise_error(/Probabilities must be 3/)
30
- lambda{BeliefNode.new(:labels => [:red, :green, :blue], :probabilities => [0.1, 0.2])}.should raise_error(/Probabilities must be 3/)
31
- end
32
-
33
- it "should normalize even the supplied probabilities" do
34
- @bn = BeliefNode.new(:labels => [:red, :green, :blue], :probabilities => [1, 2, 7])
35
- @bn.probabilities.should ==(GSL::Vector[0.1, 0.2, 0.7])
36
- end
37
-
38
- it "should initialize with a uniform likelihood vector" do
39
- @bn.likelihoods.should ==(GSL::Vector[1,1,1])
40
- end
41
-
42
- it "should allow pre-defined likelihoods to be supplied" do
43
- @bn = BeliefNode.new(:labels => [:red, :green, :blue], :likelihoods => [1,1,2])
44
- @bn.likelihoods.should ==(GSL::Vector[1,1,2])
45
- end
46
-
47
- it "should raise an error if the supplied likelihoods are the wrong size" do
48
- lambda{BeliefNode.new(:labels => [:red, :green, :blue], :likelihoods => [1,1,2,1])}.should raise_error(/Likelihoods must be 3/)
49
- lambda{BeliefNode.new(:labels => [:red, :green, :blue], :likelihoods => [1,1])}.should raise_error(/Likelihoods must be 3/)
50
- end
51
-
52
- it "should establish a precision threshold at 1.0e-05" do
53
- @bn.precision_threshold.should eql(0.00001)
54
- end
55
-
56
- it "should allow the precision_threshold as a parameter" do
57
- @bn = BeliefNode.new(:labels => [:red, :green, :blue], :precision_threshold => 0.01)
58
- @bn.precision_threshold.should eql(0.01)
59
- end
60
-
61
- it "should allow a initialization from a values hash" do
62
- @bn = BeliefNode.new(:values => {:red => 0.1, :green => 0.2, :yellow => 0.3, :blue => 0.4})
63
- @bn.probabilities.to_a.sort.should eql([0.1, 0.2, 0.3, 0.4])
64
- @bn.labels.map(&:to_s).sort.should eql(%w(blue green red yellow))
65
- end
66
-
67
- context "CPMNodes" do
68
- before do
69
- @b1 = BeliefNode.new(:name => :person, :values => {:david => 0.4, :adina => 0.6})
70
- @b2 = BeliefNode.new(:name => :movie_preference, :values => {:drama => 0.5, :comedy => 0.4, :romance => 0.1})
71
- end
72
-
73
- it "should automatically create an intermediary cpm when adding a child" do
74
- @b1.add_child(@b2)
75
- @b1.children.first.should be_a(CPMNode)
76
- @b1.children.first.child.should eql(@b2)
77
- @b2.parents.first.should be_a(CPMNode)
78
- @b2.parents.first.parent.should eql(@b1)
79
- @cpm = @b1.children.first
80
- @cpm.should eql(@b2.parents.first)
81
- end
82
-
83
- it "should create an accessor for the child, rather than the cpm" do
84
- @b1.add_child(@b2)
85
- @b1.should_not be_respond_to(:cpm)
86
- @b1.should respond_to(:movie_preference)
87
- @b1.movie_preference.should eql(@b2)
88
- end
89
-
90
- it "should create a cpm_for_* accessor for the cpm" do
91
- @b1.add_child(@b2)
92
- @b1.should respond_to(:cpm_for_movie_preference)
93
- @b1.cpm_for_movie_preference.should eql(@b1.children.first)
94
- end
95
-
96
- it "should create a cpm_for_* accessor for the child" do
97
- @b1.add_child(@b2)
98
- @b2.should respond_to(:cpm_for_person)
99
- @b2.cpm_for_person.should eql(@b2.parents.first)
100
- @b2.cpm_for_person.should eql(@b1.children.first)
101
- end
102
-
103
- it "should automatically create an intermediary cpm when adding a parent" do
104
- @b2.add_parent(@b1)
105
- @b1.children.first.should be_a(CPMNode)
106
- @b1.children.first.child.should eql(@b2)
107
- @b2.parents.first.should be_a(CPMNode)
108
- @b2.parents.first.parent.should eql(@b1)
109
- @cpm = @b1.children.first
110
- @cpm.should eql(@b2.parents.first)
111
- end
112
-
113
- it "should create an accessor for the parent, rather than the cpm" do
114
- @b2.add_parent(@b1)
115
- @b2.should_not be_respond_to(:cpm)
116
- @b2.should respond_to(:person)
117
- @b2.person.should eql(@b1)
118
- end
119
-
120
- it "should create a cpm_for_* accessor for the cpm" do
121
- @b2.add_parent(@b1)
122
- @b2.should respond_to(:cpm_for_person)
123
- @b2.cpm_for_person.should eql(@b2.parents.first)
124
- end
125
-
126
- it "should create an inspect string that reflects the grandchild, rather than the cpm" do
127
- @b1.add_child(@b2)
128
- @b1.inspect.should eql("Fathom::BeliefNode: person, children:, [\"movie_preference (Fathom::BeliefNode)\", \"movie_preference (Fathom::BeliefNode)\"], parents: , []")
129
- end
130
-
131
- it "should create an inspect string that reflects the grandparent, rather than the cpm" do
132
- @b2.add_parent(@b1)
133
- @b2.inspect.should eql("Fathom::BeliefNode: movie_preference, children:, [], parents: , [\"person (Fathom::BeliefNode)\", \"person (Fathom::BeliefNode)\"]")
134
- end
135
-
136
- it "should create a cpm_for_* accessor for the parent" do
137
- @b2.add_parent(@b1)
138
- @b1.should respond_to(:cpm_for_movie_preference)
139
- @b1.cpm_for_movie_preference.should eql(@b1.children.first)
140
- @b1.cpm_for_movie_preference.should eql(@b2.parents.first)
141
- end
142
-
143
- end
144
-
145
- context "Belief and Propagation" do
146
-
147
- before do
148
-
149
- @season = BeliefNode.new(:name => :season, :labels => [:winter, :spring, :summer, :fall])
150
- @sprinkler = BeliefNode.new(:name => :sprinkler)
151
- @rain = BeliefNode.new(:name => :rain)
152
- @wet = BeliefNode.new(:name => :wet)
153
- @slippery = BeliefNode.new(:name => :slippery)
154
-
155
- @season.add_child(@sprinkler)
156
- @sprinkler.add_child(@wet)
157
- @season.add_child(@rain)
158
- @rain.add_child(@wet)
159
- @wet.add_child(@slippery)
160
-
161
- end
162
-
163
- it "should provide the likelihood for a particular value" do
164
- @sprinkler.should be_respond_to(:likelihood)
165
- output = @sprinkler.likelihood(:true)
166
- output.should be_a(OpenStruct)
167
- # @sprinkler.parents.map(&:name_sym).each {|key| output.table.keys.should be_include(key)}
168
- # output.
169
- # output.table.keys.should eql([:matrix, :parents])
170
- end
171
- end
172
-
173
- # What's next:
174
- # Deal with multiple parents and the matrix that comes in
175
- # Propagate the likelihoods and probabilities up and down
176
- # Calculate the belief
177
- # Update the information directly on the node and propagate
178
- # Receive an update from a child or parent and propagate
179
- # Adhere to the precision_threshold when propagating
180
- end