fathom 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.autotest +10 -0
  2. data/Gemfile +10 -2
  3. data/Gemfile.lock +8 -0
  4. data/TODO.md +12 -25
  5. data/VERSION +1 -1
  6. data/lib/{fathom/ext → ext}/array.rb +0 -0
  7. data/lib/{fathom/ext → ext}/faster_csv.rb +0 -0
  8. data/lib/{fathom/ext → ext}/open_struct.rb +0 -0
  9. data/lib/{fathom/ext → ext}/string.rb +0 -0
  10. data/lib/fathom.rb +16 -13
  11. data/lib/fathom/agent.rb +8 -9
  12. data/lib/fathom/{causal_graph.rb → archive/causal_graph.rb} +0 -0
  13. data/lib/fathom/{concept.rb → archive/concept.rb} +0 -0
  14. data/lib/fathom/archive/conditional_probability_matrix.rb +3 -0
  15. data/lib/fathom/{inverter.rb → archive/inverter.rb} +0 -0
  16. data/lib/fathom/archive/node.rb +24 -1
  17. data/lib/fathom/distributions/discrete_uniform.rb +11 -32
  18. data/lib/fathom/import.rb +37 -34
  19. data/lib/fathom/import/yaml_import.rb +22 -1
  20. data/lib/fathom/knowledge_base.rb +34 -23
  21. data/lib/fathom/knowledge_base/search.rb +19 -0
  22. data/lib/fathom/node.rb +32 -1
  23. data/lib/fathom/node/belief_node.rb +121 -0
  24. data/lib/fathom/node/cpm_node.rb +100 -0
  25. data/lib/fathom/node/data_collection.rb +97 -0
  26. data/lib/fathom/{data_node.rb → node/data_node.rb} +1 -1
  27. data/lib/fathom/{value_aggregator.rb → node/decision.rb} +5 -5
  28. data/lib/fathom/node/discrete_node.rb +41 -0
  29. data/lib/fathom/node/fact.rb +24 -0
  30. data/lib/fathom/{mc_node.rb → node/mc_node.rb} +1 -1
  31. data/lib/fathom/{enforced_name.rb → node/node_extensions/enforced_name.rb} +1 -1
  32. data/lib/fathom/{numeric_methods.rb → node/node_extensions/numeric_methods.rb} +19 -1
  33. data/lib/fathom/{plausible_range.rb → node/plausible_range.rb} +1 -1
  34. data/spec/ext/array_spec.rb +10 -0
  35. data/spec/ext/faster_csv_spec.rb +10 -0
  36. data/spec/ext/open_struct_spec.rb +20 -0
  37. data/spec/ext/string_spec.rb +7 -0
  38. data/spec/fathom/import/csv_import_spec.rb +11 -9
  39. data/spec/fathom/import/yaml_import_spec.rb +27 -7
  40. data/spec/fathom/knowledge_base_spec.rb +8 -4
  41. data/spec/fathom/node/belief_node_spec.rb +180 -0
  42. data/spec/fathom/node/cpm_node_spec.rb +144 -0
  43. data/spec/fathom/node/data_collection_spec.rb +26 -0
  44. data/spec/fathom/{data_node_spec.rb → node/data_node_spec.rb} +1 -1
  45. data/spec/fathom/node/decision_spec.rb +15 -0
  46. data/spec/fathom/node/discrete_node_spec.rb +56 -0
  47. data/spec/fathom/node/fact_spec.rb +33 -0
  48. data/spec/fathom/{mc_node_spec.rb → node/mc_node_spec.rb} +1 -1
  49. data/spec/fathom/{enforced_name_spec.rb → node/node_extensions/enforced_name_spec.rb} +1 -1
  50. data/spec/fathom/{numeric_methods_spec.rb → node/node_extensions/numeric_methods_spec.rb} +53 -11
  51. data/spec/fathom/{plausible_range_spec.rb → node/plausible_range_spec.rb} +1 -1
  52. data/spec/fathom/node_spec.rb +17 -0
  53. data/spec/fathom_spec.rb +40 -0
  54. data/spec/spec_helper.rb +3 -0
  55. data/spec/support/fact.yml +11 -0
  56. metadata +57 -30
  57. 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
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
3
  include Fathom
4
4
 
@@ -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__) + '/../spec_helper')
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
3
  include Fathom
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
2
 
3
3
  include Fathom::Distributions
4
4
 
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
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
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
3
  include Fathom
4
4
 
@@ -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
@@ -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