fathom 0.3.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +7 -5
- data/.document +2 -2
- data/Gemfile +9 -10
- data/{LICENSE → LICENSE.txt} +1 -1
- data/README.md +29 -90
- data/Rakefile +34 -32
- data/VERSION +1 -1
- data/fathom.gemspec +105 -0
- data/features/fathom.feature +26 -0
- data/features/step_definitions/fathom_steps.rb +23 -0
- data/features/support/env.rb +13 -0
- data/lib/ext/array.rb +6 -2
- data/lib/ext/string.rb +86 -7
- data/lib/fathom.rb +51 -88
- data/lib/fathom/behaviors/attribute_system.rb +91 -0
- data/lib/fathom/behaviors/context_behavior.rb +28 -0
- data/lib/fathom/behaviors/plugins.rb +16 -0
- data/lib/fathom/contexts/network_population.rb +47 -0
- data/lib/fathom/contexts/network_traversal.rb +4 -0
- data/lib/fathom/data/adjacency_matrix.rb +27 -0
- data/lib/fathom/data/definition.rb +22 -0
- data/lib/fathom/data/edge.rb +58 -0
- data/lib/fathom/data/network.rb +35 -0
- data/lib/fathom/data/outcome.rb +30 -0
- data/lib/fathom/data/property.rb +31 -0
- data/lib/fathom/data/variable.rb +59 -0
- data/lib/fathom/roles/general_graph_tools.rb +87 -0
- data/lib/fathom/roles/network_builder.rb +61 -0
- data/spec/fathom/behaviors/attribute_system_spec.rb +141 -0
- data/spec/fathom/behaviors/context_behavior_spec.rb +15 -0
- data/spec/fathom/behaviors/plugins_spec.rb +80 -0
- data/spec/fathom/contexts/network_population_spec.rb +55 -0
- data/spec/fathom/contexts/network_traversal_spec.rb +11 -0
- data/spec/fathom/data/adjacency_matrix_spec.rb +42 -0
- data/spec/fathom/data/definition_spec.rb +19 -0
- data/spec/fathom/data/edge_spec.rb +77 -0
- data/spec/fathom/data/network_spec.rb +72 -0
- data/spec/fathom/data/outcome_spec.rb +17 -0
- data/spec/fathom/data/property_spec.rb +17 -0
- data/spec/fathom/data/variable_spec.rb +101 -0
- data/spec/fathom/ext/array_spec.rb +17 -0
- data/spec/fathom/ext/string_spec.rb +90 -0
- data/spec/fathom/roles/general_graph_tools_spec.rb +95 -0
- data/spec/fathom/roles/network_builder_spec.rb +90 -0
- data/spec/fathom_spec.rb +28 -49
- data/spec/spec_helper.rb +7 -11
- data/spec/support/context_behavior.rb +14 -0
- data/spec/support/custom_matchers.rb +12 -0
- data/spec/support/files.rb +8 -0
- data/spec/support/network.yml +42 -0
- metadata +133 -174
- data/.bundle/config +0 -2
- data/.gitignore +0 -6
- data/Gemfile.lock +0 -42
- data/TODO.md +0 -127
- data/autotest/discover.rb +0 -1
- data/lib/ext/faster_csv.rb +0 -1
- data/lib/ext/open_struct.rb +0 -17
- data/lib/fathom/agent.rb +0 -48
- data/lib/fathom/agent/agent_cluster.rb +0 -23
- data/lib/fathom/agent/properties.rb +0 -48
- data/lib/fathom/archive/causal_graph.rb +0 -12
- data/lib/fathom/archive/concept.rb +0 -83
- data/lib/fathom/archive/conditional_probability_matrix.rb +0 -119
- data/lib/fathom/archive/inverter.rb +0 -20
- data/lib/fathom/archive/n2.rb +0 -198
- data/lib/fathom/archive/n3.rb +0 -119
- data/lib/fathom/archive/node.rb +0 -97
- data/lib/fathom/archive/noodle.rb +0 -136
- data/lib/fathom/archive/scratch.rb +0 -45
- data/lib/fathom/distributions.rb +0 -8
- data/lib/fathom/distributions/discrete_gaussian.rb +0 -44
- data/lib/fathom/distributions/discrete_uniform.rb +0 -25
- data/lib/fathom/distributions/gaussian.rb +0 -46
- data/lib/fathom/distributions/uniform.rb +0 -35
- data/lib/fathom/import.rb +0 -85
- data/lib/fathom/import/csv_import.rb +0 -59
- data/lib/fathom/import/import_node.rb +0 -17
- data/lib/fathom/import/yaml_import.rb +0 -74
- data/lib/fathom/knowledge_base.rb +0 -46
- data/lib/fathom/knowledge_base/search.rb +0 -19
- data/lib/fathom/monte_carlo_set.rb +0 -152
- data/lib/fathom/node.rb +0 -139
- data/lib/fathom/node/belief_node.rb +0 -121
- data/lib/fathom/node/cpm_node.rb +0 -100
- data/lib/fathom/node/data_collection.rb +0 -97
- data/lib/fathom/node/data_node.rb +0 -22
- data/lib/fathom/node/decision.rb +0 -11
- data/lib/fathom/node/discrete_node.rb +0 -41
- data/lib/fathom/node/fact.rb +0 -24
- data/lib/fathom/node/mc_node.rb +0 -70
- data/lib/fathom/node/node_extensions/enforced_name.rb +0 -12
- data/lib/fathom/node/node_extensions/numeric_methods.rb +0 -68
- data/lib/fathom/node/plausible_range.rb +0 -98
- data/lib/fathom/simulation.rb +0 -59
- data/lib/fathom/simulation/tick_methods.rb +0 -25
- data/lib/fathom/simulation/tick_simulation.rb +0 -12
- data/lib/fathom/value_description.rb +0 -79
- data/lib/options_hash.rb +0 -186
- data/spec/ext/array_spec.rb +0 -10
- data/spec/ext/faster_csv_spec.rb +0 -10
- data/spec/ext/open_struct_spec.rb +0 -20
- data/spec/ext/string_spec.rb +0 -7
- data/spec/fathom/agent/agent_cluster_spec.rb +0 -17
- data/spec/fathom/agent_spec.rb +0 -51
- data/spec/fathom/distributions/discrete_gaussian_spec.rb +0 -64
- data/spec/fathom/distributions/discrete_uniform_spec.rb +0 -0
- data/spec/fathom/distributions/gaussian_spec.rb +0 -64
- data/spec/fathom/distributions/uniform_spec.rb +0 -0
- data/spec/fathom/import/csv_import_spec.rb +0 -52
- data/spec/fathom/import/import_node_spec.rb +0 -10
- data/spec/fathom/import/yaml_import_spec.rb +0 -73
- data/spec/fathom/import_spec.rb +0 -36
- data/spec/fathom/knowledge_base_spec.rb +0 -20
- data/spec/fathom/monte_carlo_set_spec.rb +0 -149
- data/spec/fathom/node/belief_node_spec.rb +0 -180
- data/spec/fathom/node/cpm_node_spec.rb +0 -144
- data/spec/fathom/node/data_collection_spec.rb +0 -26
- data/spec/fathom/node/data_node_spec.rb +0 -102
- data/spec/fathom/node/decision_spec.rb +0 -15
- data/spec/fathom/node/discrete_node_spec.rb +0 -56
- data/spec/fathom/node/fact_spec.rb +0 -33
- data/spec/fathom/node/mc_node_spec.rb +0 -66
- data/spec/fathom/node/node_extensions/enforced_name_spec.rb +0 -15
- data/spec/fathom/node/node_extensions/numeric_methods_spec.rb +0 -124
- data/spec/fathom/node/plausible_range_spec.rb +0 -151
- data/spec/fathom/node_spec.rb +0 -172
- data/spec/fathom/simulation/tick_simulation_spec.rb +0 -32
- data/spec/fathom/simulation_spec.rb +0 -24
- data/spec/fathom/value_description_spec.rb +0 -70
- data/spec/support/demo.yml +0 -17
- data/spec/support/demo_agent.rb +0 -8
- data/spec/support/dummy_numeric_node.rb +0 -8
- data/spec/support/fact.yml +0 -11
data/autotest/discover.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
Autotest.add_discovery { "rspec2" }
|
data/lib/ext/faster_csv.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
FasterCSV::HeaderConverters[:strip] = lambda{|h| h.strip}
|
data/lib/ext/open_struct.rb
DELETED
data/lib/fathom/agent.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
|
2
|
-
class Fathom::Agent
|
3
|
-
|
4
|
-
# =================
|
5
|
-
# = Class Methods =
|
6
|
-
# =================
|
7
|
-
|
8
|
-
include Properties
|
9
|
-
|
10
|
-
def initialize(opts={})
|
11
|
-
self.class.define_property_states
|
12
|
-
assert_node_accessors(opts)
|
13
|
-
end
|
14
|
-
|
15
|
-
def states
|
16
|
-
@states ||= self.class.properties.inject({}) do |h, state_method_name|
|
17
|
-
h[state_method_name] = nil
|
18
|
-
h
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def callbacks
|
23
|
-
@callbacks ||= (self.methods - Object.methods).grep(/^on_(\w+)/)
|
24
|
-
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
def assert_node_accessors(nodes)
|
28
|
-
nodes.each do |name, node|
|
29
|
-
next unless self.class.properties.include?(name)
|
30
|
-
states[name] = node.respond_to?(:rand) ? node.rand : node
|
31
|
-
|
32
|
-
method_name = ("node_for_" + name.to_s.downcase.gsub(/\s+/, '_')).to_sym
|
33
|
-
(class << self; self; end).module_eval do
|
34
|
-
define_method method_name do
|
35
|
-
node
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
if __FILE__ == $0
|
45
|
-
include Fathom
|
46
|
-
# TODO: Is there anything you want to do to run this file on its own?
|
47
|
-
# Agent.new
|
48
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fathom'))
|
2
|
-
|
3
|
-
=begin
|
4
|
-
This class is designed to hold a cluster of agents in memory. It runs the simulation locally and
|
5
|
-
speaks to other clusters via EventMachine. In this way, we don't need a Ruby runtime/thread/fiber
|
6
|
-
for each agent, just one per dozen/hundred/thousand agents, depending on what balances the
|
7
|
-
simulation.
|
8
|
-
=end
|
9
|
-
class Fathom::AgentCluster
|
10
|
-
|
11
|
-
attr_reader :agents
|
12
|
-
|
13
|
-
def initialize(*agents)
|
14
|
-
@agents = agents
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
if __FILE__ == $0
|
20
|
-
include Fathom
|
21
|
-
# TODO: Is there anything you want to do to run this file on its own?
|
22
|
-
# AgentCluster.new
|
23
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fathom'))
|
2
|
-
module Fathom
|
3
|
-
module Properties
|
4
|
-
|
5
|
-
def self.included(base)
|
6
|
-
base.send(:extend, ClassMethods)
|
7
|
-
end
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
|
11
|
-
def properties
|
12
|
-
@properties ||= []
|
13
|
-
end
|
14
|
-
|
15
|
-
def property(name, opts={})
|
16
|
-
self.properties << name_sym(name)
|
17
|
-
end
|
18
|
-
|
19
|
-
def define_property_states
|
20
|
-
return true if @property_states_defined
|
21
|
-
self.properties.each do |state_method_name|
|
22
|
-
unless self.instance_methods.include?(state_method_name.to_s)
|
23
|
-
define_method(state_method_name) do
|
24
|
-
states[state_method_name]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
state_method_writer = "#{state_method_name}=".to_sym
|
29
|
-
unless self.instance_methods.include?(state_method_writer.to_s)
|
30
|
-
define_method(state_method_writer) do |value|
|
31
|
-
states[state_method_name] = value
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
@property_states_defined = true
|
37
|
-
end
|
38
|
-
|
39
|
-
protected
|
40
|
-
def name_sym(name)
|
41
|
-
name.to_s.downcase.gsub(/\s+/, '_').to_sym
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
|
@@ -1,12 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
|
2
|
-
module Fathom
|
3
|
-
class CausalGraph
|
4
|
-
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
if __FILE__ == $0
|
9
|
-
include Fathom
|
10
|
-
# TODO: Is there anything you want to do to run this file on its own?
|
11
|
-
# CausalGraph.new
|
12
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
This is a first approach to an RDF back end for a broadly-defined data store.
|
3
|
-
|
4
|
-
I am borrowing from the SKOS ontology here to be able to define any sort of concept
|
5
|
-
that may assist me with my decision-making work.
|
6
|
-
|
7
|
-
TODO:
|
8
|
-
|
9
|
-
[x] Build a basic Spira modeal
|
10
|
-
[x] Make a SKOS commitment
|
11
|
-
[.] Create helper methods to find or create the concept easily (using hash syntax for field names)
|
12
|
-
[] Create association methods for associating the concept to other concepts (need to think about this one)
|
13
|
-
[] Create specific methods to define a plausible range (probably define a Spira model here too)
|
14
|
-
[] Create specific methods to define a ValueDescription
|
15
|
-
[] Create specific methods to define a MonteCarloSet
|
16
|
-
[] Create specific methods to define a CausalGraph
|
17
|
-
[] Create specific methods to define a DependencyGraph
|
18
|
-
[] Create specific methods to define the value of further measurement (another un-named class)
|
19
|
-
|
20
|
-
=end
|
21
|
-
|
22
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
|
23
|
-
require 'rdf'
|
24
|
-
require 'rdf/ntriples'
|
25
|
-
require 'data_objects'
|
26
|
-
require 'do_sqlite3'
|
27
|
-
require 'rdf/do'
|
28
|
-
require 'spira'
|
29
|
-
|
30
|
-
|
31
|
-
module Fathom
|
32
|
-
|
33
|
-
# Go ahead and create a generic repo for Fathom
|
34
|
-
def repo
|
35
|
-
@repo ||= RDF::DataObjects::Repository.new('sqlite3:/tmp/test.db')
|
36
|
-
end
|
37
|
-
|
38
|
-
Spira.add_repository(:default, repo)
|
39
|
-
|
40
|
-
class Concept
|
41
|
-
|
42
|
-
include Spira::Resource
|
43
|
-
include RDF
|
44
|
-
|
45
|
-
class << self
|
46
|
-
def find_or_build(name, description=nil)
|
47
|
-
concept = Concept.for(concept_name(name))
|
48
|
-
return concept if concept.exist?
|
49
|
-
concept.name = name
|
50
|
-
concept.description = description
|
51
|
-
concept
|
52
|
-
end
|
53
|
-
|
54
|
-
def find_or_create(name, description=nil)
|
55
|
-
concept = Concept.for(concept_name(name))
|
56
|
-
return concept if concept.exist?
|
57
|
-
concept.name = name
|
58
|
-
concept.description = description
|
59
|
-
concept.save!
|
60
|
-
concept
|
61
|
-
end
|
62
|
-
|
63
|
-
protected
|
64
|
-
def concept_name(name)
|
65
|
-
concept_name = name.downcase.gsub(/\s+/, '_')
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
base_uri "http://example.org/example/concepts"
|
70
|
-
|
71
|
-
property :name, :predicate => SKOS.prefLabel
|
72
|
-
property :description, :predicate => SKOS.definition
|
73
|
-
property :scope, :predicate => SKOS.scopeNote
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
if __FILE__ == $0
|
80
|
-
include Fathom
|
81
|
-
# TODO: Is there anything you want to do to run this file on its own?
|
82
|
-
# Concept.new
|
83
|
-
end
|
@@ -1,119 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'gsl'
|
3
|
-
|
4
|
-
include GSL
|
5
|
-
|
6
|
-
class NodeAccessor
|
7
|
-
|
8
|
-
attr_reader :cpm
|
9
|
-
|
10
|
-
def initialize(cpm)
|
11
|
-
@cpm = cpm
|
12
|
-
end
|
13
|
-
|
14
|
-
def is(*labels)
|
15
|
-
ChildAccessor.new(cpm, *labels)
|
16
|
-
end
|
17
|
-
|
18
|
-
def is_not(*labels)
|
19
|
-
ChildAccessor.new(cpm, *(cpm.child.labels - labels))
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class ChildAccessor
|
24
|
-
|
25
|
-
attr_reader :cpm, :labels
|
26
|
-
def initialize(cpm, *labels)
|
27
|
-
@cpm, @labels = cpm, labels
|
28
|
-
end
|
29
|
-
|
30
|
-
def given(parent_name)
|
31
|
-
ParentAccessor.new(cpm, labels)
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
class ParentAccessor
|
37
|
-
|
38
|
-
attr_reader :cpm, :node, :child_labels, :child_indices
|
39
|
-
def initialize(cpm, child_labels)
|
40
|
-
@cpm = cpm
|
41
|
-
@child_labels = child_labels
|
42
|
-
@node = cpm.parent
|
43
|
-
@child_indices = child_labels.map {|label| @cpm.child.labels.index(label)}
|
44
|
-
end
|
45
|
-
|
46
|
-
def is(*labels)
|
47
|
-
indices = labels.map {|label| get_index(label) }
|
48
|
-
sum_probabilities(indices)
|
49
|
-
end
|
50
|
-
|
51
|
-
def is_not(*labels)
|
52
|
-
not_indices = labels.map {|label| get_index(label) }
|
53
|
-
indices = (0..node.labels.size).to_a - not_indices
|
54
|
-
sum_probabilities(indices)
|
55
|
-
end
|
56
|
-
|
57
|
-
protected
|
58
|
-
|
59
|
-
# TODO: Not right...
|
60
|
-
def sum_probabilities(indices)
|
61
|
-
first_child = child_indices.first
|
62
|
-
cpm.matrix[indices.first, first_child]
|
63
|
-
# indices.inject(0.0) do |s, i|
|
64
|
-
# s += cpm.matrix[i, first_child]
|
65
|
-
# end
|
66
|
-
end
|
67
|
-
|
68
|
-
def get_index(label)
|
69
|
-
node.labels.index(label)
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
class ConditionalProbabilityMatrix
|
76
|
-
|
77
|
-
class << self
|
78
|
-
def define_node_accessor(node, cpm)
|
79
|
-
define_method(node.name.to_sym) do
|
80
|
-
NodeAccessor.new(cpm)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
attr_reader :parent, :child, :matrix
|
86
|
-
|
87
|
-
def initialize(parent, child)
|
88
|
-
@parent, @child = parent, child
|
89
|
-
@matrix = @parent.probabilities.col * @child.probabilities
|
90
|
-
assert_name_access
|
91
|
-
end
|
92
|
-
|
93
|
-
def probability(opts={})
|
94
|
-
child_label = opts[:child]
|
95
|
-
parent_label = opts[:parent]
|
96
|
-
raise ArgumentError, "Must provide a child and parent label. E.g., probability(:child => true, :parent => false)" unless child_label and parent_label
|
97
|
-
child_label_index = get_index(child, child_label)
|
98
|
-
parent_label_index = get_index(parent, parent_label)
|
99
|
-
self.matrix[parent_label_index, child_label_index]
|
100
|
-
end
|
101
|
-
alias :p :probability
|
102
|
-
|
103
|
-
def inspect
|
104
|
-
"ConditionalProbabilityMatrix: #{matrix.to_a.inspect}"
|
105
|
-
end
|
106
|
-
|
107
|
-
protected
|
108
|
-
|
109
|
-
def assert_name_access
|
110
|
-
ConditionalProbabilityMatrix.define_node_accessor(child, self)
|
111
|
-
end
|
112
|
-
|
113
|
-
def get_index(node, label)
|
114
|
-
node.labels.index(label)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
class CPM < ConditionalProbabilityMatrix
|
119
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'fathom'))
|
2
|
-
module Fathom
|
3
|
-
class Invertor < Fathom::Node
|
4
|
-
|
5
|
-
def initialize(opts={})
|
6
|
-
super(opts)
|
7
|
-
@name ||= "Inverter"
|
8
|
-
end
|
9
|
-
|
10
|
-
def value
|
11
|
-
-1
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
if __FILE__ == $0
|
17
|
-
include Fathom
|
18
|
-
# TODO: Is there anything you want to do to run this file on its own?
|
19
|
-
# Invertor.new
|
20
|
-
end
|
data/lib/fathom/archive/n2.rb
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
=begin
|
4
|
-
Some noodling about what a node might contain in order to describe the joint probabilities.
|
5
|
-
=end
|
6
|
-
class Node
|
7
|
-
|
8
|
-
attr_reader :variable, :parents
|
9
|
-
def initialize(variable, *parents)
|
10
|
-
@variable = Variable.infer(variable)
|
11
|
-
raise ArgumentError, "A valid variable cannot be implied from #{variable}" unless @variable
|
12
|
-
@parents = parents
|
13
|
-
end
|
14
|
-
|
15
|
-
def name
|
16
|
-
self.variable.name
|
17
|
-
end
|
18
|
-
|
19
|
-
def inspect
|
20
|
-
"Node: #{self.name} #{ self.parents.map{|p| p.name}.inspect }"
|
21
|
-
end
|
22
|
-
|
23
|
-
class << self
|
24
|
-
def infer(obj, *parents)
|
25
|
-
return obj if obj.is_a?(Node)
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class Variable
|
32
|
-
|
33
|
-
attr_reader :values, :name, :observations, :total
|
34
|
-
|
35
|
-
def initialize(name, *values)
|
36
|
-
values = [true, false] if values.empty?
|
37
|
-
@name = name
|
38
|
-
@values = values
|
39
|
-
@observations = Array.new(@values.size, 0)
|
40
|
-
@total = 0
|
41
|
-
end
|
42
|
-
|
43
|
-
# You can observe anything but nothing: we record any observation but nil.
|
44
|
-
# If nil is set, we use the first value as the default.
|
45
|
-
def observe(value=nil)
|
46
|
-
value = self.values.first if value.nil?
|
47
|
-
unless self.values.include?(value)
|
48
|
-
self.values << value
|
49
|
-
self.observations << 0
|
50
|
-
end
|
51
|
-
index = self.values.index(value)
|
52
|
-
self.observations[index] += 1
|
53
|
-
@total += 1
|
54
|
-
end
|
55
|
-
|
56
|
-
# Lookup observations
|
57
|
-
def observed(value)
|
58
|
-
index = self.values.index(value)
|
59
|
-
return 0 unless index
|
60
|
-
self.observations[index]
|
61
|
-
end
|
62
|
-
|
63
|
-
def inspect
|
64
|
-
"Variable: #{self.name} #{self.values.inspect}"
|
65
|
-
end
|
66
|
-
|
67
|
-
class << self
|
68
|
-
def infer(obj, *values)
|
69
|
-
return obj if obj.is_a?(Variable)
|
70
|
-
case obj
|
71
|
-
when Symbol
|
72
|
-
Variable.new(obj, *values)
|
73
|
-
when String
|
74
|
-
Variable.new(obj.to_sym, *values)
|
75
|
-
else
|
76
|
-
nil
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
require 'rubygems'
|
84
|
-
require 'spec'
|
85
|
-
|
86
|
-
describe Variable do
|
87
|
-
|
88
|
-
before do
|
89
|
-
@v = Variable.new(:v1)
|
90
|
-
end
|
91
|
-
|
92
|
-
it "should require a name" do
|
93
|
-
lambda{Variable.new}.should raise_error(ArgumentError)
|
94
|
-
lambda{@v = Variable.new(:name)}.should_not raise_error
|
95
|
-
@v.name.should eql(:name)
|
96
|
-
end
|
97
|
-
|
98
|
-
it "should default to true and false as parameter values" do
|
99
|
-
v = Variable.new(:v)
|
100
|
-
v.values.should eql([true, false])
|
101
|
-
end
|
102
|
-
|
103
|
-
it "should be able to take a variables parameters" do
|
104
|
-
v = Variable.new :v, :red, :blue, :green
|
105
|
-
v.values.should eql([:red, :blue, :green])
|
106
|
-
end
|
107
|
-
|
108
|
-
it "should be able to infer a variable from a variable" do
|
109
|
-
v = Variable.new(:v)
|
110
|
-
Variable.infer(v).should eql(v)
|
111
|
-
end
|
112
|
-
|
113
|
-
it "should be able to infer a variable from a symbol" do
|
114
|
-
v = Variable.infer(:v)
|
115
|
-
v.should be_a(Variable)
|
116
|
-
v.name.should eql(:v)
|
117
|
-
end
|
118
|
-
|
119
|
-
it "should be able to infer a variable from a string" do
|
120
|
-
v = Variable.infer('v')
|
121
|
-
v.should be_a(Variable)
|
122
|
-
v.name.should eql(:v)
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should be able to infer values from a list" do
|
126
|
-
v = Variable.infer :v, 1, 2
|
127
|
-
v.values.should eql([1,2])
|
128
|
-
end
|
129
|
-
|
130
|
-
it "should start with zero observations" do
|
131
|
-
@v.total.should eql(0)
|
132
|
-
end
|
133
|
-
|
134
|
-
it "should increment observations" do
|
135
|
-
@v.observe
|
136
|
-
@v.total.should eql(1)
|
137
|
-
@v.observe
|
138
|
-
@v.total.should eql(2)
|
139
|
-
end
|
140
|
-
|
141
|
-
it "should record observations" do
|
142
|
-
@v.observe(true)
|
143
|
-
@v.total.should eql(1)
|
144
|
-
@v.observed(true).should eql(1)
|
145
|
-
@v.observed(false).should eql(0)
|
146
|
-
@v.observe(false)
|
147
|
-
@v.total.should eql(2)
|
148
|
-
@v.observed(true).should eql(1)
|
149
|
-
@v.observed(false).should eql(1)
|
150
|
-
end
|
151
|
-
|
152
|
-
end
|
153
|
-
|
154
|
-
describe Node do
|
155
|
-
|
156
|
-
before do
|
157
|
-
@season = Variable.new(:season, :spring, :summer, :fall, :winter)
|
158
|
-
@x1 = Node.new(@season)
|
159
|
-
@x2 = Node.new(:rain, @x1)
|
160
|
-
@x3 = Node.new(:sprinkler, @x1)
|
161
|
-
@x4 = Node.new(:wet, @x3, @x2)
|
162
|
-
@x5 = Node.new(:slippery, @x4)
|
163
|
-
end
|
164
|
-
|
165
|
-
it "should infer a variable for the node" do
|
166
|
-
v = Variable.new(:v)
|
167
|
-
n = Node.new(v)
|
168
|
-
n.variable.should eql(v)
|
169
|
-
|
170
|
-
n = Node.new(:v)
|
171
|
-
v = n.variable
|
172
|
-
v.should be_a(Variable)
|
173
|
-
v.name.should eql(:v)
|
174
|
-
end
|
175
|
-
|
176
|
-
it "should raise an error when it cannot infer a variable for the node" do
|
177
|
-
lambda{Node.new(1)}.should raise_error(ArgumentError, /A valid variable cannot be implied from/)
|
178
|
-
end
|
179
|
-
|
180
|
-
it "should be able to create a node with parents" do
|
181
|
-
@x1.parents.should be_empty
|
182
|
-
@x2.parents.should eql([@x1])
|
183
|
-
@x3.parents.should eql([@x1])
|
184
|
-
@x4.parents.should eql([@x3, @x2])
|
185
|
-
@x5.parents.should eql([@x4])
|
186
|
-
end
|
187
|
-
|
188
|
-
# it "should be able to infer a node" do
|
189
|
-
# n = Node.infer(:v1, :v2)
|
190
|
-
# n.name.should eql(:v1)
|
191
|
-
# n.variable.name.should eql(:v1)
|
192
|
-
# n.variable.should be_a(Variable)
|
193
|
-
# n.parents.size.should eql(1)
|
194
|
-
# p = n.parents.first
|
195
|
-
# p.name.should eql(:v2)
|
196
|
-
# p.should be_a(Variable)
|
197
|
-
# end
|
198
|
-
end
|