fathom 0.3.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +7 -5
- data/.document +2 -2
- data/Gemfile +9 -10
- data/{LICENSE → LICENSE.txt} +1 -1
- data/README.md +29 -90
- data/Rakefile +34 -32
- data/VERSION +1 -1
- data/fathom.gemspec +105 -0
- data/features/fathom.feature +26 -0
- data/features/step_definitions/fathom_steps.rb +23 -0
- data/features/support/env.rb +13 -0
- data/lib/ext/array.rb +6 -2
- data/lib/ext/string.rb +86 -7
- data/lib/fathom.rb +51 -88
- data/lib/fathom/behaviors/attribute_system.rb +91 -0
- data/lib/fathom/behaviors/context_behavior.rb +28 -0
- data/lib/fathom/behaviors/plugins.rb +16 -0
- data/lib/fathom/contexts/network_population.rb +47 -0
- data/lib/fathom/contexts/network_traversal.rb +4 -0
- data/lib/fathom/data/adjacency_matrix.rb +27 -0
- data/lib/fathom/data/definition.rb +22 -0
- data/lib/fathom/data/edge.rb +58 -0
- data/lib/fathom/data/network.rb +35 -0
- data/lib/fathom/data/outcome.rb +30 -0
- data/lib/fathom/data/property.rb +31 -0
- data/lib/fathom/data/variable.rb +59 -0
- data/lib/fathom/roles/general_graph_tools.rb +87 -0
- data/lib/fathom/roles/network_builder.rb +61 -0
- data/spec/fathom/behaviors/attribute_system_spec.rb +141 -0
- data/spec/fathom/behaviors/context_behavior_spec.rb +15 -0
- data/spec/fathom/behaviors/plugins_spec.rb +80 -0
- data/spec/fathom/contexts/network_population_spec.rb +55 -0
- data/spec/fathom/contexts/network_traversal_spec.rb +11 -0
- data/spec/fathom/data/adjacency_matrix_spec.rb +42 -0
- data/spec/fathom/data/definition_spec.rb +19 -0
- data/spec/fathom/data/edge_spec.rb +77 -0
- data/spec/fathom/data/network_spec.rb +72 -0
- data/spec/fathom/data/outcome_spec.rb +17 -0
- data/spec/fathom/data/property_spec.rb +17 -0
- data/spec/fathom/data/variable_spec.rb +101 -0
- data/spec/fathom/ext/array_spec.rb +17 -0
- data/spec/fathom/ext/string_spec.rb +90 -0
- data/spec/fathom/roles/general_graph_tools_spec.rb +95 -0
- data/spec/fathom/roles/network_builder_spec.rb +90 -0
- data/spec/fathom_spec.rb +28 -49
- data/spec/spec_helper.rb +7 -11
- data/spec/support/context_behavior.rb +14 -0
- data/spec/support/custom_matchers.rb +12 -0
- data/spec/support/files.rb +8 -0
- data/spec/support/network.yml +42 -0
- metadata +133 -174
- data/.bundle/config +0 -2
- data/.gitignore +0 -6
- data/Gemfile.lock +0 -42
- data/TODO.md +0 -127
- data/autotest/discover.rb +0 -1
- data/lib/ext/faster_csv.rb +0 -1
- data/lib/ext/open_struct.rb +0 -17
- data/lib/fathom/agent.rb +0 -48
- data/lib/fathom/agent/agent_cluster.rb +0 -23
- data/lib/fathom/agent/properties.rb +0 -48
- data/lib/fathom/archive/causal_graph.rb +0 -12
- data/lib/fathom/archive/concept.rb +0 -83
- data/lib/fathom/archive/conditional_probability_matrix.rb +0 -119
- data/lib/fathom/archive/inverter.rb +0 -20
- data/lib/fathom/archive/n2.rb +0 -198
- data/lib/fathom/archive/n3.rb +0 -119
- data/lib/fathom/archive/node.rb +0 -97
- data/lib/fathom/archive/noodle.rb +0 -136
- data/lib/fathom/archive/scratch.rb +0 -45
- data/lib/fathom/distributions.rb +0 -8
- data/lib/fathom/distributions/discrete_gaussian.rb +0 -44
- data/lib/fathom/distributions/discrete_uniform.rb +0 -25
- data/lib/fathom/distributions/gaussian.rb +0 -46
- data/lib/fathom/distributions/uniform.rb +0 -35
- data/lib/fathom/import.rb +0 -85
- data/lib/fathom/import/csv_import.rb +0 -59
- data/lib/fathom/import/import_node.rb +0 -17
- data/lib/fathom/import/yaml_import.rb +0 -74
- data/lib/fathom/knowledge_base.rb +0 -46
- data/lib/fathom/knowledge_base/search.rb +0 -19
- data/lib/fathom/monte_carlo_set.rb +0 -152
- data/lib/fathom/node.rb +0 -139
- data/lib/fathom/node/belief_node.rb +0 -121
- data/lib/fathom/node/cpm_node.rb +0 -100
- data/lib/fathom/node/data_collection.rb +0 -97
- data/lib/fathom/node/data_node.rb +0 -22
- data/lib/fathom/node/decision.rb +0 -11
- data/lib/fathom/node/discrete_node.rb +0 -41
- data/lib/fathom/node/fact.rb +0 -24
- data/lib/fathom/node/mc_node.rb +0 -70
- data/lib/fathom/node/node_extensions/enforced_name.rb +0 -12
- data/lib/fathom/node/node_extensions/numeric_methods.rb +0 -68
- data/lib/fathom/node/plausible_range.rb +0 -98
- data/lib/fathom/simulation.rb +0 -59
- data/lib/fathom/simulation/tick_methods.rb +0 -25
- data/lib/fathom/simulation/tick_simulation.rb +0 -12
- data/lib/fathom/value_description.rb +0 -79
- data/lib/options_hash.rb +0 -186
- data/spec/ext/array_spec.rb +0 -10
- data/spec/ext/faster_csv_spec.rb +0 -10
- data/spec/ext/open_struct_spec.rb +0 -20
- data/spec/ext/string_spec.rb +0 -7
- data/spec/fathom/agent/agent_cluster_spec.rb +0 -17
- data/spec/fathom/agent_spec.rb +0 -51
- data/spec/fathom/distributions/discrete_gaussian_spec.rb +0 -64
- data/spec/fathom/distributions/discrete_uniform_spec.rb +0 -0
- data/spec/fathom/distributions/gaussian_spec.rb +0 -64
- data/spec/fathom/distributions/uniform_spec.rb +0 -0
- data/spec/fathom/import/csv_import_spec.rb +0 -52
- data/spec/fathom/import/import_node_spec.rb +0 -10
- data/spec/fathom/import/yaml_import_spec.rb +0 -73
- data/spec/fathom/import_spec.rb +0 -36
- data/spec/fathom/knowledge_base_spec.rb +0 -20
- data/spec/fathom/monte_carlo_set_spec.rb +0 -149
- data/spec/fathom/node/belief_node_spec.rb +0 -180
- data/spec/fathom/node/cpm_node_spec.rb +0 -144
- data/spec/fathom/node/data_collection_spec.rb +0 -26
- data/spec/fathom/node/data_node_spec.rb +0 -102
- data/spec/fathom/node/decision_spec.rb +0 -15
- data/spec/fathom/node/discrete_node_spec.rb +0 -56
- data/spec/fathom/node/fact_spec.rb +0 -33
- data/spec/fathom/node/mc_node_spec.rb +0 -66
- data/spec/fathom/node/node_extensions/enforced_name_spec.rb +0 -15
- data/spec/fathom/node/node_extensions/numeric_methods_spec.rb +0 -124
- data/spec/fathom/node/plausible_range_spec.rb +0 -151
- data/spec/fathom/node_spec.rb +0 -172
- data/spec/fathom/simulation/tick_simulation_spec.rb +0 -32
- data/spec/fathom/simulation_spec.rb +0 -24
- data/spec/fathom/value_description_spec.rb +0 -70
- data/spec/support/demo.yml +0 -17
- data/spec/support/demo_agent.rb +0 -8
- data/spec/support/dummy_numeric_node.rb +0 -8
- data/spec/support/fact.yml +0 -11
File without changes
|
@@ -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,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
|
data/spec/fathom/import_spec.rb
DELETED
@@ -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
|