pgm 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8801783d85c563cfc9bdbf262d6ebf52db065a1
4
+ data.tar.gz: d60116e3e47e50f95fa90de363ff2aa36a58f60f
5
+ SHA512:
6
+ metadata.gz: df4d59efac1f7cc6912c4f2f5c163dfd6428e271b7991a8e46f2756fe3fcf057dfc872812620cc39138b6fb9bf55f03c923b68d3184b49db66c3deb54490e9a5
7
+ data.tar.gz: 294e6adb7dabe1aafc2726bbb92612b6666a7794b47792d563ea14a18a934bc96781a3013606c819655c68d689657f041f908739e3800a1dc5d2896ea6d3d866
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rspec'
4
+ gem 'rgl'
@@ -0,0 +1,15 @@
1
+ # PGM: Probabilistic Graphical Model Library
2
+
3
+ [![Bitbucket](https://img.shields.io/badge/bitbucket-natapol--pmg-blue.svg)](https://bitbucket.org/natapol/pgm)
4
+ [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://rubydoc.org/gems/pgm/frames)
5
+ [![Gem Version](https://badge.fury.io/rb/pgm.svg)](https://bitbucket.org/natapol/pgm/releases)
6
+ [![License](http://img.shields.io/badge/license-GPL--3-yellowgreen.svg)](#license)
7
+
8
+ ## Synopsis
9
+ This library is expected to provide basic functions for Probabilistic Graphical Model analysis. However, the library is still in development process. Only Bayesian network with discrete random variable module is available at this time.
10
+
11
+ ## Feature List
12
+ **1. Bayesian network with discrete random variable**
13
+
14
+ ## Design principles
15
+ The model library is derived from
@@ -0,0 +1 @@
1
+ Natapol Pornputtapong
@@ -0,0 +1,44 @@
1
+ # Probabilistic Graphical Model Library
2
+ #
3
+ # Copyright (C) 2016
4
+ #
5
+ # author: Natapol Pornputtapong <natapol.por@gmail.com>
6
+ #
7
+ # Documentation: Natapol Pornputtapong
8
+ #
9
+
10
+ raise "Please, use ruby 1.9.0 or later." if RUBY_VERSION < "1.9.0"
11
+
12
+ $: << File.join(File.expand_path(File.dirname(__FILE__)))
13
+
14
+ #Internal library
15
+ require 'i18n'
16
+
17
+ #External library
18
+
19
+
20
+ I18n.enforce_available_locales = false
21
+
22
+
23
+
24
+ #library
25
+ require 'pgm/model'
26
+
27
+ # A ruby library for probabilistic graphical model
28
+ module PGM
29
+
30
+ # version of the library
31
+ VERSION = "0.0.1"
32
+
33
+ # Exception class for NotDirectedError
34
+ class NotDirectedError < RuntimeError; end
35
+
36
+ # Exception class for NotUndirectedError
37
+ class NotUndirectedError < RuntimeError; end
38
+
39
+ # Exception class for NoVertexError
40
+ class NoVertexError < IndexError; end
41
+
42
+ # Exception class for NoEdgeError
43
+ class NoEdgeError < IndexError; end
44
+ end
@@ -0,0 +1,126 @@
1
+ # Probabilistic Graphical Model Library
2
+ #
3
+ # Copyright (C) 2016
4
+ #
5
+ # author: Natapol Pornputtapong <natapol.por@gmail.com>
6
+ #
7
+ # Documentation: Natapol Pornputtapong
8
+ #
9
+
10
+ module PGM
11
+
12
+ # describe bidirectionally function to Graph
13
+ module Bidirectional
14
+
15
+ # Instantiate DiscreateVariable
16
+ #
17
+ # @param edgelist_class [Object] Object to store edge class
18
+ # @param other_graphs [PGM::BayesianNet] List of variable states
19
+ def initialize(edgelist_class = Set, *other_graphs)
20
+ @vertices_rev_dict = Hash.new
21
+ @variables = Hash.new
22
+ super
23
+ end
24
+
25
+ # Copy internal vertices_dict
26
+ #
27
+ # @param orig [PGM::BayesianNet] Copy from other graph
28
+ def initialize_copy(orig)
29
+ super
30
+ @vertices_rev_dict = orig.instance_eval { @vertices_rev_dict }.dup
31
+ @vertices_rev_dict.keys.each do |v|
32
+ @vertices_rev_dict[v] = @vertices_rev_dict[v].dup
33
+ end
34
+ @variables = orig.instance_eval { @variables }.dup
35
+ end
36
+
37
+ # Return variable with the name
38
+ #
39
+ # @param name [String, Symbol] The name of variable
40
+ # @return [PGM::Variable] Variable object
41
+ def var(name=nil)
42
+ if nil
43
+ return @variables
44
+ else
45
+ return @variables[name.to_sym]
46
+ end
47
+ end
48
+
49
+ # Create variable
50
+ #
51
+ # @param name [String, Symbol] Name of variable
52
+ # @param states [Array<String, Symbol>] List of variable states
53
+ # @param parents [Array<PGM::Variable>] List of parent variable
54
+ # @param type [PGM::Variable] type of variable
55
+ def create_var(name, states, parents: [], type: PGM::DiscreateVariable)
56
+ add_vertex(name)
57
+ @variables[name.to_sym] = type.new(self, name, states, parents: parents)
58
+ end
59
+
60
+ # Check if model has the variable named as input
61
+ #
62
+ # @param name [String, Symbol] Name of variable
63
+ # @return [Boolean] if model has the variable
64
+ def has_var?(name)
65
+ return @variables.has_key?(name.to_sym)
66
+ end
67
+
68
+ # assign link from u to v
69
+ #
70
+ # @param u [PGM::Variable] Variable to be linked
71
+ # @param v [PGM::Variable] Variable to be linked
72
+ def create_link(u, v)
73
+ if has_var?(u) && has_var?(v)
74
+ var(v).to(var(u))
75
+ end
76
+ end
77
+
78
+ protected
79
+
80
+ def basic_add_edge(u, v)
81
+ super
82
+ @vertices_rev_dict[v].add(u)
83
+ end
84
+
85
+ # See MutableGraph#add_vertex.
86
+ #
87
+ # If the vertex is already in the graph (using eql?), the method does
88
+ # nothing.
89
+ #
90
+ def add_vertex(v)
91
+ super
92
+ @vertices_rev_dict[v] ||= @edgelist_class.new
93
+ end
94
+
95
+ # See MutableGraph#remove_vertex.
96
+ #
97
+ def remove_vertex(v)
98
+ super
99
+ @vertices_rev_dict.delete(v)
100
+ # remove v from all adjacency lists
101
+ @vertices_rev_dict.each_value { |adjList| adjList.delete(v) }
102
+ end
103
+
104
+ # See MutableGraph::remove_edge.
105
+ #
106
+ # Also removes (v,u)
107
+ #
108
+ def remove_edge(u, v)
109
+ super
110
+ @vertices_rev_dict[v].delete(u) unless @vertices_dict[v].nil?
111
+ end
112
+
113
+ def each_parent(v, &b) # :nodoc:
114
+ parent_list = (@vertices_rev_dict[v] or raise NoVertexError, "No vertex #{v}.")
115
+ parent_list.each(&b)
116
+ end
117
+
118
+ def parent_vertices(v)
119
+ r = []
120
+ each_parent(v) { |u| r << u }
121
+ r
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,209 @@
1
+ # Probabilistic Graphical Model Library
2
+ #
3
+ # Copyright (C) 2016
4
+ #
5
+ # author: Natapol Pornputtapong <natapol.por@gmail.com>
6
+ #
7
+ # Documentation: Natapol Pornputtapong
8
+ #
9
+
10
+ require 'rgl/adjacency'
11
+ require 'rgl/topsort'
12
+ require 'rgl/dot'
13
+
14
+ require 'pgm/variable'
15
+ require 'pgm/bidirectional'
16
+
17
+ module PGM
18
+ # Bayesian Network Class
19
+ #
20
+ # Example:
21
+ #
22
+ # create model
23
+ # bayenet = PGM::BayesianNet.new()
24
+ # bayenet.create_var(:cloudy, [:t, :f])
25
+ # bayenet.create_var(:sprinkler, [:t, :f])
26
+ # bayenet.create_var(:rain, [:t, :f])
27
+ # bayenet.create_var(:grass_wet, [:t, :f])
28
+ # bayenet.create_link(:cloudy, :sprinkler)
29
+ # bayenet.create_link(:cloudy, :rain)
30
+ # bayenet.create_link(:sprinkler, :grass_wet)
31
+ # bayenet.create_link(:rain, :grass_wet)
32
+ #
33
+ # learn with data
34
+ # bayenet.learn([
35
+ # {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
36
+ # {:cloudy => :t, :sprinkler => :t, :rain => :f, :grass_wet => :t},
37
+ # {:cloudy => :f, :sprinkler => :f, :rain => :t, :grass_wet => :t},
38
+ # {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
39
+ # {:cloudy => :f, :sprinkler => :t, :rain => :f, :grass_wet => :f},
40
+ # {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
41
+ # {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
42
+ # {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
43
+ # {:cloudy => :t, :sprinkler => :f, :rain => :f, :grass_wet => :f},
44
+ # {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
45
+ # ])
46
+ #
47
+ # print out the conditional_probability_table
48
+ # bayenet.puts_conditional_probability_table
49
+ #
50
+ # create graph from model
51
+ # bayenet.write_to_graphic_file('jpg')
52
+ class BayesianNet < RGL::DirectedAdjacencyGraph
53
+
54
+ include PGM::Bidirectional
55
+
56
+ # Find common parents of input nodes
57
+ #
58
+ # @param vs [Array<PGM::Variable>] List of vertices
59
+ # @return [Array<PGM::Variable>] List parent vertices
60
+ def common_parents(*vs)
61
+ r = nil
62
+ vs.each do |v|
63
+ if r
64
+ r &= parent_vertices(v)
65
+ else
66
+ r = parent_vertices(v)
67
+ end
68
+ end
69
+ r ||= []
70
+ end
71
+
72
+ # Check if there are common parents of input vertices
73
+ #
74
+ # @param vs [Array<PGM::Variable>] List of variables
75
+ # @return [Boolean] Is there common parents
76
+ def common_parents?(*vs)
77
+ return !common_parents(*vs).empty?
78
+ end
79
+
80
+ # Find common children of input vertices
81
+ #
82
+ # @param vs [Array<PGM::Variable>] List of vertices
83
+ # @return [Array<PGM::Variable>] List child vertices
84
+ def common_children(*vs)
85
+ r = nil
86
+ vs.each do |v|
87
+ if r
88
+ r &= adjacent_vertices(v)
89
+ else
90
+ r = adjacent_vertices(v)
91
+ end
92
+ end
93
+ r ||= []
94
+ end
95
+
96
+ # Check if there are common children of input vertices
97
+ #
98
+ # @param vs [Array<PGM::Variable>] List of vertices
99
+ # @return [Boolean] Is there common children
100
+ def common_children?(*vs)
101
+ return !common_children(*vs).empty?
102
+ end
103
+
104
+ # Check if vertices are in cascading topology
105
+ #
106
+ # @param vs [Array<PGM::Variable>] List of vertices
107
+ # @return [Boolean] Are vertices in cascading topology
108
+ def cascade?(*vs)
109
+ prev_children = nil
110
+ r = true
111
+ if vs.length > 2
112
+ vs.each do |v|
113
+ prev_children ||= [v]
114
+ if !prev_children.include?(v)
115
+ r = false
116
+ break
117
+ else
118
+ prev_children = adjacent_vertices(v)
119
+ end
120
+ end
121
+ else
122
+ r = false
123
+ end
124
+ return r
125
+ end
126
+
127
+ # Check if vertices are in V-Structure
128
+ #
129
+ # @param vs [Array<PGM::Variable>] List of vertices
130
+ # @return [Boolean] Are vertices in V-Structure
131
+ def v_tructure?(*vs)
132
+ if vs.length == 3
133
+ return parent_vertices(vs[0]).sort == vs[1..-1].sort
134
+ else
135
+ return false
136
+ end
137
+ end
138
+
139
+ # Generate string notation of Baysian Network
140
+ #
141
+ # @return [String] Model notation
142
+ def model_notation
143
+ line = []
144
+ each_vertex do |v|
145
+ parents = parent_vertices(v)
146
+ line.push("P(#{v}#{parents.empty? ? '' : "|#{parents.join(',')}"})")
147
+ end
148
+ return "P(#{vertices.sort.join(',')}) = #{line.sort.join('')}"
149
+ end
150
+
151
+ # learn model from array of condition and automatically calculate the
152
+ # conditional probability table. Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
153
+ #
154
+ # @param arr [Array<Hash{Symbol => Symbol,String}>] Array of data for learning in Hash objects
155
+ def learn(arr)
156
+ @variables.each_value do |var|
157
+ var.learn(arr)
158
+ end
159
+ end
160
+
161
+ # Learn the model with discrete data.
162
+ # Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
163
+ #
164
+ # @param condition [Hash{Symbol => Symbol,String}] Data for learning in a Hash objects
165
+ def add_data(condition)
166
+ @variables.each_value do |var|
167
+ var.add_data(condition)
168
+ end
169
+ end
170
+
171
+ # Set conditional probability to a random variable
172
+ # Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
173
+ #
174
+ # @param varname [String, Symbol] Random variable name
175
+ # @param prob [Float] Probability
176
+ # @param condition [Hash{Symbol => Symbol,String}] Data for learning in Hash objects
177
+ def set_conditional_probability(varname, prob, condition)
178
+ var(varname.to_sym).set_conditional_probability(prob, condition)
179
+ end
180
+
181
+ # Calculate conditional probability table from learned data
182
+ #
183
+ def calculate_conditional_probability_table
184
+ @variables.each_value do |var|
185
+ var.calculate_conditional_probability_table
186
+ end
187
+ end
188
+
189
+ alias_method :cal_cpt, :calculate_conditional_probability_table
190
+
191
+ # puts conditional probability table on the screen
192
+ #
193
+ # @param cellsize [Integer] Size of each cell
194
+ def puts_conditional_probability_table(cellsize = 25)
195
+ @variables.each_value do |val|
196
+ val.puts_conditional_probability_table(cellsize)
197
+ end
198
+ end
199
+
200
+ alias_method :puts_cpt, :puts_conditional_probability_table
201
+
202
+ # Order random variable by ancestral order
203
+ #
204
+ def ancestral_ordering
205
+ return self.topsort_iterator.to_a
206
+ end
207
+ end
208
+
209
+ end
@@ -0,0 +1,180 @@
1
+ # Probabilistic Graphical Model Library
2
+ #
3
+ # Copyright (C) 2016
4
+ #
5
+ # author: Natapol Pornputtapong <natapol.por@gmail.com>
6
+ #
7
+ # Documentation: Natapol Pornputtapong
8
+ #
9
+
10
+ require 'narray'
11
+
12
+ module PGM
13
+
14
+ # Most ancestral class for Random variable
15
+ class Variable
16
+ def initialize
17
+ raise NotImplementedError
18
+ end
19
+ end
20
+
21
+ # A class for discrete random variable
22
+ class DiscreateVariable < Variable
23
+
24
+ attr_reader :name, :model, :states, :parents, :children
25
+
26
+ # Instantiate DiscreateVariable
27
+ #
28
+ # @param model [PGM::BayesianNet] Model for variable to be added
29
+ # @param name [String, Symbol] Name of variable
30
+ # @param states [Array<String, Symbol>] List of variable states
31
+ # @param parents [Array<PGM::Variable>] List of parent vertices
32
+ # @param children [Array<PGM::Variable>] List of child vertices
33
+ def initialize(model, name, states, parents: [], children: [])
34
+ @name = name.to_sym
35
+ @model = model
36
+ @states = states.map{|x| x.to_sym}.sort
37
+ @parents = parents
38
+ @children = children
39
+ self.create_conditional_probability_table
40
+ end
41
+
42
+ # compare variables
43
+ #
44
+ # @param other [PGM::Variable] Model for variable to be added
45
+ # @return [Integer] List of child vertices
46
+ def <=>(other)
47
+ if other.is_a?(PGM::Variable)
48
+ if @model == other.model
49
+ return @name <=> other.name
50
+ else
51
+ return @model <=> other.model
52
+ end
53
+ else
54
+ raise TypeError, "#{other} is not PGM::Variable"
55
+ end
56
+ end
57
+
58
+ # assign link from self to other
59
+ #
60
+ # @param other [PGM::Variable] Variable to be linked
61
+ def to(other)
62
+ other.add_parent(self)
63
+ self.add_child(other)
64
+ @model.add_edge(@name, other.name)
65
+ end
66
+
67
+ # learn model from array of condition and automatically calculate the
68
+ # conditional probability table
69
+ #
70
+ # @param arr [Array<Hash{Symbol => Symbol,String}>] Array of data for learning in Hash objects
71
+ def learn(arr)
72
+ arr.each do |item|
73
+ self.add_data(item)
74
+ end
75
+ self.calculate_conditional_probability_table
76
+ end
77
+
78
+ # learn model from data.
79
+ # Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
80
+ #
81
+ # @param condition [Hash{Symbol => Symbol,String}] Data for learning in Hash objects
82
+ def add_data(condition)
83
+ @count_table[*self.data_coor(condition)] += 1
84
+ end
85
+
86
+ # calculate conditional probability table
87
+ def calculate_conditional_probability_table
88
+ @probability_table = @count_table.floor / @count_table.sum(1).to_f
89
+ end
90
+
91
+ alias_method :cal_cpt, :calculate_conditional_probability_table
92
+
93
+ # puts conditional probability table on the screen
94
+ #
95
+ # @param cellsize [Integer] Size of each cell
96
+ def puts_conditional_probability_table(cellsize = 25)
97
+ puts "#{@name.to_s.center(cellsize)}|#{@adjoint_var_list.map{|x| "P(#{x})".center(cellsize)}.join('|')}"
98
+ 0.upto(@states.length - 1).each do |ind|
99
+ puts "#{@states[ind].to_s.rjust(cellsize)}|#{@probability_table[true, ind].to_a.map{|x| x.round(5).to_s.rjust(cellsize)}.join('|')}"
100
+ end
101
+ end
102
+
103
+ alias_method :puts_cpt, :puts_conditional_probability_table
104
+
105
+ # assign conditional probability for specified condition.
106
+ # Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
107
+ #
108
+ # @param prob [Float] probability
109
+ # @param condition [Hash{Symbol => Symbol,String}] condition in Hash objects
110
+ def set_conditional_probability(prob, condition)
111
+ @probability_table[*self.data_coor(condition)] = prob
112
+ end
113
+
114
+ alias_method :set_cp, :set_conditional_probability
115
+
116
+ # return conditional probability of the specified condition.
117
+ # Condition is in a Hash format { var_name1: state_name1, var_name2: state_name2 }
118
+ #
119
+ # @param condition [Hash{Symbol => Symbol,String}] condition in Hash objects
120
+ # @return [Float] probability
121
+ def conditional_probability(condition)
122
+ return @probability_table[*self.data_coor(condition)]
123
+ end
124
+
125
+ protected
126
+
127
+ def data_coor(condition)
128
+ #[column, row]
129
+ notation = []
130
+ @parents.map{|x| x.name.to_sym}.each do |name|
131
+ notation.push("#{name}=#{condition[name]}")
132
+ end
133
+ return [
134
+ @adjoint_var_list.include?(notation.join(',')) ? @adjoint_var_list.index(notation.join(',')) : true,
135
+ @states.include?(condition[@name]) ? @states.index(condition[@name]) : true
136
+ ]
137
+ end
138
+
139
+ def state_notation
140
+ notations = []
141
+ self.states.each do |state|
142
+ notations.push("#{name}=#{state}")
143
+ end
144
+ return notations
145
+ end
146
+
147
+ def create_conditional_probability_table
148
+ list = []
149
+ @parents.each do |parent|
150
+ list.push(parent.state_notation)
151
+ end
152
+ if list.length == 0
153
+ @adjoint_var_list = [@name]
154
+ elsif list.length == 1
155
+ @adjoint_var_list = list[0]
156
+ else
157
+ @adjoint_var_list = list[0].product(*list[1..-1]).map{|x| x.join(',')}
158
+ end
159
+ @count_table = NArray.sfloat(@adjoint_var_list.length, @states.length).fill(0.0000001)
160
+ @probability_table = NArray.sfloat(@adjoint_var_list.length, @states.length)
161
+ end
162
+
163
+ def add_parent(var)
164
+ @parents.push(var)
165
+ @parents.sort!
166
+ self.create_conditional_probability_table
167
+ end
168
+
169
+ def add_child(var)
170
+ @children.push(var)
171
+ @children.sort!
172
+ end
173
+
174
+ end
175
+
176
+ # A class for concrete random variable
177
+ class ConcreteVariable < Variable
178
+
179
+ end
180
+ end
@@ -0,0 +1,46 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'pgm'
4
+ require 'date'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'pgm'
8
+ s.version = PGM::VERSION
9
+ s.date = Date.today.strftime('%Y-%m-%d')
10
+ s.platform = Gem::Platform::RUBY
11
+
12
+ s.summary = "A ruby library for probabilistic graphical model"
13
+ s.description = "A ruby library for probabilistic graphical model"
14
+ s.authors = ["Natapol Pornputtapong"]
15
+ s.email = ['natapol.por@gmail.com']
16
+
17
+ s.homepage = 'http://rubygems.org/gems/pgm'
18
+ s.licenses = ['GPL-3.0']
19
+
20
+ # s.rubyforge_project = "neography"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ['lib']
26
+ s.bindir = 'bin'
27
+
28
+ s.add_dependency "rgl", "~> 0.5"
29
+ s.add_dependency "narray", "~> 0.6"
30
+ # s.add_dependency "bson_ext", "~> 1.9"
31
+ # s.add_dependency "rdf", "~> 1.1"
32
+
33
+
34
+ s.add_development_dependency "rspec", ">= 3.4"
35
+
36
+ #### Documentation and testing.
37
+
38
+ s.has_rdoc = true
39
+ s.extra_rdoc_files = ['README.md']
40
+ s.rdoc_options += [
41
+ '--title', 'PGM - Probabilistic Graphical Model Library',
42
+ '--main', 'README.md',
43
+ '--line-numbers'
44
+ ]
45
+
46
+ end
@@ -0,0 +1,150 @@
1
+ # in spec/calculator_spec.rb
2
+ # - RSpec adds ./lib to the $LOAD_PATH
3
+ require "pgm/model"
4
+
5
+ RSpec.describe PGM::BayesianNet do
6
+
7
+ context "test graph module" do
8
+ def build_model
9
+ PGM::BayesianNet[*'ACCFBDBEDFEHFHFG'.chars]
10
+ end
11
+
12
+ before(:example) do
13
+ @model = build_model
14
+ end
15
+
16
+ describe '#common_parents' do
17
+ it 'returns the blank array' do
18
+ expect(@model.common_parents).to eq([])
19
+ end
20
+
21
+ it 'returns the common parent list' do
22
+ expect(@model.common_parents(*'GH'.chars)).to eq(['F'])
23
+ end
24
+ end
25
+
26
+ describe '#common_parents?' do
27
+ it 'returns false when blank input' do
28
+ expect(@model.common_parents?).to eq(false)
29
+ end
30
+
31
+ it 'returns false when no common parents' do
32
+ expect(@model.common_parents?(*'CD'.chars)).to eq(false)
33
+ expect(@model.common_parents?(*'CDE'.chars)).to eq(false)
34
+ end
35
+
36
+ it 'returns true when there are common parents' do
37
+ expect(@model.common_parents?(*'GH'.chars)).to eq(true)
38
+ end
39
+ end
40
+
41
+ describe '#common_children' do
42
+ it 'returns the blank array' do
43
+ expect(@model.common_children).to eq([])
44
+ end
45
+
46
+ it 'returns the common child list' do
47
+ expect(@model.common_children(*'CD'.chars)).to eq(['F'])
48
+ end
49
+ end
50
+
51
+ describe '#common_children?' do
52
+ it 'returns false when blank input' do
53
+ expect(@model.common_children?).to eq(false)
54
+ end
55
+
56
+ it 'returns false when no common children' do
57
+ expect(@model.common_children?(*'CE'.chars)).to eq(false)
58
+ expect(@model.common_children?(*'CDE'.chars)).to eq(false)
59
+ end
60
+
61
+ it 'returns true when there are common children' do
62
+ expect(@model.common_children?(*'CD'.chars)).to eq(true)
63
+ end
64
+ end
65
+
66
+ describe '#cascade?' do
67
+ it 'returns false when blank inputs' do
68
+ expect(@model.cascade?).to eq(false)
69
+ end
70
+
71
+ it 'returns false when not cascading' do
72
+ expect(@model.cascade?(*'AC'.chars)).to eq(false)
73
+ expect(@model.cascade?(*'ACH'.chars)).to eq(false)
74
+ expect(@model.cascade?(*'ADE'.chars)).to eq(false)
75
+ end
76
+
77
+ it 'returns true when cascading' do
78
+ expect(@model.cascade?(*'ACF'.chars)).to eq(true)
79
+ expect(@model.cascade?(*'ACFH'.chars)).to eq(true)
80
+ end
81
+ end
82
+
83
+ describe '#v_tructure?' do
84
+ it 'returns false when wrong number of node' do
85
+ expect(@model.v_tructure?).to eq(false)
86
+ expect(@model.v_tructure?(*'AC'.chars)).to eq(false)
87
+ expect(@model.v_tructure?(*'FCDB'.chars)).to eq(false)
88
+ end
89
+
90
+ it 'returns false when not in v-structure' do
91
+ expect(@model.v_tructure?(*'CFD'.chars)).to eq(false)
92
+ expect(@model.v_tructure?(*'BDE'.chars)).to eq(false)
93
+ expect(@model.v_tructure?(*'FAB'.chars)).to eq(false)
94
+ end
95
+
96
+ it 'returns true when v-structure' do
97
+ expect(@model.v_tructure?(*'FCD'.chars)).to eq(true)
98
+ end
99
+ end
100
+
101
+ describe '#model_notation' do
102
+ it 'returns a joint ditribution string' do
103
+ expect(@model.model_notation).to eq('P(A,B,C,D,E,F,G,H) = P(A)P(B)P(C|A)P(D|B)P(E|B)P(F|C,D)P(G|F)P(H|E,F)')
104
+ end
105
+ end
106
+ end
107
+
108
+ context "testing probability net" do
109
+ def build_model
110
+ a = PGM::BayesianNet.new()
111
+ a.create_var(:cloudy, [:t, :f])
112
+ a.create_var(:sprinkler, [:t, :f])
113
+ a.create_var(:rain, [:t, :f])
114
+ a.create_var(:grass_wet, [:t, :f])
115
+ a.create_link(:cloudy, :sprinkler)
116
+ a.create_link(:cloudy, :rain)
117
+ a.create_link(:sprinkler, :grass_wet)
118
+ a.create_link(:rain, :grass_wet)
119
+ return a
120
+ end
121
+
122
+ before(:example) do
123
+ @model = build_model
124
+ end
125
+
126
+ describe '#model_notation' do
127
+ it 'returns a model notation string' do
128
+ expect(@model.model_notation).to eq("P(cloudy,grass_wet,rain,sprinkler) = P(cloudy|sprinkler,rain)P(grass_wet)P(rain|grass_wet)P(sprinkler|grass_wet)")
129
+ end
130
+ end
131
+
132
+ describe '#learning' do
133
+ it 'returns a joint ditribution string' do
134
+ @model.learn([
135
+ {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
136
+ {:cloudy => :t, :sprinkler => :t, :rain => :f, :grass_wet => :t},
137
+ {:cloudy => :f, :sprinkler => :f, :rain => :t, :grass_wet => :t},
138
+ {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
139
+ {:cloudy => :f, :sprinkler => :t, :rain => :f, :grass_wet => :f},
140
+ {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
141
+ {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
142
+ {:cloudy => :t, :sprinkler => :f, :rain => :t, :grass_wet => :t},
143
+ {:cloudy => :t, :sprinkler => :f, :rain => :f, :grass_wet => :f},
144
+ {:cloudy => :f, :sprinkler => :f, :rain => :f, :grass_wet => :f},
145
+ ])
146
+ expect(@model.var(:cloudy).conditional_probability({:cloudy => :f}).to_a).to eq([0.75, 0.4999999403953552, 0.25, 0.0])
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,8 @@
1
+ RSpec.configure do |config|
2
+ # use color in STDOUT
3
+ config.color_enabled = true
4
+
5
+ config.tty = true
6
+
7
+ config.formatter = :documentation
8
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pgm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Natapol Pornputtapong
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rgl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: narray
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ description: A ruby library for probabilistic graphical model
56
+ email:
57
+ - natapol.por@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files:
61
+ - README.md
62
+ files:
63
+ - Gemfile
64
+ - README.md
65
+ - contributors.txt
66
+ - lib/pgm.rb
67
+ - lib/pgm/bidirectional.rb
68
+ - lib/pgm/model.rb
69
+ - lib/pgm/variable.rb
70
+ - pgm.gemspec
71
+ - spec/model_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: http://rubygems.org/gems/pgm
74
+ licenses:
75
+ - GPL-3.0
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options:
79
+ - "--title"
80
+ - PGM - Probabilistic Graphical Model Library
81
+ - "--main"
82
+ - README.md
83
+ - "--line-numbers"
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.0.14.1
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: A ruby library for probabilistic graphical model
102
+ test_files:
103
+ - spec/model_spec.rb
104
+ - spec/spec_helper.rb
105
+ has_rdoc: true