fathom 0.3.7 → 0.5.0

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