bn4r 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.
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ *0.1*
2
+
3
+ First release
data/README ADDED
@@ -0,0 +1,62 @@
1
+
2
+ = Bayesian Networks for Ruby ( bn4r )
3
+
4
+ bn4r is a bayesian networks library on ruby that provides
5
+ the user with classes for create bayesian networks and
6
+ diverse algorithms for solve them.
7
+
8
+ Its algorithms implementation are based on: S.Russell, P.Norving, "Artificial
9
+ Intelligence, A Modern Approach", 2nd Edition.
10
+
11
+ Website:
12
+ http://bn4r.rubyforge.org
13
+
14
+ Rubyforge Project:
15
+ http://rubyforge.org/projects/bn4r
16
+
17
+ = Dependencies
18
+
19
+ * rgl-0.2.3 ( Ruby Graph Library ), http://rgl.rubyforge.org
20
+
21
+ = Design principles
22
+
23
+ The library consists on the object BayesNet thinked to be filled with
24
+ BayesNetNode, these objects are defined in bn.rb. BayesNet object is a
25
+ especialization of RGL::DirectedAdjacencyGraph ( http://rgl.rubyforge.org ).
26
+
27
+
28
+ The file bn_algorithms.rb has the implementation of the inference algorithms
29
+ that can be used to solve BayesNet structures.
30
+
31
+ Finally, a set of objects and methods are given to automaticly fill BayesNetNode
32
+ probabilities tables.
33
+
34
+
35
+ = Documentation
36
+
37
+ Documentation can be found at
38
+ http://bn4r.rubyforge.org/rdoc
39
+ or can be generated using rdoc tool under the source code with:
40
+ rdoc README lib
41
+
42
+ = Credits
43
+
44
+ Thanks to N�ria Bel ( http://www.upf.edu/pdi/iula/nuria.bel ) for her work in this project
45
+ without her it cannot be done.
46
+
47
+ Thanks to Ryan Dahl for his work in http://www.math.rochester.edu/people/grads/rld/bayesnets
48
+ that was the inspiration of the project.
49
+
50
+ Also thanks to all the ruby community.
51
+
52
+ == Copying
53
+
54
+ This work is developed by Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
55
+ mainly in Institut Universitari de Ling�istica Aplicada of Universitat Pompeu Fabra ( http://www.iula.upf.es ),
56
+ and also in bee.com.es ( http://bee.com.es ).
57
+
58
+ It is free software, and may be redistributed under GPL license.
59
+
60
+ == Support
61
+
62
+ Please contact me in http://rubyforge.org/projects/bn4r.
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ include FileUtils
11
+ require File.join(File.dirname(__FILE__), 'lib', 'bn4r', 'version')
12
+
13
+ AUTHOR = "Sergio Espeja"
14
+ EMAIL = "sergio.espeja@gmail.com"
15
+ DESCRIPTION = "bn4r is a bayesian networks library on ruby that provides the user with classes for create bayesian networks and diverse algorithms for solve them."
16
+ RUBYFORGE_PROJECT = "bn4r"
17
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
+ BIN_FILES = %w( )
19
+
20
+
21
+ NAME = "bn4r"
22
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
23
+ VERS = ENV['VERSION'] || (Bn4r::VERSION::STRING + (REV ? ".#{REV}" : ""))
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = ['--quiet', '--title', "bn4r documentation",
26
+ "--opname", "index.html",
27
+ "--line-numbers",
28
+ "--main", "README",
29
+ "--inline-source"]
30
+
31
+ desc "Packages up bn4r gem."
32
+ task :default => [:test]
33
+ task :package => [:clean]
34
+
35
+ Rake::TestTask.new("test") { |t|
36
+ t.libs << "test"
37
+ t.pattern = "test/**/*_test.rb"
38
+ t.verbose = true
39
+ }
40
+
41
+ spec =
42
+ Gem::Specification.new do |s|
43
+ s.name = NAME
44
+ s.version = VERS
45
+ s.platform = Gem::Platform::RUBY
46
+ s.has_rdoc = true
47
+ s.extra_rdoc_files = ["README", "CHANGELOG"]
48
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
49
+ s.summary = DESCRIPTION
50
+ s.description = DESCRIPTION
51
+ s.author = AUTHOR
52
+ s.email = EMAIL
53
+ s.homepage = HOMEPATH
54
+ s.executables = BIN_FILES
55
+ s.rubyforge_project = RUBYFORGE_PROJECT
56
+ s.bindir = "bin"
57
+ s.require_path = "lib"
58
+ s.autorequire = "bn4r"
59
+
60
+ s.add_dependency('rgl', '>=0.2.3')
61
+ #s.required_ruby_version = '>= 1.8.2'
62
+
63
+ s.files = %w(README CHANGELOG Rakefile) +
64
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
65
+ Dir.glob("ext/**/*.{h,c,rb}") +
66
+ Dir.glob("examples/**/*.rb") +
67
+ Dir.glob("tools/*.rb")
68
+
69
+ # s.extensions = FileList["ext/**/extconf.rb"].to_a
70
+ end
71
+
72
+ Rake::GemPackageTask.new(spec) do |p|
73
+ p.need_tar = true
74
+ p.gem_spec = spec
75
+ end
76
+
77
+ task :install do
78
+ name = "#{NAME}-#{VERS}.gem"
79
+ sh %{rake package}
80
+ sh %{sudo gem install pkg/#{name}}
81
+ end
82
+
83
+ task :uninstall => [:clean] do
84
+ sh %{sudo gem uninstall #{NAME}}
85
+ end
data/lib/bn4r/bn.rb ADDED
@@ -0,0 +1,249 @@
1
+ ##############################################################
2
+ #
3
+ # Bayesian Network Library for Ruby
4
+ #
5
+ # Author: Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es ) and
8
+ # in bee.com.es ( http://bee.com.es )
9
+ #
10
+ # Based on work by Ryan Dahl in
11
+ # http://www.math.rochester.edu/people/grads/rld/bayesnets
12
+ #
13
+ ##############################################################
14
+ require 'rgl/adjacency'
15
+ require 'rgl/dot'
16
+
17
+ include RGL
18
+
19
+ class BayesNet < DirectedAdjacencyGraph
20
+
21
+ # DirectedAdjacencyGraph redefined methods
22
+ # ----------------------------------------
23
+
24
+ # Adds a directed edge between parent and child BayesNetNodes labeled
25
+ # with tag ( if tag is included, othewise the label is nil ).
26
+ def add_edge(parent, child, tag=nil)
27
+ raise "Nodes must be of the class BayesNetNodes" if parent.class != BayesNetNode or child.class != BayesNetNode
28
+ edge = super(parent, child)
29
+ child.parents << parent
30
+ child.relations << tag if !tag.nil?
31
+ edge
32
+ end
33
+
34
+ # BN Methods
35
+ # ----------
36
+
37
+ # Clears the value of all BayesNetNodes in Bayes Net.
38
+ def clear_values!
39
+ vertices.each { |v| v.clear_value }
40
+ end
41
+
42
+ # Gets the variable with given name.
43
+ def get_variable( text )
44
+ vertices.each { |v| return v if v.name == text }
45
+ end
46
+
47
+ # Returns the root nodes of the Bayes Net.
48
+ def roots
49
+ vertices.select { |v| root?(v) }
50
+ end
51
+
52
+ # Returns true/false if given Node is root.
53
+ def root?(v)
54
+ return true if num_parents(v) == 0
55
+ false
56
+ end
57
+
58
+ # Returns the number of parents of a node.
59
+ def num_parents(v)
60
+ return v.parents.size
61
+ end
62
+
63
+ # Return the probability of a distribution in the bayes net
64
+ # all nodes in the Bayes Net must have a value, otherwise
65
+ # will raise a exception
66
+ def inference_by_enumeration
67
+ prob = 1.0;
68
+ vertices.each {|v| prob = prob * p_v_cond_parents(v)}
69
+ prob
70
+ end
71
+
72
+ # Returns the probability of a node conditioned to his parents:
73
+ # P(v|parents(v))
74
+ def p_v_cond_parents(v)
75
+ givens_assignments = v.parents.collect {|parents| parents.value}
76
+ v.get_probability(v.value, givens_assignments).to_f
77
+ end
78
+
79
+ # Returns true if all nodes have values.
80
+ def all_nodes_with_values?
81
+ vertices.select {|v| !v.value.nil? }.size == vertices.size
82
+ end
83
+
84
+ # Returns nodes ordered by dependencies ( from those who haven't ( roots )
85
+ # to leaves ).
86
+ def nodes_ordered_by_dependencies(nodes = vertices, bn_ordered = Array.new)
87
+ nodes.each { |v|
88
+ next if bn_ordered.include?(v)
89
+ nodes_ordered_by_dependencies(v.parents, bn_ordered) if !v.root?
90
+ bn_ordered << v
91
+ }
92
+ return bn_ordered.flatten
93
+ end
94
+
95
+
96
+ end
97
+
98
+ class BayesNetNode
99
+ attr_reader :name, :outcomes, :extra_info, :value, :relations
100
+
101
+ # create de BayesNetNode
102
+ # name is the identifier of the node in the Bayesian Network
103
+ # outcomes is an array with the posible values that this node
104
+ # can have, by default [true, false].
105
+ # [optional] extra_info extra information that can be putted in the node
106
+ def initialize ( name , outcomes = [true, false], extra_info = nil)
107
+ @name = name
108
+ @outcomes = outcomes
109
+ @extra_info = extra_info
110
+ @givens = []
111
+ @relations = []
112
+ end
113
+
114
+ # Returns a copy of the node itself
115
+ def copy
116
+ tmp = BayesNetNode.new(@name, @outcomes, @extra_info)
117
+ tmp.set_value(@value)
118
+ tmp.set_probability_table(@givens, @table)
119
+ tmp
120
+ end
121
+
122
+ def set_value(value)
123
+ @value = value
124
+ end
125
+
126
+ def clear_value
127
+ @value = nil
128
+ end
129
+
130
+ # Return node parents
131
+ def parents
132
+ return @givens
133
+ end
134
+
135
+ # Return node relations
136
+ def relations
137
+ return @relations
138
+ end
139
+
140
+ # Return the number of parents
141
+ def num_parents
142
+ parents.size
143
+ end
144
+
145
+ # Returns true if the node is a root node ( doesn't have parents ).
146
+ def root?
147
+ return true if num_parents == 0
148
+ false
149
+ end
150
+
151
+ # Returns true if all parents of the node in the bn have values
152
+ def all_parents_with_values?
153
+ parents.select {|v| !v.value.nil? }.size == parents.size
154
+ end
155
+
156
+ def to_s
157
+ return name + (value.nil? ? "" : (" = " + value.to_s))
158
+ end
159
+
160
+ # if givens is nil, then internal givens is assumed
161
+ # table must have probability values in order like BAD!!!
162
+ # [g0=pos0 & g1=pos0 & ... & node_value=pos0, ..., g0=pos0 & g1=pos0 & ... & node_value=posN,
163
+ #
164
+ def set_probability_table (givens, table)
165
+ # perhaps we should do some error checking on the table entries here?
166
+ @table_is_a_proc = (table.class != Array)
167
+ @givens = givens if !givens.nil?
168
+
169
+ raise "Error table incorrect number of positions (" \
170
+ + table.size.to_s + " of " + self.get_table_size.to_s \
171
+ + ")" if table.size != self.get_table_size
172
+
173
+ @table = table
174
+ end
175
+
176
+ def parents_assignments
177
+ parents.collect { |p| p.value }
178
+ end
179
+
180
+ # returns the number of cells that conditional probability table ( CPT )
181
+ # haves.
182
+ def get_table_size
183
+ num = @outcomes.size
184
+ @givens.each { |given| num = num * given.outcomes.size }
185
+ return num
186
+ end
187
+
188
+ # Sets a probability to the node with a node_assignment conditioned to given_assignments
189
+ # P (node = node_assignment | givens = givens_assignments) = number
190
+ def set_probability(number, node_assignment, givens_assignments)
191
+ @table[get_table_index(node_assignment, givens_assignments)] = number
192
+ end
193
+
194
+ # Returns the probability of an assignment to a node conditioned to given_assignments
195
+ # P(node = node_assignment | givens = givens_assignments)
196
+ #
197
+ # All givens_assigments must have value.
198
+ def get_probability(node_assignment = value, givens_assignments = parents_assignments)
199
+ # raise "Node must have a value and a givens_assignments, otherwise put" \
200
+ # + "them in function call" if node_assignment.nil? or givens_assignments.nil?
201
+ # if there's a cached table take the index
202
+ return @table[get_table_index(node_assignment, givens_assignments)] if @table_is_a_proc.nil? or !@table_is_a_proc
203
+ # if the value is calculated on the fly using a function instead of
204
+ # a table
205
+ return @table[node_assignment, givens_assignments]
206
+ end
207
+
208
+ # Returns the corresponding index for the probability table given
209
+ # <i>node_assignment</i> and <i>givens_assignments</i>
210
+ def get_table_index(node_assignment, givens_assignments)
211
+ x = []
212
+ indices = []
213
+ index = 0
214
+
215
+ if givens_assignments.length != @givens.length
216
+ raise "Error. Number of assignments does not match node."
217
+ end
218
+
219
+ if @givens.length > 0
220
+ # create a indices array with the position of each value of
221
+ # given assignments.
222
+ givens_assignments.length.times { |i|
223
+ assignment = givens_assignments[i]
224
+ indices[i] = @givens[i].outcomes.index(assignment)
225
+ }
226
+
227
+ # create a array with the number of possible values each
228
+ # given node and this node itself can have ( node.outcomes.length )
229
+ # plus all next nodes.
230
+ x[givens_assignments.length-1] = @outcomes.length
231
+ (givens_assignments.length-2).downto(0) { |j|
232
+ x[j] = x[j+1] * @givens[j+1].outcomes.length
233
+ }
234
+
235
+ # to get the index, sum for each assignment the
236
+ # product of each given assignment outcomes size
237
+ # by its value index.
238
+ givens_assignments.length.times { |i|
239
+ index += x[i] * indices[i]
240
+ }
241
+ end
242
+
243
+ index += @outcomes.index(node_assignment)
244
+
245
+ return index
246
+ end
247
+
248
+
249
+ end
@@ -0,0 +1,215 @@
1
+ ##############################################################
2
+ #
3
+ # Inference Algorithms for Bayesian Network Library for Ruby
4
+ #
5
+ # Author: Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es ) and
8
+ # in bee.com.es ( http://bee.com.es )
9
+ #
10
+ # == Current implemented algorithms
11
+ # * enumeration_ask
12
+ # * prior_sample
13
+ # * rejection_sampling
14
+ # * likelihood_weighting
15
+ #
16
+ ##############################################################
17
+ class BayesNet < DirectedAdjacencyGraph
18
+
19
+ # Inference Algorithms
20
+
21
+ # ENUMERATION ASK algorithm
22
+ #
23
+ # Implementation based on: S.Russell, P.Norving, "Artificial
24
+ # Intelligence, A Modern Approach", 2nd Edition. pp 506
25
+ #
26
+ # <b>x</b> --> query variable
27
+ #
28
+ # <b>e</b> --> variables with observed values
29
+ def enumeration_ask(x,e, bn_vertices = vertices)
30
+ e << x
31
+ q = []
32
+ #p bn_vertices.collect { |v| v.name }
33
+ x.outcomes.each {|outcome|
34
+ x.set_value(outcome)
35
+ q << enumerate_all(bn_vertices, e)
36
+ }
37
+ q
38
+ end
39
+
40
+ # Returns a sample from prior joint distribution specified by the network.
41
+ #
42
+ # Implementation based on: S.Russell, P.Norving, "Artificial
43
+ # Intelligence, A Modern Approach", 2nd Edition. pp 511-512
44
+ #
45
+ # The input are the nodes of the bn ordered by dependencies see nodes_ordered_by_dependencies
46
+ def prior_sample(nodes_ordered = nodes_ordered_by_dependencies)
47
+ sample = Array.new
48
+ nodes_ordered.each { |v|
49
+ value = rand < v.get_probability(true)
50
+ v.set_value(value)
51
+ sample << v.copy
52
+ }
53
+ # leave the bn clear of values.
54
+ nodes_ordered.each { |v| v.clear_value }
55
+
56
+ return sample
57
+ end
58
+
59
+ # Returns an estimation of P(X=x|e) = <P(X=x|e), 1 - P(X=x|e)> obtained. Generates samples from prior joint
60
+ # distribution specified by the network, rejects all those that do not match the evidence,
61
+ # and finally counts hoy often X = x occurs in remaining samples.
62
+ #
63
+ # Caution, this algorthm is unusable for complex problems because rejects many samples!
64
+ #
65
+ # Implementation based on: S.Russell, P.Norving, "Artificial
66
+ # Intelligence, A Modern Approach", 2nd Edition. pp 513
67
+ #
68
+ # <b>x</b> --> query variable
69
+ #
70
+ # <b>e</b> --> variables with observed values
71
+ #
72
+ # <b>n</b> --> Number of samples generated
73
+ #
74
+ def rejection_sampling( x, e, n, bn = self )
75
+
76
+ evidece_list = [e] if e.class != Array
77
+ x_list = [x] if x.class != Array
78
+
79
+ nodes_ordered = bn.nodes_ordered_by_dependencies
80
+ evidence_vector = get_vector_value(evidece_list, nodes_ordered)
81
+ x_vector = get_vector_value(x_list, nodes_ordered)
82
+
83
+ total_valid = 0; total_correct = 0
84
+ n.times do
85
+ sample_vector = bn.prior_sample(nodes_ordered).collect {|v| v.value}
86
+
87
+ valid = true; correct = true
88
+ for i in 0..(sample_vector.size-1) do
89
+ correct = false if !x_vector[i].nil? and sample_vector[i] != x_vector[i]
90
+ valid = false and break if !evidence_vector[i].nil? and sample_vector[i] != evidence_vector[i]
91
+ end
92
+
93
+ next if !valid
94
+ total_valid += 1
95
+ total_correct += 1 if correct
96
+ end
97
+
98
+ p_true = total_correct.to_f/total_valid.to_f
99
+ return [p_true, 1-p_true]
100
+ #return [total_correct.to_f, total_valid.to_f]
101
+ end
102
+
103
+ # Returns an estimation of P(X=x|e) = <P(X=x|e), 1 - P(X=x|e)> obtained.
104
+ #
105
+ # Implementation based on: S.Russell, P.Norving, "Artificial
106
+ # Intelligence, A Modern Approach", 2nd Edition. pp 515
107
+ #
108
+ # <b>x</b> --> query variable
109
+ #
110
+ # <b>e</b> --> variables with observed values
111
+ #
112
+ # <b>n</b> --> Number of samples generated
113
+ #
114
+ def likelihood_weighting( x, e, n, bn = self )
115
+
116
+ retval = [0.0, 0.0]
117
+ n.times {
118
+ w_sample, w = weighted_sample(e)
119
+ value = w_sample.select { |v| v.name == x.name }[0].value
120
+ #p value
121
+ if value == x.value
122
+ retval[1] += w
123
+ else
124
+ retval[0] += w
125
+ end
126
+ }
127
+
128
+ norm = retval[1].to_f / (retval[0]+retval[1]).to_f
129
+
130
+ return [norm, 1-norm]
131
+ end
132
+
133
+ protected
134
+ # Auxiliar function to compute Enumeration Ask Algorithm
135
+ def enumerate_all(vars, e)
136
+
137
+ return 1.0 if vars.empty?
138
+
139
+ y = vars.first; i = 1
140
+ while !y.all_parents_with_values? and i < vars.size
141
+ y = vars[i]
142
+ i = i + 1
143
+ end
144
+ raise "Error bayes net not computable with enumeration-ask " + \
145
+ "algorithm" if i == vars.size and !y.all_parents_with_values?
146
+
147
+ if e.include?(y)
148
+ return p_v_cond_parents(y) * enumerate_all(vars-[y], e)
149
+ else
150
+ prob = 0.0
151
+ y.outcomes.each { |outcome|
152
+ y.set_value(outcome)
153
+ prob = prob + p_v_cond_parents(y) * enumerate_all(vars-[y], e+[y])
154
+ y.clear_value
155
+ }
156
+ return prob
157
+ end
158
+ end
159
+
160
+ # Returns an event and a weight.
161
+ #
162
+ # Implementation based on: S.Russell, P.Norving, "Artificial
163
+ # Intelligence, A Modern Approach", 2nd Edition. pp 515
164
+ #
165
+ # <b>e</b> --> variables with observed values
166
+ #
167
+ def weighted_sample(e, bn = self)
168
+
169
+ nodes_ordered = bn.nodes_ordered_by_dependencies
170
+
171
+ sample = Array.new
172
+ w = 1.0
173
+ nodes_ordered.each { |v|
174
+ node_actual = e.select { |node| node.name == v.name } if e.class == Array
175
+ node_actual = [e] if e.class == BayesNetNode and e.name == v.name
176
+ if !node_actual.nil? and node_actual.size == 1
177
+ value = node_actual[0].value
178
+ w = w * v.get_probability(value)
179
+ else
180
+ value = rand < v.get_probability(true)
181
+ end
182
+ v.set_value(value)
183
+ sample << v.copy
184
+ }
185
+
186
+ # leave the bn clear of values.
187
+ bn.clear_values!
188
+ return sample, w
189
+ end
190
+
191
+ # Axiliar function that returns an array of bn_vertices_ordered.size positions
192
+ # with nil if position aren't in vertices_vector, and value if there's a match
193
+ # in vertices_vector.
194
+ def get_vector_value(vertices_vector, bn_vertices_ordered)
195
+ bn_vertices_ordered.collect { |v|
196
+ if !vertices_vector.nil?
197
+ node_actual = vertices_vector.select { |node| node.name == v.name }
198
+
199
+ case node_actual.size
200
+ when 0
201
+ nil
202
+ when 1
203
+ node_actual[0].value
204
+ else
205
+ raise "Error in get_vector_value"
206
+ end
207
+ else
208
+ nil
209
+ end
210
+ }
211
+ end
212
+
213
+
214
+
215
+ end
@@ -0,0 +1,65 @@
1
+ ##############################################################
2
+ #
3
+ # Methods for poulating Bayes Net Probabilities tables.
4
+ #
5
+ # Author: Sergio Espeja ( http://www.upf.edu/pdi/iula/sergio.espeja, sergio.espeja at gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es )
8
+ #
9
+ ##############################################################
10
+
11
+
12
+ # Bayes Net Table Probabilities Generator
13
+ class BnTableProbabilitiesGenerator
14
+ def get_node_probability_from_boolean_combination(boolean_combination)
15
+ 0.0
16
+ end
17
+ end
18
+
19
+ # Bayes Net Table Probabilities Generator from Positive Negative Relations
20
+ class BNTPGFromPositiveNegativeRelations < BnTableProbabilitiesGenerator
21
+
22
+ public
23
+ # type_of_position_impact is a array of boolean values showing the
24
+ # relation ( positive | negative ) beetwen the node and its parents.
25
+ def table_probabilities_for_node(node, type_of_position_impact)
26
+ raise "Node parents and type_of_position_impact with different size" if node.parents.size != type_of_position_impact.size
27
+ boolean_combinations = []
28
+ (2**node.parents.size).times { |i|
29
+ boolean_combination = Array.new(node.parents.size, false)
30
+ actual_value = i
31
+ (node.parents.size).times { |j|
32
+ boolean_combination[j] = !(actual_value%2 == 0)
33
+ actual_value = actual_value / 2
34
+ }
35
+ boolean_combinations << boolean_combination
36
+ }
37
+ #p boolean_combinations
38
+ table_probabilities = [] # Array.new(2**(node.parents.size+1))
39
+ boolean_combinations.each { |boolean_combination|
40
+ [true,false].each { |node_value|
41
+ prob = BNTPGFromPositiveNegativeRelations.get_node_probability_from_boolean_combination(boolean_combination, type_of_position_impact)
42
+ prob = 1 - prob if node_value == false
43
+ table_probabilities[node.get_table_index(node_value, boolean_combination)] = prob
44
+ #p "pos :" + node.get_table_index(node_value, boolean_combination).to_s
45
+ #p "Ok :" + node.get_table_index(node_value, boolean_combination).to_s if node.get_table_index(node_value, boolean_combination) > 2**node.parents.size
46
+ }
47
+ }
48
+ #p table_probabilities
49
+ table_probabilities
50
+ end
51
+
52
+ # returns P(node=yes| parents={boolean_combination}) where parents have
53
+ # relations with the node showed in type_of_position_impact
54
+ # type_of_position_impact is a array of boolean values showing the
55
+ # relation ( positive | negative ) beetwen the node and its parents.
56
+ def self.get_node_probability_from_boolean_combination(boolean_combination, type_of_position_impact)
57
+ num_eq = 0.0
58
+ boolean_combination.size.times { |i|
59
+ num_eq = num_eq + 1.0 if type_of_position_impact[i] && boolean_combination[i]
60
+ num_eq = num_eq + 1.0 if !type_of_position_impact[i] && !boolean_combination[i]
61
+ }
62
+ num_eq / boolean_combination.size.to_f
63
+ end
64
+ end
65
+
@@ -0,0 +1,9 @@
1
+ module Bn4r #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/bn4r.rb ADDED
@@ -0,0 +1,11 @@
1
+ ##############################################################
2
+ #
3
+ # Bayesian Network Library for Ruby
4
+ #
5
+ # Author: Sergio Espeja ( sergio.espeja@gmail.com )
6
+ #
7
+ # Developed in: IULA ( http://www.iula.upf.es ) and
8
+ # in bee.com.es ( http://bee.com.es )
9
+ #
10
+ ##############################################################
11
+ Dir[File.join(File.dirname(__FILE__), 'bn4r/**/*.rb')].sort.each { |lib| require lib }
data/test/bn4r_test.rb ADDED
@@ -0,0 +1,290 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class Bn4rTest < Test::Unit::TestCase
4
+
5
+ # Returns the BayesNet used as example in "Artificial Intelligence A
6
+ # Modern Approach, Rusell & Norvig, 2nd Ed." pp.494
7
+ def self.bayes_net_aima
8
+ bn_aima = BayesNet.new
9
+
10
+ b = BayesNetNode.new("Burglary")
11
+ e = BayesNetNode.new("Earthquake")
12
+ a = BayesNetNode.new("Alarm")
13
+ j = BayesNetNode.new("JohnCalls")
14
+ m = BayesNetNode.new("MaryCalls")
15
+
16
+ bn_aima.add_vertex(b)
17
+ bn_aima.add_vertex(e)
18
+ bn_aima.add_vertex(a)
19
+ bn_aima.add_vertex(j)
20
+ bn_aima.add_vertex(m)
21
+
22
+ bn_aima.add_edge(b,a)
23
+ bn_aima.add_edge(e,a)
24
+ bn_aima.add_edge(a,j)
25
+ bn_aima.add_edge(a,m)
26
+
27
+ b.set_probability_table([], [0.001, 0.999] )
28
+ e.set_probability_table([], [0.002, 0.998] )
29
+
30
+ a.set_probability_table([b,e], [0.95, 0.05, 0.94, 0.06, 0.29, 0.61, 0.001,0.999] )
31
+
32
+ j.set_probability_table([a], [0.90,0.10,0.05,0.95])
33
+ m.set_probability_table([a], [0.70,0.30,0.01,0.99])
34
+
35
+ bn_aima
36
+ end
37
+
38
+ # Returns the BayesNet used as example in "Artificial Intelligence A
39
+ # Modern Approach, Rusell & Norvig, 2nd Ed." pp.510
40
+ def self.bayes_net_aima2
41
+ bn_aima = BayesNet.new
42
+
43
+ cloudy = BayesNetNode.new("Cloudy")
44
+ sprinkler = BayesNetNode.new("Sprinkler")
45
+ rain = BayesNetNode.new("Rain")
46
+ wetgrass = BayesNetNode.new("WetGrass")
47
+
48
+ bn_aima.add_vertex(cloudy)
49
+ bn_aima.add_vertex(sprinkler)
50
+ bn_aima.add_vertex(rain)
51
+ bn_aima.add_vertex(wetgrass)
52
+
53
+ bn_aima.add_edge(cloudy,sprinkler)
54
+ bn_aima.add_edge(cloudy,rain)
55
+ bn_aima.add_edge(sprinkler,wetgrass)
56
+ bn_aima.add_edge(rain,wetgrass)
57
+
58
+ cloudy.set_probability_table([], [0.5, 0.5] )
59
+
60
+ sprinkler.set_probability_table([cloudy], [0.1, 0.9, 0.5, 0.5] )
61
+ rain.set_probability_table([cloudy], [0.8, 0.2, 0.2, 0.8] )
62
+
63
+ wetgrass.set_probability_table([sprinkler, rain], [0.99, 0.01, 0.9, 0.1, 0.9, 0.1, 0.0, 1.0] )
64
+ bn_aima
65
+ end
66
+
67
+ def bayes_net_aaile
68
+ bn = BayesNet.new
69
+ rel = BayesNetNode.new("relational")
70
+ q = BayesNetNode.new("qualificative")
71
+ a = BayesNetNode.new("Adverbial")
72
+ bn.add_vertex(rel)
73
+ bn.add_vertex(q)
74
+ bn.add_vertex(a)
75
+
76
+ # ["preN", ...]
77
+ preN = BayesNetNode.new("preN")
78
+ bn.add_vertex(preN)
79
+
80
+ postN = BayesNetNode.new("postN")
81
+ bn.add_vertex(postN)
82
+
83
+ # bn.add_edge("ser")
84
+ # bn.add_edge("G")
85
+ # bn.add_edge("prep")
86
+
87
+ bn.add_edge(rel, preN)
88
+ bn.add_edge(q, preN)
89
+ bn.add_edge(a, preN)
90
+ bn.add_edge(q, postN)
91
+
92
+ bn
93
+ end
94
+
95
+ def setup
96
+ end
97
+
98
+ # TESTS
99
+
100
+ def test_create_sample_bn
101
+ bn = bayes_net_aaile
102
+
103
+ assert_equal bn.vertices.size, 5
104
+ assert_equal bn.edges.size, 4
105
+ end
106
+
107
+
108
+ def test_graph_viz
109
+ bn = bayes_net_aaile
110
+ # print "\n\n" + bn.to_dot_graph.to_s
111
+
112
+ assert bn.to_dot_graph.to_s.size > 0, "bn.to_dot_graph gives no output."
113
+ end
114
+
115
+ def test_probability_assingment
116
+ bn_aima = Bn4rTest.bayes_net_aima
117
+ b = bn_aima.get_variable("Burglary")
118
+ a = bn_aima.get_variable("Alarm")
119
+ j = bn_aima.get_variable("JohnCalls")
120
+ m = bn_aima.get_variable("MaryCalls")
121
+
122
+ assert_equal b.get_probability(true, []), 0.001
123
+ assert_equal b.get_probability(false, []), 0.999
124
+ assert_equal b.get_probability(false, []), 0.999
125
+
126
+ assert_equal a.get_probability(true, [false,false]), 0.001
127
+ assert_equal a.get_probability(true, [true,false]), 0.94
128
+ assert_equal a.get_probability(false, [true,true]), 0.05
129
+
130
+ assert_equal j.get_probability(true, [true]), 0.90
131
+ assert_equal m.get_probability(true, [false]), 0.01
132
+
133
+ end
134
+
135
+ def test_CPT_size
136
+ bn_aima = Bn4rTest.bayes_net_aima
137
+ b = bn_aima.get_variable("Burglary")
138
+ a = bn_aima.get_variable("Alarm")
139
+ j = bn_aima.get_variable("JohnCalls")
140
+ m = bn_aima.get_variable("MaryCalls")
141
+
142
+ assert_equal b.get_table_size, 2
143
+ assert_equal a.get_table_size, 8
144
+ assert_equal j.get_table_size, 4
145
+ assert_equal m.get_table_size, 4
146
+ end
147
+
148
+ def test_base_methods
149
+ bn_aima = Bn4rTest.bayes_net_aima
150
+ b = bn_aima.get_variable("Burglary")
151
+ a = bn_aima.get_variable("Alarm")
152
+ assert bn_aima.root?(b)
153
+ assert !bn_aima.root?(a)
154
+
155
+ end
156
+
157
+ def test_inference_by_enumeration
158
+ bn_aima = Bn4rTest.bayes_net_aima
159
+ b = bn_aima.get_variable("Burglary")
160
+ e = bn_aima.get_variable("Earthquake")
161
+ a = bn_aima.get_variable("Alarm")
162
+ j = bn_aima.get_variable("JohnCalls")
163
+ m = bn_aima.get_variable("MaryCalls")
164
+
165
+ assert_equal bn_aima.vertices.size, 5
166
+ assert_equal bn_aima.edges.size, 4
167
+
168
+ assert !bn_aima.all_nodes_with_values?
169
+ b.set_value(false); assert !bn_aima.all_nodes_with_values?
170
+ e.set_value(false); assert !bn_aima.all_nodes_with_values?
171
+ a.set_value(true); assert !bn_aima.all_nodes_with_values?
172
+ j.set_value(true); assert !bn_aima.all_nodes_with_values?
173
+ m.set_value(true)
174
+
175
+ assert bn_aima.all_nodes_with_values?
176
+
177
+ value = bn_aima.inference_by_enumeration
178
+ assert_in_delta 0.0006281112, value, 10**(-10)
179
+
180
+ bn_aima.clear_values!
181
+ assert !bn_aima.all_nodes_with_values?
182
+
183
+ end
184
+
185
+ def test_inference_by_enumeration_ask
186
+ bn_aima = Bn4rTest.bayes_net_aima
187
+ b = bn_aima.get_variable("Burglary")
188
+ e = bn_aima.get_variable("Earthquake")
189
+ a = bn_aima.get_variable("Alarm")
190
+ j = bn_aima.get_variable("JohnCalls")
191
+ m = bn_aima.get_variable("MaryCalls")
192
+ j.set_value(true)
193
+ m.set_value(true)
194
+
195
+ assert_equal bn_aima.vertices.size, 5
196
+ assert_equal bn_aima.edges.size, 4
197
+ value1 = bn_aima.enumeration_ask(b,[j,m])
198
+ value2 = bn_aima.enumeration_ask(b,[j,m], [j,m,a,b,e])
199
+ value3 = bn_aima.enumeration_ask(b,[j,m], [b,e,a,j,m])
200
+ assert_equal value1[0], value2[0]
201
+ assert_equal value1[0].to_f, value3[0].to_f
202
+ assert_in_delta value1[1].to_f, value2[1].to_f, 10**(-10)
203
+ assert_equal value1[1], value3[1]
204
+ assert_in_delta 0.000592242, bn_aima.enumeration_ask(b,[j,m])[0], 10**(-9)
205
+ bn_aima.clear_values!
206
+ assert !bn_aima.all_nodes_with_values?
207
+
208
+ end
209
+
210
+
211
+ def test_table_probabilities_for_node
212
+ bn_aima = Bn4rTest.bayes_net_aima
213
+ b = bn_aima.get_variable("Burglary")
214
+ e = bn_aima.get_variable("Earthquake")
215
+ a = bn_aima.get_variable("Alarm")
216
+ BNTPGFromPositiveNegativeRelations.new.table_probabilities_for_node(a, [true,true])
217
+
218
+ # BNTPGFromPositiveNegativeRelations.new.populate_bn_with_tags(bn_aima)
219
+ assert true
220
+ end
221
+
222
+ def test_prior_sampling
223
+
224
+ bn = Bn4rTest.bayes_net_aima2
225
+
226
+ hash = Hash.new(0)
227
+ nodes_ordered = bn.nodes_ordered_by_dependencies
228
+ 10000.times { hash[ bn.prior_sample(nodes_ordered).collect {|v| v.value} ] += 1 }
229
+
230
+ combination_of_prob = nodes_ordered.collect { |v|
231
+ case v.name
232
+ when "Cloudy"
233
+ true
234
+ when "Sprinkler"
235
+ false
236
+ when "Rain"
237
+ true
238
+ when "WetGrass"
239
+ true
240
+ else
241
+ raise "Incorrect BayesNet created at Bn4rTest.bayes_net_aima2"
242
+ end
243
+ }
244
+
245
+ prob = hash[combination_of_prob].to_f/10000.0
246
+
247
+ assert_in_delta 0.3, prob, 0.1, "Its inprobable but possible that this error occurs, \
248
+ because we are working with statistics and random data, \
249
+ try again and if still occurs take care of it."
250
+
251
+ end
252
+
253
+ def test_rejection_sampling
254
+ bn = Bn4rTest.bayes_net_aima2
255
+
256
+ rain = bn.get_variable("Rain").copy
257
+ sprinkler = bn.get_variable("Sprinkler").copy
258
+
259
+ rain.set_value(true)
260
+ sprinkler.set_value(true)
261
+
262
+ results = bn.rejection_sampling(rain, sprinkler, 1000)
263
+
264
+ str_error = " Its improbable but possible that this error occurs, \
265
+ because we are working with statistics and random data, \
266
+ try again and if still occurs take care of it."
267
+ assert ((results[0] > 0.2) and (results[0] < 0.4)), "Results aren't in interval [0.2,0.4] --> " + results[0].to_s + str_error
268
+ assert ((results[1] > 0.6) and (results[1] < 0.8)), "Results aren't in interval [0.6,0.8] --> " + results[1].to_s + str_error
269
+ end
270
+
271
+ def test_likelihood_weighting
272
+ bn = Bn4rTest.bayes_net_aima2
273
+
274
+ rain = bn.get_variable("Rain").copy
275
+ sprinkler = bn.get_variable("Sprinkler").copy
276
+
277
+ rain.set_value(true)
278
+ sprinkler.set_value(true)
279
+
280
+ results = bn.likelihood_weighting(rain, sprinkler, 100)
281
+
282
+ str_error = " Its improbable but possible that this error occurs, \
283
+ because we are working with statistics and random data, \
284
+ try again and if still occurs take care of it."
285
+ assert ((results[0] > 0.2) and (results[0] < 0.4)), "Results aren't in interval [0.2,0.4] --> " + results[0].to_s + str_error
286
+ assert ((results[1] > 0.6) and (results[1] < 0.8)), "Results aren't in interval [0.6,0.8] --> " + results[1].to_s + str_error
287
+ end
288
+
289
+
290
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/bn4r'
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: bn4r
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-10-31 00:00:00 +01:00
8
+ summary: bn4r is a bayesian networks library on ruby that provides the user with classes for create bayesian networks and diverse algorithms for solve them.
9
+ require_paths:
10
+ - lib
11
+ email: sergio.espeja@gmail.com
12
+ homepage: http://bn4r.rubyforge.org
13
+ rubyforge_project: bn4r
14
+ description: bn4r is a bayesian networks library on ruby that provides the user with classes for create bayesian networks and diverse algorithms for solve them.
15
+ autorequire: bn4r
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Sergio Espeja
30
+ files:
31
+ - README
32
+ - CHANGELOG
33
+ - Rakefile
34
+ - test/test_helper.rb
35
+ - test/bn4r_test.rb
36
+ - lib/bn4r
37
+ - lib/bn4r.rb
38
+ - lib/bn4r/version.rb
39
+ - lib/bn4r/bn.rb
40
+ - lib/bn4r/bn_table_probabilities.rb
41
+ - lib/bn4r/bn_algorithms.rb
42
+ test_files: []
43
+
44
+ rdoc_options:
45
+ - --quiet
46
+ - --title
47
+ - bn4r documentation
48
+ - --opname
49
+ - index.html
50
+ - --line-numbers
51
+ - --main
52
+ - README
53
+ - --inline-source
54
+ - --exclude
55
+ - ^(examples|extras)/
56
+ extra_rdoc_files:
57
+ - README
58
+ - CHANGELOG
59
+ executables: []
60
+
61
+ extensions: []
62
+
63
+ requirements: []
64
+
65
+ dependencies:
66
+ - !ruby/object:Gem::Dependency
67
+ name: rgl
68
+ version_requirement:
69
+ version_requirements: !ruby/object:Gem::Version::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.2.3
74
+ version: