fathom 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/.bundle/config +2 -0
  2. data/.document +5 -0
  3. data/.gitignore +5 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +5 -0
  6. data/Gemfile.lock +30 -0
  7. data/LICENSE +20 -0
  8. data/README.md +176 -0
  9. data/Rakefile +50 -0
  10. data/VERSION +1 -0
  11. data/autotest/discover.rb +1 -0
  12. data/lib/fathom.rb +68 -0
  13. data/lib/fathom/archive/conditional_probability_matrix.rb +116 -0
  14. data/lib/fathom/archive/n2.rb +198 -0
  15. data/lib/fathom/archive/n3.rb +119 -0
  16. data/lib/fathom/archive/node.rb +74 -0
  17. data/lib/fathom/archive/noodle.rb +136 -0
  18. data/lib/fathom/archive/scratch.rb +45 -0
  19. data/lib/fathom/basic_node.rb +8 -0
  20. data/lib/fathom/causal_graph.rb +12 -0
  21. data/lib/fathom/combined_plausibilities.rb +12 -0
  22. data/lib/fathom/concept.rb +83 -0
  23. data/lib/fathom/data_node.rb +51 -0
  24. data/lib/fathom/import.rb +68 -0
  25. data/lib/fathom/import/csv_import.rb +60 -0
  26. data/lib/fathom/import/yaml_import.rb +53 -0
  27. data/lib/fathom/inverter.rb +21 -0
  28. data/lib/fathom/knowledge_base.rb +23 -0
  29. data/lib/fathom/monte_carlo_set.rb +76 -0
  30. data/lib/fathom/node_utilities.rb +8 -0
  31. data/lib/fathom/plausible_range.rb +82 -0
  32. data/lib/fathom/value_aggregator.rb +11 -0
  33. data/lib/fathom/value_description.rb +79 -0
  34. data/lib/fathom/value_multiplier.rb +18 -0
  35. data/lib/options_hash.rb +186 -0
  36. data/spec/fathom/data_node_spec.rb +61 -0
  37. data/spec/fathom/import/csv_import_spec.rb +36 -0
  38. data/spec/fathom/import/yaml_import_spec.rb +40 -0
  39. data/spec/fathom/import_spec.rb +22 -0
  40. data/spec/fathom/knowledge_base_spec.rb +16 -0
  41. data/spec/fathom/monte_carlo_set_spec.rb +58 -0
  42. data/spec/fathom/plausible_range_spec.rb +130 -0
  43. data/spec/fathom/value_description_spec.rb +70 -0
  44. data/spec/fathom_spec.rb +8 -0
  45. data/spec/spec_helper.rb +13 -0
  46. data/spec/support/demo.yml +17 -0
  47. metadata +135 -0
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
+ class Fathom::MonteCarloSet
3
+
4
+ class << self
5
+ def define_key(key)
6
+ define_method(key.to_sym) do
7
+ self.samples[key.to_sym]
8
+ end
9
+ end
10
+ end
11
+
12
+ attr_reader :value_description, :samples_taken, :samples
13
+
14
+ def initialize(value_description)
15
+ @value_description = value_description
16
+ @samples = {}
17
+ end
18
+
19
+ def process(n=10_000)
20
+ @samples_taken = n
21
+ @samples_taken.times do
22
+ result = value_description.process
23
+ store(result)
24
+ end
25
+ assert_sample_vectors
26
+ end
27
+
28
+ def reset!
29
+ @samples = {}
30
+ @keys_asserted = nil
31
+ end
32
+
33
+ protected
34
+
35
+ def assert_sample_vectors
36
+ vectors = @samples.inject({}) do |h, o|
37
+ key, array = o.first, o.last
38
+ h[key] = GSL::Vector.ary_to_gv(array)
39
+ h
40
+ end
41
+ @samples = vectors
42
+ end
43
+
44
+ def store(result)
45
+ result = assert_result_hash(result)
46
+ assert_keys(result)
47
+ result.each do |key, value|
48
+ @samples[key.to_sym] << value
49
+ end
50
+ end
51
+
52
+ def assert_result_hash(result)
53
+ result.is_a?(Hash) ? result : {:result => result}
54
+ end
55
+
56
+ # Assumes the same value description for all samples taken
57
+ def assert_keys(result)
58
+ return true if @keys_asserted
59
+ result.keys.each do |key|
60
+ assert_key(key)
61
+ end
62
+ @keys_asserted = true
63
+ end
64
+
65
+ def assert_key(key)
66
+ self.class.define_key(key)
67
+ @samples[key.to_sym] ||= []
68
+ end
69
+
70
+ end
71
+
72
+ if __FILE__ == $0
73
+ include Fathom
74
+ # TODO: Is there anything you want to do to run this file on its own?
75
+ # MonteCarloSet.new
76
+ end
@@ -0,0 +1,8 @@
1
+ module Fathom
2
+ module NodeUtilities
3
+ def name_sym
4
+ return nil unless self.name
5
+ @name_sym ||= self.name.to_s.downcase.gsub(/\s+/, '_').to_sym
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
+ module Fathom
3
+ class PlausibleRange
4
+
5
+ include NodeUtilities
6
+
7
+ attr_reader :upper_bound, :lower_bound, :confidence_interval, :name, :description, :hard_lower_bound, :hard_upper_bound
8
+
9
+ def initialize(opts={})
10
+
11
+ opts = OptionsHash.new(opts)
12
+
13
+ @hard_upper_bound = opts[:hard_upper_bound]
14
+ @upper_bound = opts[:upper_bound]
15
+ @upper_bound ||= opts[:max]
16
+ @upper_bound ||= @hard_upper_bound
17
+ @upper_bound = @hard_upper_bound if @upper_bound and @hard_upper_bound and @hard_upper_bound < @upper_bound
18
+ raise ArgumentError, "Must provide an upper bound." unless @upper_bound
19
+
20
+ @hard_lower_bound = opts[:hard_lower_bound]
21
+ @lower_bound = opts[:lower_bound]
22
+ @lower_bound ||= opts[:min]
23
+ @lower_bound ||= @hard_lower_bound
24
+ @lower_bound = @hard_lower_bound if @lower_bound and @hard_lower_bound and @hard_lower_bound > @lower_bound
25
+ raise ArgumentError, "Must provide a lower bound." unless @lower_bound
26
+
27
+ @confidence_interval = opts[:confidence_interval]
28
+ @confidence_interval ||= opts[:ci]
29
+ @confidence_interval ||= 0.9
30
+
31
+ @name = opts[:name]
32
+ @description = opts[:description]
33
+
34
+ end
35
+
36
+ alias :min :lower_bound
37
+ alias :max :upper_bound
38
+ alias :ci :confidence_interval
39
+
40
+ def midpoint
41
+ @midpoint ||= lower_bound + (range / 2.0)
42
+ end
43
+
44
+ def range
45
+ @range ||= upper_bound - lower_bound
46
+ end
47
+
48
+ def standard_deviation
49
+ @standard_deviation ||= range / 3.29
50
+ end
51
+ alias :std :standard_deviation
52
+
53
+ # Not using specific distributions yet
54
+ def rand
55
+ rng.gaussian(std) + midpoint
56
+ end
57
+
58
+ def array_of_random_values(n=10)
59
+ n.times.map {self.rand}
60
+ end
61
+ alias :to_a :array_of_random_values
62
+
63
+ def vector_of_random_values(n=10)
64
+ GSL::Vector.alloc(array_of_random_values(n))
65
+ end
66
+ alias :to_v :vector_of_random_values
67
+
68
+ protected
69
+ def rng
70
+ @rng ||= GSL::Rng.alloc
71
+ end
72
+ end
73
+
74
+ class R < PlausibleRange
75
+ end
76
+ end
77
+
78
+ if __FILE__ == $0
79
+ include Fathom
80
+ # TODO: Is there anything you want to do to run this file on its own?
81
+ # PlausibleRange.new
82
+ end
@@ -0,0 +1,11 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
+ module Fathom
3
+ class ValueAggregator < ValueDescription
4
+ end
5
+ end
6
+
7
+ if __FILE__ == $0
8
+ include Fathom
9
+ # TODO: Is there anything you want to do to run this file on its own?
10
+ # ValueAggregator.new
11
+ end
@@ -0,0 +1,79 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
+ module Fathom
3
+ class ValueDescription
4
+
5
+ class << self
6
+ # Returns a simple accessor for the node, so that the value description works a little like an OpenStruct.
7
+ def define_node_accessor(node, method = :node)
8
+ define_method(node.name_sym) do
9
+ node.send(method)
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ attr_reader :nodes, :last_process
16
+
17
+ def initialize(*nodes, &block)
18
+ assert_nodes(nodes)
19
+ @process_block = block if block_given?
20
+ end
21
+
22
+ def add_node(node, method = :rand)
23
+ assert_node(node, method)
24
+ end
25
+
26
+ def process
27
+ prepare_process
28
+ @process_block ? @process_block.call(@last_process) : default_process(@last_process)
29
+ end
30
+
31
+ protected
32
+
33
+
34
+ def prepare_process
35
+ @last_process = self.nodes.inject(OpenStruct.new) do |o, node|
36
+ o.table[node.name_sym] = self.send(node.name_sym)
37
+ o
38
+ end
39
+ end
40
+
41
+ def default_process(obj)
42
+ obj.values.inject(0.0) do |s, e|
43
+ s += e
44
+ end
45
+ end
46
+
47
+ def assert_nodes(nodes)
48
+ @nodes ||= []
49
+ nodes.each do |node|
50
+ case node
51
+ when Hash
52
+ assert_node_from_hash(node)
53
+ else
54
+ assert_node(node)
55
+ end
56
+ end
57
+ end
58
+
59
+ def assert_node(node, method = :rand)
60
+ raise ArgumentError, "Must provide a node that can respond to name" unless node.respond_to?(:name)
61
+ @nodes ||= []
62
+ self.class.define_node_accessor(node, method)
63
+ @nodes << node
64
+ end
65
+
66
+ # Takes a node => :value_hash signature for the values in the hash
67
+ def assert_node_from_hash(hash)
68
+ hash.each do |node, value_method|
69
+ assert_node(node, value_method)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ if __FILE__ == $0
76
+ include Fathom
77
+ # TODO: Is there anything you want to do to run this file on its own?
78
+ # ValueDescription.new
79
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
+ module Fathom
3
+ class ValueMultiplier < ValueDescription
4
+
5
+ def default_process
6
+ obj.values.inject(0.0) do |s, e|
7
+ s *= e
8
+ end
9
+ end
10
+ protected :default_process
11
+ end
12
+ end
13
+
14
+ if __FILE__ == $0
15
+ include Fathom
16
+ # TODO: Is there anything you want to do to run this file on its own?
17
+ # ValueMultiplier.new
18
+ end
@@ -0,0 +1,186 @@
1
+ # Taken brazenly from ActiveSupport
2
+ class OptionsHash < Hash
3
+ # Return a new hash with all keys converted to strings.
4
+ def stringify_keys
5
+ dup.stringify_keys!
6
+ end
7
+
8
+ # Destructively convert all keys to strings.
9
+ def stringify_keys!
10
+ keys.each do |key|
11
+ self[key.to_s] = delete(key)
12
+ end
13
+ self
14
+ end
15
+
16
+ # Return a new hash with all keys converted to symbols, as long as
17
+ # they respond to +to_sym+.
18
+ def symbolize_keys
19
+ dup.symbolize_keys!
20
+ end
21
+
22
+ # Destructively convert all keys to symbols, as long as they respond
23
+ # to +to_sym+.
24
+ def symbolize_keys!
25
+ keys.each do |key|
26
+ self[(key.to_sym rescue key) || key] = delete(key)
27
+ end
28
+ self
29
+ end
30
+
31
+ alias_method :to_options, :symbolize_keys
32
+ alias_method :to_options!, :symbolize_keys!
33
+
34
+ # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
35
+ # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
36
+ # as keys, this will fail.
37
+ #
38
+ # ==== Examples
39
+ # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
40
+ # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
41
+ # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
42
+ def assert_valid_keys(*valid_keys)
43
+ unknown_keys = keys - [valid_keys].flatten
44
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
45
+ end
46
+
47
+ def extractable_options?
48
+ true
49
+ end
50
+
51
+ def initialize(constructor = {})
52
+ if constructor.is_a?(Hash)
53
+ super()
54
+ update(constructor)
55
+ else
56
+ super(constructor)
57
+ end
58
+ end
59
+
60
+ def default(key = nil)
61
+ if key.is_a?(Symbol) && include?(key = key.to_s)
62
+ self[key]
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ def self.new_from_hash_copying_default(hash)
69
+ OptionsHash.new(hash).tap do |new_hash|
70
+ new_hash.default = hash.default
71
+ end
72
+ end
73
+
74
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
75
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
76
+
77
+ # Assigns a new value to the hash:
78
+ #
79
+ # hash = OptionsHash.new
80
+ # hash[:key] = "value"
81
+ #
82
+ def []=(key, value)
83
+ regular_writer(convert_key(key), convert_value(value))
84
+ end
85
+
86
+ # Updates the instantized hash with values from the second:
87
+ #
88
+ # hash_1 = OptionsHash.new
89
+ # hash_1[:key] = "value"
90
+ #
91
+ # hash_2 = OptionsHash.new
92
+ # hash_2[:key] = "New Value!"
93
+ #
94
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
95
+ #
96
+ def update(other_hash)
97
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
98
+ self
99
+ end
100
+
101
+ alias_method :merge!, :update
102
+
103
+ # Checks the hash for a key matching the argument passed in:
104
+ #
105
+ # hash = OptionsHash.new
106
+ # hash["key"] = "value"
107
+ # hash.key? :key # => true
108
+ # hash.key? "key" # => true
109
+ #
110
+ def key?(key)
111
+ super(convert_key(key))
112
+ end
113
+
114
+ alias_method :include?, :key?
115
+ alias_method :has_key?, :key?
116
+ alias_method :member?, :key?
117
+
118
+ # Fetches the value for the specified key, same as doing hash[key]
119
+ def fetch(key, *extras)
120
+ super(convert_key(key), *extras)
121
+ end
122
+
123
+ # Returns an array of the values at the specified indices:
124
+ #
125
+ # hash = OptionsHash.new
126
+ # hash[:a] = "x"
127
+ # hash[:b] = "y"
128
+ # hash.values_at("a", "b") # => ["x", "y"]
129
+ #
130
+ def values_at(*indices)
131
+ indices.collect {|key| self[convert_key(key)]}
132
+ end
133
+
134
+ # Returns an exact copy of the hash.
135
+ def dup
136
+ OptionsHash.new(self)
137
+ end
138
+
139
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
140
+ # Does not overwrite the existing hash.
141
+ def merge(hash)
142
+ self.dup.update(hash)
143
+ end
144
+
145
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
146
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a OptionsHash.
147
+ def reverse_merge(other_hash)
148
+ super self.class.new_from_hash_copying_default(other_hash)
149
+ end
150
+
151
+ def reverse_merge!(other_hash)
152
+ replace(reverse_merge( other_hash ))
153
+ end
154
+
155
+ # Removes a specified key from the hash.
156
+ def delete(key)
157
+ super(convert_key(key))
158
+ end
159
+
160
+ def stringify_keys!; self end
161
+ def stringify_keys; dup end
162
+ undef :symbolize_keys!
163
+ def symbolize_keys; to_hash.symbolize_keys end
164
+ def to_options!; self end
165
+
166
+ # Convert to a Hash with String keys.
167
+ def to_hash
168
+ Hash.new(default).merge!(self)
169
+ end
170
+
171
+ protected
172
+ def convert_key(key)
173
+ key.kind_of?(Symbol) ? key.to_s : key
174
+ end
175
+
176
+ def convert_value(value)
177
+ case value
178
+ when Hash
179
+ self.class.new_from_hash_copying_default(value)
180
+ when Array
181
+ value.collect { |e| e.is_a?(Hash) ? self.class.new_from_hash_copying_default(e) : e }
182
+ else
183
+ value
184
+ end
185
+ end
186
+ end