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,139 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
2
- # TODO: Move this into a proper configuration module
3
- # require 'spira'
4
- # @repository = RDF::Repository.new
5
- # Spira.add_repository(:default, @repository)
6
-
7
- class Fathom::Node
8
-
9
- # See notes in the spec about this.
10
- # include Spira::Resource
11
-
12
- attr_reader :name, :distribution, :description, :values
13
-
14
- def initialize(opts={})
15
- symbolize_keys!(opts)
16
- @name = opts[:name]
17
- assert_distribution(opts)
18
- @description = opts[:description]
19
- @values = opts[:values]
20
- assert_links(opts)
21
- end
22
-
23
- def name_sym
24
- return nil unless self.name
25
- @name_sym ||= self.name.to_s.downcase.gsub(/\s+|-+/, '_').to_sym
26
- end
27
-
28
- def parents
29
- @parents ||= []
30
- end
31
-
32
- def add_parent(parent)
33
- self.parents << parent
34
- self.add_accessor_for_node(parent)
35
- parent.register_child(self)
36
- end
37
-
38
- def register_child(child)
39
- raise "Cannot register a child if this node is not a parent already. Use add_parent to the other node or add_child to this node." unless
40
- child.parents.include?(self)
41
- unless children.include?(child)
42
- self.add_accessor_for_node(child)
43
- children << child
44
- end
45
- true
46
- end
47
-
48
- def children
49
- @children ||= []
50
- end
51
-
52
- def add_child(child)
53
- self.children << child
54
- self.add_accessor_for_node(child)
55
- child.register_parent(self)
56
- end
57
-
58
- def register_parent(parent)
59
- raise "Cannot register a parent if this node is not a child already. Use add_child to the other node or add_parent to this node." unless
60
- parent.children.include?(self)
61
- unless parents.include?(parent)
62
- self.add_accessor_for_node(parent)
63
- parents << parent
64
- end
65
- true
66
- end
67
-
68
- def simple_inspect
69
- self.name ? "#{self.name} (#{self.class.to_s})" : self.class.to_s
70
- end
71
-
72
- def inspect
73
- "#{self.class.to_s}: " + [
74
- self.name,
75
- self.description,
76
- "children:",
77
- self.children.map {|e| e.simple_inspect }.inspect,
78
- "parents: ",
79
- self.parents.map {|e| e.simple_inspect }.inspect,
80
- ].compact.join(", ")
81
- end
82
-
83
- protected
84
-
85
- # Quick and dirty extract from ActiveSupport's same method
86
- def symbolize_keys!(h)
87
- h.keys.each do |key|
88
- h[(key.to_sym rescue key) || key] = h.delete(key)
89
- end
90
- h
91
- end
92
-
93
- def add_accessor_for_node(node)
94
- return false unless node.respond_to?(:name_sym) and node.name_sym
95
- return false if self.respond_to?(node.name_sym)
96
- (class << self; self; end).module_eval do
97
- define_method node.name_sym do
98
- node
99
- end
100
- end
101
- end
102
-
103
- def assert_links(opts)
104
- found = opts[:parents]
105
- found ||= opts[:parent]
106
- found ||= []
107
- found = [found] unless found.is_a?(Array)
108
- found.each do |parent|
109
- add_parent(parent)
110
- end
111
-
112
- found = opts[:children]
113
- found ||= opts[:child]
114
- found ||= []
115
- found = [found] unless found.is_a?(Array)
116
- found.each do |child|
117
- add_child(child)
118
- end
119
- end
120
-
121
- def assert_distribution(opts)
122
- case opts[:distribution]
123
- when Class
124
- @distribution = opts[:distribution]
125
- when Symbol
126
- class_name = opts[:distribution].to_s.downcase.split(/_+/).map {|t| t[0].chr.upcase + t[1..-1]}.join
127
- if Fathom::Distributions.constants.include?(class_name)
128
- @distribution = "Fathom::Distributions::#{class_name}".constantize
129
- end
130
- end
131
- @distribution ||= Fathom::Distributions::Gaussian
132
- end
133
- end
134
-
135
- if __FILE__ == $0
136
- include Fathom
137
- # TODO: Is there anything you want to do to run this file on its own?
138
- # Node.new
139
- end
@@ -1,121 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fathom'))
2
- class Fathom::BeliefNode < DiscreteNode
3
-
4
- attr_reader :probabilities, :likelihoods, :precision_threshold
5
-
6
- def initialize(opts={})
7
- super(opts)
8
- assert_probabilities(opts)
9
- assert_liklihoods(opts)
10
- @precision_threshold = opts.fetch(:precision_threshold, 0.00001)
11
- end
12
-
13
- def add_child(child)
14
- if child.is_a?(BeliefNode)
15
- cpm = CPMNode.new(:parent => self, :child => child)
16
- # self.children << cpm
17
- self.add_accessor_for_cpm(cpm, child)
18
- self.add_accessor_for_node(child)
19
- # cpm.register_parent(self)
20
- self.children << child
21
- child.register_parent(self)
22
- child.add_accessor_for_cpm(cpm, self)
23
- else
24
- super(child)
25
- end
26
- end
27
-
28
- def add_parent(parent)
29
- if parent.is_a?(BeliefNode)
30
- cpm = CPMNode.new(:parent => parent, :child => self)
31
- # self.parents << cpm
32
- self.add_accessor_for_cpm(cpm, parent)
33
- self.add_accessor_for_node(parent)
34
- # cpm.register_child(self)
35
- self.parents << parent
36
- parent.register_child(self)
37
- parent.add_accessor_for_cpm(cpm, self)
38
- else
39
- super(parent)
40
- end
41
- end
42
-
43
- def inspect
44
- "#{self.class.to_s}: " + [
45
- self.name,
46
- self.description,
47
- "children:",
48
- self.children.map {|e| e.is_a?(CPMNode) ? e.child.simple_inspect : e.simple_inspect }.inspect,
49
- "parents: ",
50
- self.parents.map {|e| e.is_a?(CPMNode) ? e.parent.simple_inspect : e.simple_inspect }.inspect,
51
- ].compact.join(", ")
52
- end
53
-
54
- def add_accessor_for_cpm(cpm, node)
55
- return false unless cpm.is_a?(CPMNode) and cpm.name_sym
56
- method_name = ("cpm_for_" + node.name_sym.to_s).to_sym
57
- return false if self.respond_to?(method_name)
58
- (class << self; self; end).module_eval do
59
- define_method method_name do
60
- cpm
61
- end
62
- end
63
- end
64
-
65
- def likelihood(label)
66
- OpenStruct.new
67
- end
68
-
69
- protected
70
-
71
-
72
- def assert_probabilities(opts)
73
- return assert_probabilities_and_labels_from_values_hash(opts[:values]) if
74
- opts[:values] and opts[:values].is_a?(Hash)
75
-
76
- unnormalized_obj = opts.fetch(:probabilities, Array.new(self.size, 1.0))
77
- unnormalized_vector = case unnormalized_obj
78
- when Array
79
- GSL::Vector.ary_to_gv(unnormalized_obj)
80
- when GSL::Vector
81
- unnormalized_obj
82
- else
83
- GSL::Vector[unnormalized_obj]
84
- end
85
-
86
- raise ArgumentError, "Probabilities must be #{self.size} items long" unless
87
- unnormalized_vector.size == self.size
88
-
89
- sum = unnormalized_vector.sum
90
- @probabilities = unnormalized_vector.map {|e| e / sum }
91
- end
92
-
93
- def assert_probabilities_and_labels_from_values_hash(values)
94
- @labels, probabilities = values.inject([[], []]) do |list, e|
95
- list.first << e.first
96
- list.last << e.last
97
- list
98
- end
99
- @probabilities = GSL::Vector.ary_to_gv(probabilities)
100
- end
101
-
102
- def assert_liklihoods(opts)
103
- likelihoods = opts.fetch(:likelihoods, Array.new(self.size, 1.0))
104
- @likelihoods = case likelihoods
105
- when Array
106
- GSL::Vector.ary_to_gv(likelihoods)
107
- when GSL::Vector
108
- likelihoods
109
- else
110
- GSL::Vector[likelihoods]
111
- end
112
- raise ArgumentError, "Likelihoods must be #{self.size} items long" unless
113
- likelihoods.size == self.size
114
- end
115
- end
116
-
117
- if __FILE__ == $0
118
- include Fathom
119
- # TODO: Is there anything you want to do to run this file on its own?
120
- # BeliefNode.new
121
- end
@@ -1,100 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fathom'))
2
-
3
- # Conditional Probability Matrix to join two nodes
4
- class Fathom::CPMNode < Fathom::Node
5
-
6
- def initialize(opts={})
7
- ensure_belief_nodes(opts)
8
- super(opts)
9
- assert_name
10
- assert_description
11
- assert_cpm
12
- end
13
-
14
- def parent
15
- parents.first
16
- end
17
-
18
- def child
19
- children.first
20
- end
21
-
22
- alias :matrix :values
23
-
24
- # Filter values from the matrix. Allows us to grab multiple rows and columns if desired.
25
- # If the rows or columns aren't filtered, all values are assumed to be desired.
26
- #
27
- # @cpm.probability :child_node_name => [:desired, :values], :parent_node_name => :value
28
- # This filters both the child columns and the parent rows
29
- #
30
- # @cpm.probability :child_node_name => [:desired, :values]
31
- # This only filters the child columns
32
- def probability(opts={})
33
- # Are we using long descriptions for the return value?
34
- # If so, we'll use a hash to describe it without having to parse the value out of a string later.
35
- describe = opts.delete(:describe) || false
36
-
37
- # Is something unknown being asked for?
38
- allowed = [parent.name_sym, child.name_sym]
39
- unknown_keys = opts.reject {|k, v| allowed.include?(k)}
40
- raise ArgumentError, "Unknown node: #{unknown_keys.inspect}" unless unknown_keys.empty?
41
-
42
- # Values for the desired child and parent values
43
- child_values = Array(opts[self.child.name_sym] || self.child.labels)
44
- parent_values = Array(opts[self.parent.name_sym] || self.parent.labels)
45
-
46
- # Indices in the matrix for the desired values
47
- child_indices = child_values.map {|c| self.child.labels.index(c)}
48
- parent_indices = parent_values.map {|c| self.parent.labels.index(c)}
49
-
50
- # Collect the filtered values from the matrix
51
- value = parent_indices.inject(0.0) do |sum, row|
52
- sum += child_indices.inject(0.0) do |s, col|
53
- s += matrix.get(row, col)
54
- end
55
- end
56
-
57
- return value unless describe
58
-
59
- label = "P(" +
60
- child_values.map(&:to_s).join(" or ") +
61
- " | " +
62
- parent_values.map(&:to_s).join(" or ") +
63
- ")"
64
- { label => value }
65
-
66
- end
67
- alias :p :probability
68
-
69
- def odds(opts={})
70
- p = probability(opts)
71
- return p / (1 - p)
72
- end
73
- alias :o :odds
74
-
75
- # Returns a vector of likelihoods for each parent value, given the child value
76
- def likelihood(value)
77
- GSL::Vector.alloc(
78
- *parent.labels.map {|parent_label| probability(parent.name_sym => parent_label, child.name_sym => value)}
79
- )
80
- end
81
- alias :l :likelihood
82
-
83
- protected
84
- def assert_name
85
- @name ||= :cpm
86
- end
87
-
88
- def assert_description
89
- @description ||= "Conditional Probability Matrix from #{parent.name.to_s} to #{child.name.to_s}."
90
- end
91
-
92
- def ensure_belief_nodes(opts)
93
- raise ArgumentError, "The child must be a BeliefNode" unless opts[:child].is_a?(BeliefNode)
94
- raise ArgumentError, "The parent must be a BeliefNode" unless opts[:parent].is_a?(BeliefNode)
95
- end
96
-
97
- def assert_cpm
98
- @values = self.parent.probabilities.col * self.child.probabilities
99
- end
100
- end
@@ -1,97 +0,0 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fathom'))
2
-
3
- =begin
4
- This class uses SQLite for in-memory set operations. It is based on a discrete variable
5
- which can be translated into fields in a table. For now, I am just using float data
6
- types for the fields. This will evolve as needs drive it into a more robust data set.
7
-
8
- It turns out that SQLite set operations are quite fast and speed things up pretty well.
9
- So, to use this class, you'll need to have sqlite3-ruby SQLite3 bindings installed.
10
-
11
- This also uses uuid to enforce a node name, an additional dependency.
12
- =end
13
-
14
- require 'uuid'
15
-
16
- class Fathom::DataCollection < DiscreteNode
17
-
18
- def initialize(opts={})
19
- opts = extract_labels(opts)
20
- opts[:name] ||= UUID.generate
21
- super(opts)
22
- end
23
-
24
-
25
- protected
26
-
27
- # Looking for labels.
28
- # Using :labels, then :parents, then :parent, looking for the first node with labels defined
29
- def extract_labels(opts)
30
- return opts if opts[:labels]
31
- parents = opts[:parents]
32
- parents ||= opts[:parent]
33
- parents = Array[parents] if parents and not parents.is_a?(Array)
34
- parents.each do |parent|
35
- if parent.respond_to?(:labels)
36
- opts[:labels] = parent.labels
37
- return opts
38
- end
39
- end
40
- opts
41
- end
42
-
43
- end
44
-
45
- if __FILE__ == $0
46
- include Fathom
47
- # TODO: Is there anything you want to do to run this file on its own?
48
- # DataCollection.new
49
- end
50
-
51
-
52
- # J2: Some bare-minimum sqlite3 stuff
53
- # See: http://sqlite-ruby.rubyforge.org/
54
- # require 'rubygems'
55
- # require 'sqlite3'
56
- #
57
- # def prepare_database(table_name)
58
- # @db = SQLite3::Database.new(":memory:")
59
- # # @db = SQLite3::Database.new("/tmp/j2.db")
60
- #
61
- # create_sql = <<-SQL
62
- #
63
- # CREATE TABLE "#{table_name}" (
64
- # "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
65
- # "field1" FLOAT);
66
- # SQL
67
- # @db.execute_batch(create_sql)
68
- #
69
- # insert_sql = <<-SQL
70
- # INSERT INTO #{table_name}
71
- # ("field1")
72
- # VALUES
73
- # (:field1);
74
- # SQL
75
- # @insert_record = @db.prepare(insert_sql)
76
- #
77
- # # select_sql = <<-SQL
78
- # # "select * from 'asdf';"
79
- # # SQL
80
- # # @select_record = @db.prepare(select_sql)
81
- # @select_record = @db.prepare( "select * from asdf" )
82
- # end
83
- #
84
- # def insert_record(opts)
85
- # @insert_record.bind_params(:field1 => opts[:field1])
86
- # @insert_record.execute
87
- # end
88
- #
89
- # def select_record(opts={})
90
- # # @db.execute( "select * from 'asdf'" )
91
- # @select_record.execute.entries
92
- # end
93
- #
94
- # prepare_database('asdf')
95
- # insert_record :field1 => 123.1
96
- # insert_record :field1 => 122.2
97
- # @a = select_record