fathom 0.3.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. data/.autotest +7 -5
  2. data/.document +2 -2
  3. data/Gemfile +9 -10
  4. data/{LICENSE → LICENSE.txt} +1 -1
  5. data/README.md +29 -90
  6. data/Rakefile +34 -32
  7. data/VERSION +1 -1
  8. data/fathom.gemspec +105 -0
  9. data/features/fathom.feature +26 -0
  10. data/features/step_definitions/fathom_steps.rb +23 -0
  11. data/features/support/env.rb +13 -0
  12. data/lib/ext/array.rb +6 -2
  13. data/lib/ext/string.rb +86 -7
  14. data/lib/fathom.rb +51 -88
  15. data/lib/fathom/behaviors/attribute_system.rb +91 -0
  16. data/lib/fathom/behaviors/context_behavior.rb +28 -0
  17. data/lib/fathom/behaviors/plugins.rb +16 -0
  18. data/lib/fathom/contexts/network_population.rb +47 -0
  19. data/lib/fathom/contexts/network_traversal.rb +4 -0
  20. data/lib/fathom/data/adjacency_matrix.rb +27 -0
  21. data/lib/fathom/data/definition.rb +22 -0
  22. data/lib/fathom/data/edge.rb +58 -0
  23. data/lib/fathom/data/network.rb +35 -0
  24. data/lib/fathom/data/outcome.rb +30 -0
  25. data/lib/fathom/data/property.rb +31 -0
  26. data/lib/fathom/data/variable.rb +59 -0
  27. data/lib/fathom/roles/general_graph_tools.rb +87 -0
  28. data/lib/fathom/roles/network_builder.rb +61 -0
  29. data/spec/fathom/behaviors/attribute_system_spec.rb +141 -0
  30. data/spec/fathom/behaviors/context_behavior_spec.rb +15 -0
  31. data/spec/fathom/behaviors/plugins_spec.rb +80 -0
  32. data/spec/fathom/contexts/network_population_spec.rb +55 -0
  33. data/spec/fathom/contexts/network_traversal_spec.rb +11 -0
  34. data/spec/fathom/data/adjacency_matrix_spec.rb +42 -0
  35. data/spec/fathom/data/definition_spec.rb +19 -0
  36. data/spec/fathom/data/edge_spec.rb +77 -0
  37. data/spec/fathom/data/network_spec.rb +72 -0
  38. data/spec/fathom/data/outcome_spec.rb +17 -0
  39. data/spec/fathom/data/property_spec.rb +17 -0
  40. data/spec/fathom/data/variable_spec.rb +101 -0
  41. data/spec/fathom/ext/array_spec.rb +17 -0
  42. data/spec/fathom/ext/string_spec.rb +90 -0
  43. data/spec/fathom/roles/general_graph_tools_spec.rb +95 -0
  44. data/spec/fathom/roles/network_builder_spec.rb +90 -0
  45. data/spec/fathom_spec.rb +28 -49
  46. data/spec/spec_helper.rb +7 -11
  47. data/spec/support/context_behavior.rb +14 -0
  48. data/spec/support/custom_matchers.rb +12 -0
  49. data/spec/support/files.rb +8 -0
  50. data/spec/support/network.yml +42 -0
  51. metadata +133 -174
  52. data/.bundle/config +0 -2
  53. data/.gitignore +0 -6
  54. data/Gemfile.lock +0 -42
  55. data/TODO.md +0 -127
  56. data/autotest/discover.rb +0 -1
  57. data/lib/ext/faster_csv.rb +0 -1
  58. data/lib/ext/open_struct.rb +0 -17
  59. data/lib/fathom/agent.rb +0 -48
  60. data/lib/fathom/agent/agent_cluster.rb +0 -23
  61. data/lib/fathom/agent/properties.rb +0 -48
  62. data/lib/fathom/archive/causal_graph.rb +0 -12
  63. data/lib/fathom/archive/concept.rb +0 -83
  64. data/lib/fathom/archive/conditional_probability_matrix.rb +0 -119
  65. data/lib/fathom/archive/inverter.rb +0 -20
  66. data/lib/fathom/archive/n2.rb +0 -198
  67. data/lib/fathom/archive/n3.rb +0 -119
  68. data/lib/fathom/archive/node.rb +0 -97
  69. data/lib/fathom/archive/noodle.rb +0 -136
  70. data/lib/fathom/archive/scratch.rb +0 -45
  71. data/lib/fathom/distributions.rb +0 -8
  72. data/lib/fathom/distributions/discrete_gaussian.rb +0 -44
  73. data/lib/fathom/distributions/discrete_uniform.rb +0 -25
  74. data/lib/fathom/distributions/gaussian.rb +0 -46
  75. data/lib/fathom/distributions/uniform.rb +0 -35
  76. data/lib/fathom/import.rb +0 -85
  77. data/lib/fathom/import/csv_import.rb +0 -59
  78. data/lib/fathom/import/import_node.rb +0 -17
  79. data/lib/fathom/import/yaml_import.rb +0 -74
  80. data/lib/fathom/knowledge_base.rb +0 -46
  81. data/lib/fathom/knowledge_base/search.rb +0 -19
  82. data/lib/fathom/monte_carlo_set.rb +0 -152
  83. data/lib/fathom/node.rb +0 -139
  84. data/lib/fathom/node/belief_node.rb +0 -121
  85. data/lib/fathom/node/cpm_node.rb +0 -100
  86. data/lib/fathom/node/data_collection.rb +0 -97
  87. data/lib/fathom/node/data_node.rb +0 -22
  88. data/lib/fathom/node/decision.rb +0 -11
  89. data/lib/fathom/node/discrete_node.rb +0 -41
  90. data/lib/fathom/node/fact.rb +0 -24
  91. data/lib/fathom/node/mc_node.rb +0 -70
  92. data/lib/fathom/node/node_extensions/enforced_name.rb +0 -12
  93. data/lib/fathom/node/node_extensions/numeric_methods.rb +0 -68
  94. data/lib/fathom/node/plausible_range.rb +0 -98
  95. data/lib/fathom/simulation.rb +0 -59
  96. data/lib/fathom/simulation/tick_methods.rb +0 -25
  97. data/lib/fathom/simulation/tick_simulation.rb +0 -12
  98. data/lib/fathom/value_description.rb +0 -79
  99. data/lib/options_hash.rb +0 -186
  100. data/spec/ext/array_spec.rb +0 -10
  101. data/spec/ext/faster_csv_spec.rb +0 -10
  102. data/spec/ext/open_struct_spec.rb +0 -20
  103. data/spec/ext/string_spec.rb +0 -7
  104. data/spec/fathom/agent/agent_cluster_spec.rb +0 -17
  105. data/spec/fathom/agent_spec.rb +0 -51
  106. data/spec/fathom/distributions/discrete_gaussian_spec.rb +0 -64
  107. data/spec/fathom/distributions/discrete_uniform_spec.rb +0 -0
  108. data/spec/fathom/distributions/gaussian_spec.rb +0 -64
  109. data/spec/fathom/distributions/uniform_spec.rb +0 -0
  110. data/spec/fathom/import/csv_import_spec.rb +0 -52
  111. data/spec/fathom/import/import_node_spec.rb +0 -10
  112. data/spec/fathom/import/yaml_import_spec.rb +0 -73
  113. data/spec/fathom/import_spec.rb +0 -36
  114. data/spec/fathom/knowledge_base_spec.rb +0 -20
  115. data/spec/fathom/monte_carlo_set_spec.rb +0 -149
  116. data/spec/fathom/node/belief_node_spec.rb +0 -180
  117. data/spec/fathom/node/cpm_node_spec.rb +0 -144
  118. data/spec/fathom/node/data_collection_spec.rb +0 -26
  119. data/spec/fathom/node/data_node_spec.rb +0 -102
  120. data/spec/fathom/node/decision_spec.rb +0 -15
  121. data/spec/fathom/node/discrete_node_spec.rb +0 -56
  122. data/spec/fathom/node/fact_spec.rb +0 -33
  123. data/spec/fathom/node/mc_node_spec.rb +0 -66
  124. data/spec/fathom/node/node_extensions/enforced_name_spec.rb +0 -15
  125. data/spec/fathom/node/node_extensions/numeric_methods_spec.rb +0 -124
  126. data/spec/fathom/node/plausible_range_spec.rb +0 -151
  127. data/spec/fathom/node_spec.rb +0 -172
  128. data/spec/fathom/simulation/tick_simulation_spec.rb +0 -32
  129. data/spec/fathom/simulation_spec.rb +0 -24
  130. data/spec/fathom/value_description_spec.rb +0 -70
  131. data/spec/support/demo.yml +0 -17
  132. data/spec/support/demo_agent.rb +0 -8
  133. data/spec/support/dummy_numeric_node.rb +0 -8
  134. data/spec/support/fact.yml +0 -11
@@ -1,119 +0,0 @@
1
- class Array
2
- def expand(array)
3
- return array.map{|e| [e]} if self.empty?
4
- array.inject([]) do |list, other_e|
5
- self.each do |e|
6
- list << [e,other_e].flatten
7
- end
8
- list
9
- end
10
- end
11
- end
12
-
13
- class Variable
14
-
15
- attr_reader :values, :name, :observations, :total
16
-
17
- def initialize(name, *values)
18
- values = [true, false] if values.empty?
19
- @name = name
20
- @values = values
21
- @observations = Array.new(@values.size, 0)
22
- @total = 0
23
- end
24
-
25
- # You can observe anything but nothing: we record any observation but nil.
26
- # If nil is set, we use the first value as the default.
27
- def observe(value=nil)
28
- value = self.values.first if value.nil?
29
- unless self.values.include?(value)
30
- self.values << value
31
- self.observations << 0
32
- end
33
- index = self.values.index(value)
34
- self.observations[index] += 1
35
- @total += 1
36
- end
37
-
38
- # Lookup observations
39
- def observed(value)
40
- index = self.values.index(value)
41
- return 0 unless index
42
- self.observations[index]
43
- end
44
-
45
- def inspect
46
- "Variable: #{self.name} #{self.values.inspect}"
47
- end
48
-
49
- class << self
50
- def infer(obj, *values)
51
- return obj if obj.is_a?(Variable)
52
- case obj
53
- when Symbol
54
- Variable.new(obj, *values)
55
- when String
56
- Variable.new(obj.to_sym, *values)
57
- else
58
- nil
59
- end
60
- end
61
-
62
- end
63
- end
64
-
65
- class Table
66
-
67
- attr_reader :columns, :variables, :legend
68
- def initialize(*variables)
69
- @columns = variables.map {|v| v.name}
70
- @variables = variables
71
- @legend = @variables.inject([]) do |list, e|
72
- list = list.expand(e)
73
- end
74
- end
75
-
76
- protected
77
- def lookup()
78
- end
79
- end
80
-
81
- class Node
82
- def initialize(name, *parents)
83
- end
84
-
85
- # Take an array, array of arrays, dictionary, hash, or OpenStruct.
86
- # Anything but an array can add a new parent to observe.
87
- def observe(values)
88
- end
89
- end
90
-
91
- require 'rubygems'
92
- require 'spec'
93
-
94
- =begin
95
- variables = [[:spring, :summer, :fall, :winter], [:true, :false], [:true, :false], [:true, :false]]
96
-
97
- @all = []
98
- (0...variables.size).each do |i|
99
- @all << variables.inject([]) do |list, v|
100
- list << (0...v.size).map do |j|
101
- variables[i][j]
102
- end
103
- end
104
- end
105
-
106
- @all
107
-
108
-
109
- v1 = [:spring, :summer, :fall, :winter]
110
- v2 = [:true, :false]
111
- a = [v1, v2]
112
- b = []
113
- a.each do |e|
114
- b << e
115
- end
116
-
117
-
118
-
119
- =end
@@ -1,97 +0,0 @@
1
- require 'rubygems'
2
- require 'gsl'
3
-
4
- include GSL
5
-
6
- class Node
7
-
8
- attr_reader :name, :labels, :probabilities, :likelihood
9
-
10
- def initialize(*args)
11
- @name = args.shift
12
- raise ArgumentError, "Must provide a node name" unless self.name
13
- if args.empty?
14
- extract_from_array([:true, :false])
15
- elsif args.length == 1 and args.first.is_a?(Hash)
16
- extract_from_hash(args.first)
17
- elsif args.length == 1 and args.first.is_a?(Array)
18
- extract_from_array(args.first)
19
- else
20
- extract_from_array(args)
21
- end
22
- assert_likelihood
23
- end
24
-
25
- def inspect
26
- matched_array = []
27
- self.labels.each_with_index {|e, i| matched_array << e; matched_array << self.probabilities[i]}
28
- "Node: #{self.name.to_s} #{self.labels.inspect} #{self.probabilities.to_a.inspect}"
29
- "Node: #{self.name.to_s} #{matched_array.inspect}"
30
- end
31
-
32
- def belief
33
- probabilities * likelihood
34
- end
35
-
36
- alias :b :belief
37
- alias :l :likelihood
38
- alias :p :probabilities
39
-
40
- def probability(label)
41
- probabilities[index_for(label)]
42
- end
43
-
44
- def inverse_probability(label)
45
- 1 - probability(label)
46
- end
47
-
48
- def odds(label)
49
- probability(label) / inverse_probability(label)
50
- end
51
-
52
- def to_a
53
- self.probabilities.to_a
54
- end
55
-
56
- protected
57
-
58
- def index_for(label)
59
- labels.index(label)
60
- end
61
-
62
- def assert_likelihood
63
- @likelihood = Vector.ary_to_gv(Array.new(@probabilities.size, 1))
64
- end
65
-
66
- def extract_from_array(array)
67
- @labels = array
68
- @probabilities = Vector.ary_to_gv(uniform_distribution(array.size))
69
- end
70
-
71
- def uniform_distribution(n)
72
- Array.new(n, 1/n.to_f)
73
- end
74
-
75
- def extract_from_hash(hash)
76
- @labels, probabilities = [], []
77
- hash.each do |k, v|
78
- @labels << k
79
- probabilities << v
80
- end
81
- @probabilities = Vector.ary_to_gv(probabilities)
82
- normalize_probabilities!
83
- end
84
-
85
- # I don't like GSL::Vector#normalize!, it's not accurate, or has a different
86
- # idea of what a normalized vector looks like.
87
- def normalize_probabilities!
88
- sum = 0.0
89
- @probabilities.each do |value|
90
- sum += value
91
- end
92
- @probabilities.map! do |value|
93
- value.to_f / sum
94
- end
95
- end
96
-
97
- end
@@ -1,136 +0,0 @@
1
- =begin
2
- I want to noodle around a bit with basic probabilities, odds, that sort of thing. Here's the example:
3
-
4
- Upon being awakened by the sound of a burglar alary, what is your degree of belief that a burglary attempt took place?
5
-
6
- Supporting information:
7
-
8
- * There is a 95% chance that an attempted burglary will trigger the alarm system, P(alarm|burglary) = 95%
9
- * There is a 1% chance that the alarms will be triggered by non-burglary attempts, p(alarm|no burglary) = 1%
10
- * There is a 1/10_000 chance of a particular home being burglarized, generally, P(burglary) = 10^-4
11
-
12
- O(burglary|alarm) = L(alarm|burglary)O(burglary)
13
-
14
- P(burglary|alarm) = O(burglary|alarm) / 1 + O(burglary|alarm)
15
-
16
- =end
17
-
18
- require 'mathn'
19
-
20
- def likelihood(effect, cause)
21
- effect / cause
22
- end
23
- alias :l :likelihood
24
-
25
- def odds(effect, cause=nil)
26
- cause ? ( likelihood(cause, effect) * odds(effect) ) : (effect / (1 - effect))
27
- end
28
- alias :o :odds
29
-
30
- # The same as the odds above, but only in the case of a single hypothesis
31
- def prior_odds(e)
32
- e / ( 1 - e )
33
- end
34
-
35
- def likelihood_ratio(e, h)
36
- p(e,h) / p(e, 1 - h)
37
- end
38
-
39
- def probability(e, h)
40
- (e * h) / h
41
- end
42
-
43
- def product_rule(*e)
44
- end
45
- alias :p :probability
46
-
47
- class Array
48
-
49
- def givens
50
- @givens ||= {}
51
- end
52
-
53
- def give(sym, array=Array.new(self.size, 1/self.size))
54
- self.givens[sym] = array
55
- end
56
-
57
- def given(sym, index)
58
- self.givens[sym][index]
59
- end
60
- end
61
-
62
- =begin
63
- Since Rational is the automatic choice for probabilistic data,
64
- and since I don't want to override how mathn infers numbers,
65
- I am adding some baggage to Rational:
66
-
67
- * It still reduces to the LCD
68
- * It keeps track of all events,
69
- so that I can keep a new event proportional to old ones
70
- * It has an add_event (add) which takes a true or false value
71
- true values, records that a condition was found
72
-
73
- This really only works for binary data, but this is a noodle file.
74
-
75
- =end
76
- class Rational < Numeric
77
- class << self
78
- alias :orig_reduce :reduce
79
- def reduce(num, den=1)
80
- val = orig_reduce(num, den)
81
- val.total_events = den
82
- val.positive_events = num
83
- val
84
- end
85
- end
86
-
87
- def total_events(val=nil)
88
- @total_events ||= 0
89
- @total_events = val if val
90
- @total_events
91
- end
92
- alias :events :total_events
93
- alias :total :total_events
94
-
95
- def total_events=(val)
96
- total_events(val)
97
- end
98
-
99
- def positive_events(val=nil)
100
- @positive_events ||= 0
101
- @positive_events = val if val
102
- @positive_events
103
- end
104
- alias :positive :positive_events
105
-
106
- def positive_events=(val)
107
- positive_events(val)
108
- end
109
-
110
- # Uses the Rational constructor to calculate the lowest common denominator
111
- def add_event(positive=true)
112
- num = positive ? self.positive_events + 1 : self.positive_events
113
- den = self.total_events + 1
114
- other = Rational(num, den)
115
- @numerator, @denominator, @positive_events, @total_events = other.numerator, other.denominator, num, den
116
- self
117
- end
118
- alias :add :add_event
119
- end
120
-
121
-
122
- class A
123
- class << self
124
- include GSL
125
-
126
- def r
127
- @r ||= Rng.alloc
128
- end
129
-
130
- def pdf
131
- r = self.r.gaussian
132
- pdf = Ran.gaussian_pdf(r)
133
- puts r, pdf
134
- end
135
- end
136
- end
@@ -1,45 +0,0 @@
1
- require 'node'
2
- require 'conditional_probability_matrix'
3
-
4
- class A
5
- class << self
6
- # def will_pay
7
- # @will_pay ||= Node.new :true, :false
8
- # end
9
- #
10
- # def has_money
11
- # @has_money ||= Node.new :plenty => 0.1, :some => 0.8, :little => 0.1
12
- # end
13
- #
14
- # def ones
15
- # Matrix.ones(will_pay.values.length, has_money.values.length)
16
- # end
17
- #
18
- # def parents
19
- # will_pay.values.col * has_money.values
20
- # # will_pay.each do |value|
21
- # #
22
- # # end
23
- # end
24
- #
25
- # def cpm
26
- # ConditionalProbabilityMatrix.new(will_pay, has_money)
27
- # end
28
-
29
- def killer_identity
30
- @killer_identity ||= Node.new(:killer_identity, :jack => 0.8, :joe => 0.1, :jeff => 0.1)
31
- end
32
- alias :k :killer_identity
33
- alias :x :killer_identity
34
-
35
- def fingerprint_information
36
- @fingerprint_information ||= Node.new(:fingerprint_information, :jack => 2/3.0, :joe => 1/6.0, :jeff => 1/6.0)
37
- end
38
- alias :f :fingerprint_information
39
- alias :y :fingerprint_information
40
-
41
- def cpm
42
- @cpm ||= ConditionalProbabilityMatrix.new(x, y)
43
- end
44
- end
45
- end
@@ -1,8 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
- module Fathom
3
- module Distributions
4
- module SharedMethods
5
- # TODO: Put helper methods here for sharing some of the distribution functionality
6
- end
7
- end
8
- end
@@ -1,44 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fathom'))
2
- class Fathom::Distributions::DiscreteGaussian
3
- extend Fathom::Distributions::SharedMethods
4
- class << self
5
- def rng
6
- @rng ||= GSL::Rng.alloc(GSL::Rng::MT19937_1999, Kernel.rand(100_000))
7
- end
8
-
9
- def rand(sd)
10
- (rng.gaussian(sd) / size).floor + 1
11
- end
12
-
13
- def inverse_cdf(opts={})
14
- mean = opts[:mean]
15
- sd = opts[:sd]
16
- sd ||= opts[:std]
17
- sd ||= opts[:standard_deviation]
18
- lower = opts.fetch(:lower, true)
19
- lower = false if opts[:upper]
20
- confidence_interval = opts.fetch(:confidence_interval, 0.05)
21
- value = lower ? GSL::Cdf.gaussian_Pinv(confidence_interval, sd) : GSL::Cdf.gaussian_Qinv(confidence_interval, sd)
22
- value + mean
23
- end
24
- alias :lower_bound :inverse_cdf
25
-
26
- def upper_bound(opts={})
27
- inverse_cdf(opts.merge(:lower => false))
28
- end
29
-
30
- def interval_values(opts={})
31
- confidence_interval = opts.fetch(:confidence_interval, 0.9)
32
- bound = (1 - confidence_interval) / 2.0
33
- [lower_bound(opts.merge(:confidence_interval => bound)), upper_bound(opts.merge(:confidence_interval => bound))]
34
- end
35
-
36
- # If only I had the background to explain what this is....
37
- # I want to know how many standard deviations are expressed by the confidence interval
38
- # I can then divide the range by this number to get the standard deviation
39
- def standard_deviations_under(confidence_interval)
40
- GSL::Cdf.gaussian_Qinv((1 - confidence_interval) / 2) * 2
41
- end
42
- end
43
- end
44
-