fathom 0.3.0 → 0.3.1
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.
- data/.autotest +10 -0
- data/Gemfile +10 -2
- data/Gemfile.lock +8 -0
- data/TODO.md +12 -25
- data/VERSION +1 -1
- data/lib/{fathom/ext → ext}/array.rb +0 -0
- data/lib/{fathom/ext → ext}/faster_csv.rb +0 -0
- data/lib/{fathom/ext → ext}/open_struct.rb +0 -0
- data/lib/{fathom/ext → ext}/string.rb +0 -0
- data/lib/fathom.rb +16 -13
- data/lib/fathom/agent.rb +8 -9
- data/lib/fathom/{causal_graph.rb → archive/causal_graph.rb} +0 -0
- data/lib/fathom/{concept.rb → archive/concept.rb} +0 -0
- data/lib/fathom/archive/conditional_probability_matrix.rb +3 -0
- data/lib/fathom/{inverter.rb → archive/inverter.rb} +0 -0
- data/lib/fathom/archive/node.rb +24 -1
- data/lib/fathom/distributions/discrete_uniform.rb +11 -32
- data/lib/fathom/import.rb +37 -34
- data/lib/fathom/import/yaml_import.rb +22 -1
- data/lib/fathom/knowledge_base.rb +34 -23
- data/lib/fathom/knowledge_base/search.rb +19 -0
- data/lib/fathom/node.rb +32 -1
- data/lib/fathom/node/belief_node.rb +121 -0
- data/lib/fathom/node/cpm_node.rb +100 -0
- data/lib/fathom/node/data_collection.rb +97 -0
- data/lib/fathom/{data_node.rb → node/data_node.rb} +1 -1
- data/lib/fathom/{value_aggregator.rb → node/decision.rb} +5 -5
- data/lib/fathom/node/discrete_node.rb +41 -0
- data/lib/fathom/node/fact.rb +24 -0
- data/lib/fathom/{mc_node.rb → node/mc_node.rb} +1 -1
- data/lib/fathom/{enforced_name.rb → node/node_extensions/enforced_name.rb} +1 -1
- data/lib/fathom/{numeric_methods.rb → node/node_extensions/numeric_methods.rb} +19 -1
- data/lib/fathom/{plausible_range.rb → node/plausible_range.rb} +1 -1
- data/spec/ext/array_spec.rb +10 -0
- data/spec/ext/faster_csv_spec.rb +10 -0
- data/spec/ext/open_struct_spec.rb +20 -0
- data/spec/ext/string_spec.rb +7 -0
- data/spec/fathom/import/csv_import_spec.rb +11 -9
- data/spec/fathom/import/yaml_import_spec.rb +27 -7
- data/spec/fathom/knowledge_base_spec.rb +8 -4
- data/spec/fathom/node/belief_node_spec.rb +180 -0
- data/spec/fathom/node/cpm_node_spec.rb +144 -0
- data/spec/fathom/node/data_collection_spec.rb +26 -0
- data/spec/fathom/{data_node_spec.rb → node/data_node_spec.rb} +1 -1
- data/spec/fathom/node/decision_spec.rb +15 -0
- data/spec/fathom/node/discrete_node_spec.rb +56 -0
- data/spec/fathom/node/fact_spec.rb +33 -0
- data/spec/fathom/{mc_node_spec.rb → node/mc_node_spec.rb} +1 -1
- data/spec/fathom/{enforced_name_spec.rb → node/node_extensions/enforced_name_spec.rb} +1 -1
- data/spec/fathom/{numeric_methods_spec.rb → node/node_extensions/numeric_methods_spec.rb} +53 -11
- data/spec/fathom/{plausible_range_spec.rb → node/plausible_range_spec.rb} +1 -1
- data/spec/fathom/node_spec.rb +17 -0
- data/spec/fathom_spec.rb +40 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/fact.yml +11 -0
- metadata +57 -30
- data/lib/fathom/value_multiplier.rb +0 -18
@@ -0,0 +1,144 @@
|
|
1
|
+
require File.expand_path('../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe CPMNode do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@child = BeliefNode.new(:name => :color, :values => {:red => 0.4, :green => 0.6})
|
7
|
+
@parent = BeliefNode.new(:name => :child, :values => {:asher => 0.2, :stella => 0.8})
|
8
|
+
@cpm = CPMNode.new(:child => @child, :parent => @parent)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should subclass Node" do
|
12
|
+
CPMNode.ancestors[1].should eql(Node)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should allow a BeliefNode child and parent" do
|
16
|
+
@cpm.parent.should eql(@parent)
|
17
|
+
@cpm.child.should eql(@child)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should generate a conditional probability matrix from the parent and child" do
|
21
|
+
parent_values = @cpm.parent.probabilities.col
|
22
|
+
child_values = @cpm.child.probabilities
|
23
|
+
matrix = parent_values * child_values
|
24
|
+
@cpm.values.to_a.should eql(matrix.to_a)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should alias the values with matrix" do
|
28
|
+
@cpm.values.should eql(@cpm.matrix)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise an error when trying to load non-BeliefNode parent or child" do
|
32
|
+
lambda{CPMNode.new(:child => :not_a_belief_node, :parent => @parent)}.should raise_error(/must be a BeliefNode/)
|
33
|
+
lambda{CPMNode.new(:child => @child, :parent => :not_a_belief_node)}.should raise_error(/must be a BeliefNode/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have a probability lookup" do
|
37
|
+
@cpm.should be_respond_to(:probability)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should only allow options for the parent and child names" do
|
41
|
+
lambda{@cpm.probability :not_a_link => true}.should raise_error(/unknown/i)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be able to lookup the values we're looking for" do
|
45
|
+
# The Matrix looks like this:
|
46
|
+
# GSL::Matrix
|
47
|
+
# [ 8.000e-02 1.200e-01
|
48
|
+
# 3.200e-01 4.800e-01 ]
|
49
|
+
@cpm.probability(:color => :red, :child => :asher).should be_close(0.08, 1e-10)
|
50
|
+
@cpm.probability(:color => :red, :child => :stella).should be_close(0.32, 1e-10)
|
51
|
+
@cpm.probability(:color => :green, :child => :asher).should be_close(0.12, 1e-10)
|
52
|
+
@cpm.probability(:color => :green, :child => :stella).should be_close(0.48, 1e-10)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should look for all children values, if no child is provided" do
|
56
|
+
@cpm.probability(:child => :asher).should be_close(0.2, 1e-10)
|
57
|
+
@cpm.probability(:child => :stella).should be_close(0.8, 1e-10)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should look for all parent values, if no parent is provided" do
|
61
|
+
@cpm.probability(:color => :red).should be_close(0.4, 1e-10)
|
62
|
+
@cpm.probability(:color => :green).should be_close(0.6, 1e-10)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should provide a probability of 1 if no filter is set" do
|
66
|
+
@cpm.probability.should be_close(1.0, 1e-10)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should give a description in a hash, if description is turned on" do
|
70
|
+
@cpm.probability(:color => :red, :child => :asher, :describe => true).keys.first.should eql('P(red | asher)')
|
71
|
+
@cpm.probability(:color => :red, :child => :stella, :describe => true).keys.first.should eql('P(red | stella)')
|
72
|
+
@cpm.probability(:color => :green, :child => :asher, :describe => true).keys.first.should eql('P(green | asher)')
|
73
|
+
@cpm.probability(:color => :green, :child => :stella, :describe => true).keys.first.should eql('P(green | stella)')
|
74
|
+
@cpm.probability(:child => :asher, :describe => true).keys.first.should eql('P(red or green | asher)')
|
75
|
+
@cpm.probability(:child => :stella, :describe => true).keys.first.should eql('P(red or green | stella)')
|
76
|
+
@cpm.probability(:color => :red, :describe => true).keys.first.should eql('P(red | asher or stella)')
|
77
|
+
@cpm.probability(:color => :green, :describe => true).keys.first.should eql('P(green | asher or stella)')
|
78
|
+
@cpm.probability(:describe => true).keys.first.should eql('P(red or green | asher or stella)')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should alias p for probability" do
|
82
|
+
@cpm.p(:color => :red, :child => :asher).should be_close(0.08, 1e-10)
|
83
|
+
@cpm.p(:color => :red, :child => :stella).should be_close(0.32, 1e-10)
|
84
|
+
@cpm.p(:color => :green, :child => :asher).should be_close(0.12, 1e-10)
|
85
|
+
@cpm.p(:color => :green, :child => :stella).should be_close(0.48, 1e-10)
|
86
|
+
@cpm.p(:child => :asher).should be_close(0.2, 1e-10)
|
87
|
+
@cpm.p(:child => :stella).should be_close(0.8, 1e-10)
|
88
|
+
@cpm.p(:color => :red).should be_close(0.4, 1e-10)
|
89
|
+
@cpm.p(:color => :green).should be_close(0.6, 1e-10)
|
90
|
+
@cpm.p.should be_close(1.0, 1e-10)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should have odds for any query" do
|
94
|
+
@cpm.odds(:color => :red, :child => :asher).should be_close(0.087, 1e-3)
|
95
|
+
@cpm.odds(:color => :red, :child => :stella).should be_close(0.471, 1e-3)
|
96
|
+
@cpm.odds(:color => :green, :child => :asher).should be_close(0.136, 1e-3)
|
97
|
+
@cpm.odds(:color => :green, :child => :stella).should be_close(0.923, 1e-3)
|
98
|
+
@cpm.odds(:child => :asher).should be_close(0.25, 1e-3)
|
99
|
+
@cpm.odds(:child => :stella).should be_close(4.0, 1e-3)
|
100
|
+
@cpm.odds(:color => :red).should be_close(0.666, 1e-3)
|
101
|
+
@cpm.odds(:color => :green).should be_close(1.5, 1e-3)
|
102
|
+
@cpm.odds.should eql(1 / 0.0)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should alias o for odds" do
|
106
|
+
@cpm.o(:color => :red, :child => :asher).should be_close(0.087, 1e-3)
|
107
|
+
@cpm.o(:color => :red, :child => :stella).should be_close(0.471, 1e-3)
|
108
|
+
@cpm.o(:color => :green, :child => :asher).should be_close(0.136, 1e-3)
|
109
|
+
@cpm.o(:color => :green, :child => :stella).should be_close(0.923, 1e-3)
|
110
|
+
@cpm.o(:child => :asher).should be_close(0.25, 1e-3)
|
111
|
+
@cpm.o(:child => :stella).should be_close(4.0, 1e-3)
|
112
|
+
@cpm.o(:color => :red).should be_close(0.666, 1e-3)
|
113
|
+
@cpm.o(:color => :green).should be_close(1.5, 1e-3)
|
114
|
+
@cpm.o.should eql(1 / 0.0)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should default the name to :cpm" do
|
118
|
+
@cpm.name.should eql(:cpm)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should default the description" do
|
122
|
+
@cpm.description.should eql("Conditional Probability Matrix from child to color.")
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should return a vector for likelihood" do
|
126
|
+
@cpm.likelihood(:red).should be_a(GSL::Vector)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should offer the likelihood of each parent value, given a child value" do
|
130
|
+
values = @cpm.likelihood(:red).to_a
|
131
|
+
values.sort.map {|e| (e * 100).to_i}.should eql([8, 32])
|
132
|
+
|
133
|
+
values = @cpm.likelihood(:green).to_a
|
134
|
+
values.sort.map {|e| (e * 100).to_i}.should eql([12, 48])
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should offer l as an alias for likelihood" do
|
138
|
+
values = @cpm.l(:red).to_a
|
139
|
+
values.sort.map {|e| (e * 100).to_i}.should eql([8, 32])
|
140
|
+
|
141
|
+
values = @cpm.l(:green).to_a
|
142
|
+
values.sort.map {|e| (e * 100).to_i}.should eql([12, 48])
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Fathom
|
4
|
+
|
5
|
+
describe DataCollection do
|
6
|
+
it "should be a node" do
|
7
|
+
DataCollection.ancestors.should be_include(Node)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be a DiscreteNode as well" do
|
11
|
+
DataCollection.ancestors.should be_include(DiscreteNode)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be able to extract the discrete labels from the first discrete parent node added to it" do
|
15
|
+
pr = PlausibleRange.new(:min => 0, :max => 1)
|
16
|
+
dn = DiscreteNode.new(:labels => [1,2,3])
|
17
|
+
dc = DataCollection.new(:parents => [pr, dn])
|
18
|
+
dc.labels.should eql([1,2,3])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should enforce a name" do
|
22
|
+
dc = DataCollection.new(:labels => [1,2,3])
|
23
|
+
dc.name.should_not be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Fathom
|
4
|
+
|
5
|
+
describe Decision do
|
6
|
+
it "should be a type of node" do
|
7
|
+
Decision.ancestors.should be_include(Node)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should not have a distribution or values" do
|
11
|
+
@d = Decision.new
|
12
|
+
@d.should_not be_respond_to(:distribution)
|
13
|
+
@d.should_not be_respond_to(:values)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Fathom
|
4
|
+
|
5
|
+
describe DiscreteNode do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@dn = DiscreteNode.new(:labels => [1,2,3])
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should assign :true and :false for labels, if none are provided" do
|
12
|
+
lambda{@dn = DiscreteNode.new}.should_not raise_error
|
13
|
+
@dn.labels.should eql([:true, :false])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should create an array from a single value for its labels" do
|
17
|
+
dn = DiscreteNode.new(:labels => 1)
|
18
|
+
dn.labels.should eql([1])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should take the values and convert them to labels, if there are no labels provided" do
|
22
|
+
dn = DiscreteNode.new(:values => [1,2,3])
|
23
|
+
dn.labels.should eql([1,2,3])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should create a unique list of labels" do
|
27
|
+
dn = DiscreteNode.new(:labels => [1,1,2,3,1])
|
28
|
+
dn.labels.should eql([1,2,3])
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should expose the size of the labels" do
|
32
|
+
@dn.size.should eql(3)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should alias length for size" do
|
36
|
+
@dn.length.should eql(3)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should have a default distribution of DiscreteUniform" do
|
40
|
+
@dn.distribution.should eql(Fathom::Distributions::DiscreteUniform)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be able to produce a random variable from the discrete distribution" do
|
44
|
+
@dn = DiscreteNode.new(:labels => [1,2])
|
45
|
+
# 9.31322574615479e-10 chance of failing
|
46
|
+
30.times.map {@dn.rand}.uniq.sort.should eql([1,2])
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should offer a uniform likelihood" do
|
50
|
+
@dn.likelihood(:anything).to_a.should eql([1.0, 1.0, 1.0])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should offer an alias of l for likelihood" do
|
54
|
+
@dn.l(:anything).to_a.should eql([1.0, 1.0, 1.0])
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include Fathom
|
4
|
+
|
5
|
+
describe Fact do
|
6
|
+
before do
|
7
|
+
@f = Fact.new(
|
8
|
+
:value => 2,
|
9
|
+
:name => "Some Fact",
|
10
|
+
:description => "Just a demonstration of what a good fact may look like. Facts are really about things that are certain--there are 7 days in a week, that sort of thing. Too often I was creating a PlausibleRange with the min and max to the same value."
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be a Node" do
|
15
|
+
Fact.ancestors.should be_include(Fathom::Node)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should take a value (rather than values)" do
|
19
|
+
@f.value.should eql(2)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return the value for rand" do
|
23
|
+
@f.rand.should eql(2)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have removed the distribution and values methods" do
|
27
|
+
@f.should_not be_respond_to(:values)
|
28
|
+
@f.should_not be_respond_to(:distribution)
|
29
|
+
@n = Node.new(:distribution => :standard, :values => [1,2,3])
|
30
|
+
@n.values.should eql([1,2,3])
|
31
|
+
@n.distribution.should eql(Fathom::Distributions::Gaussian)
|
32
|
+
end
|
33
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
|
2
2
|
|
3
3
|
include Fathom
|
4
4
|
|
@@ -11,11 +11,11 @@ describe NumericMethods do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
before do
|
14
|
-
@n = DummyNumericNode.new(@opts)
|
14
|
+
@n = Distributions::DummyNumericNode.new(@opts)
|
15
15
|
end
|
16
16
|
|
17
17
|
it "should respond to a rand method" do
|
18
|
-
@n = DummyNumericNode.new
|
18
|
+
@n = Distributions::DummyNumericNode.new
|
19
19
|
@n.should be_respond_to(:rand)
|
20
20
|
end
|
21
21
|
|
@@ -33,7 +33,7 @@ describe NumericMethods do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should not raise an error when looking for a standard deviation and there is no vector" do
|
36
|
-
@n = DummyNumericNode.new
|
36
|
+
@n = Distributions::DummyNumericNode.new
|
37
37
|
@n.vector.should be_nil
|
38
38
|
@n.sd.should be_nil
|
39
39
|
end
|
@@ -43,17 +43,17 @@ describe NumericMethods do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
it "should return nil for mean if it doesn't have a vector" do
|
46
|
-
@n = DummyNumericNode.new
|
46
|
+
@n = Distributions::DummyNumericNode.new
|
47
47
|
@n.mean.should be_nil
|
48
48
|
end
|
49
49
|
|
50
50
|
it "should provide rand, if it has a vector" do
|
51
|
-
Gaussian.should_receive(:rand).and_return(0.5)
|
51
|
+
Distributions::Gaussian.should_receive(:rand).and_return(0.5)
|
52
52
|
@n.rand
|
53
53
|
end
|
54
54
|
|
55
55
|
it "should return nil for rand if it doesn't have a vector" do
|
56
|
-
@n = DummyNumericNode.new
|
56
|
+
@n = Distributions::DummyNumericNode.new
|
57
57
|
@n.mean.should be_nil
|
58
58
|
end
|
59
59
|
|
@@ -65,18 +65,60 @@ describe NumericMethods do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
it "should pass the mean and standard deviation to the distribution when calculating an inverse_cdf" do
|
68
|
-
@n.inverse_cdf.should eql(Gaussian.inverse_cdf(:mean => @n.mean, :sd => @n.sd))
|
68
|
+
@n.inverse_cdf.should eql(Distributions::Gaussian.inverse_cdf(:mean => @n.mean, :sd => @n.sd))
|
69
69
|
end
|
70
70
|
|
71
71
|
it "should pass the mean and standard deviation to the distribution when calculating an lower_bound" do
|
72
|
-
@n.lower_bound.should eql(Gaussian.lower_bound(:mean => @n.mean, :sd => @n.sd))
|
72
|
+
@n.lower_bound.should eql(Distributions::Gaussian.lower_bound(:mean => @n.mean, :sd => @n.sd))
|
73
73
|
end
|
74
74
|
|
75
75
|
it "should pass the mean and standard deviation to the distribution when calculating an upper_bound" do
|
76
|
-
@n.upper_bound.should eql(Gaussian.upper_bound(:mean => @n.mean, :sd => @n.sd))
|
76
|
+
@n.upper_bound.should eql(Distributions::Gaussian.upper_bound(:mean => @n.mean, :sd => @n.sd))
|
77
77
|
end
|
78
78
|
|
79
79
|
it "should pass the mean and standard deviation to the distribution when calculating an interval_values" do
|
80
|
-
@n.interval_values.should eql(Gaussian.interval_values(:mean => @n.mean, :sd => @n.sd))
|
80
|
+
@n.interval_values.should eql(Distributions::Gaussian.interval_values(:mean => @n.mean, :sd => @n.sd))
|
81
81
|
end
|
82
|
+
|
83
|
+
it "should have a coefficient_of_variation" do
|
84
|
+
@n.coefficient_of_variation.should eql(@n.sd / @n.mean)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should have a cov alias for coefficient_of_variation" do
|
88
|
+
@n.cov.should eql(@n.coefficient_of_variation)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should have a summary hash" do
|
92
|
+
@n.should be_respond_to(:summary)
|
93
|
+
@n.summary.should be_a(Hash)
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should have a mean in the summary" do
|
97
|
+
@n.summary[:mean].should eql(@n.mean)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should have a standard deviation in the summary" do
|
101
|
+
@n.summary[:standard_deviation].should eql(@n.standard_deviation)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should have a min in the summary" do
|
105
|
+
@n.summary[:min].should eql(@n.vector.min)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should have a max in the summary" do
|
109
|
+
@n.summary[:max].should eql(@n.vector.max)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should have a lower_bound in the summary" do
|
113
|
+
@n.summary[:lower_bound].should eql(@n.lower_bound)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should have an upper_bound in the summary" do
|
117
|
+
@n.summary[:upper_bound].should eql(@n.upper_bound)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should have a coefficient_of_variation in the summary" do
|
121
|
+
@n.summary[:coefficient_of_variation].should eql(@n.sd / @n.mean)
|
122
|
+
end
|
123
|
+
|
82
124
|
end
|
data/spec/fathom/node_spec.rb
CHANGED
@@ -151,5 +151,22 @@ describe Node do
|
|
151
151
|
n2 = Node.new :name => 'n2', :parent => n1
|
152
152
|
n1.n2.should eql(n2)
|
153
153
|
end
|
154
|
+
|
155
|
+
# it "should have introduced the Spira infrastructure by now" do
|
156
|
+
# Object.should be_const_defined(:Spira)
|
157
|
+
# end
|
158
|
+
|
159
|
+
# it "should have defined the spira repository" do
|
160
|
+
# Spira.repositories[:default].should be_a(RDF::Repository)
|
161
|
+
# end
|
162
|
+
|
163
|
+
# This really changes the interface for everything. So, either I re-purpose a node, or something...
|
164
|
+
# Next steps: read the Spira source and figure out what's easier to do: replace the functionality
|
165
|
+
# or change my interfaces. (btw, 9 issues, only.
|
166
|
+
# Mostly just tied to creating the node accessors (n1.n2 type thing))
|
167
|
+
|
168
|
+
# it "should have Spira included" do
|
169
|
+
# Node.included_modules.should be_include(Spira::Resource)
|
170
|
+
# end
|
154
171
|
|
155
172
|
end
|
data/spec/fathom_spec.rb
CHANGED
@@ -14,3 +14,43 @@ describe "Fathom" do
|
|
14
14
|
Fathom.kb.should eql(Fathom.knowledge_base)
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
18
|
+
|
19
|
+
=begin
|
20
|
+
OK, so I'm thinking about how this works....
|
21
|
+
A knowledge base is something I work from. I could create a default knowledge base. I won't be able to save it to anywhere permenant, but possibly I want to just be able to start working for a while, have things link up, then decide if I've created anything of value...
|
22
|
+
|
23
|
+
Or, I could always use an explicit knowledge base.
|
24
|
+
|
25
|
+
Frankly, the way I thought I would use it would be:
|
26
|
+
|
27
|
+
home_budget = Fathom::KnowledgeBase.find_or_create(:home_budget)
|
28
|
+
home_budget.new(:plausible_range, :hard_min => 0, :min => 500, :max => 750, :name => "Referral Income", :type => :revenue)
|
29
|
+
revenue = home_budget.find(...)
|
30
|
+
mc_revenue = home_budget.new(:mc_node, :values => revenue, :type => :revenue, :name => "Combined Income", :description => "A collection of income from our investments, job, and miscellaneous projects.") do |s|
|
31
|
+
:combined_income => s.values.inject(0.0) do |sum, e|
|
32
|
+
sum += e
|
33
|
+
end
|
34
|
+
end
|
35
|
+
home_budget.new(:svm_node, :values => mc_revenue.process, :type => :revenue)
|
36
|
+
|
37
|
+
mc_daily_expenses = home_budget.new(:mc_node,
|
38
|
+
:values => {:expenses => home_budget.find(...), :dates => Fathom::KnowledgeBase.find(:dates)},
|
39
|
+
:type => :expenses,
|
40
|
+
:name => "Combined Expenses",
|
41
|
+
:description => "All of the household expenses on a monthly basis"
|
42
|
+
) do |s|
|
43
|
+
cycle ||= (1..365).cycle
|
44
|
+
day = s.values.dates.day_in_year(cycle.next)
|
45
|
+
...
|
46
|
+
end
|
47
|
+
|
48
|
+
create a mini belief network about whether the budget is appropriate here...
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
For this demo, I picked up a project I had been working on. I added a Monte Carlo analysis that
|
53
|
+
could always be re-run, in case the inputs change. I don't yet have an automated way to keep up with the
|
54
|
+
changes to the inputs on that, but I do on the belief nodes.
|
55
|
+
|
56
|
+
=end
|