Class: Glymour::StructureLearning::LearningNet

Inherits:
Object
  • Object
show all
Includes:
Glymour::Statistics
Defined in:
lib/glymour.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Glymour::Statistics

#coindependent?

Constructor Details

#initialize(variable_container, p_value = 0.05) ⇒ LearningNet

Returns a new instance of LearningNet.



139
140
141
142
143
144
145
# File 'lib/glymour.rb', line 139

def initialize(variable_container, p_value = 0.05)
  @net = complete_graph(variable_container.variables).extend(GraphAlgorithms)
  @directed_edges = {}
  @directed_edges.default = []
  @n = -1
  @p_value = p_value
end

Instance Attribute Details

#directed_edgesObject

Returns the value of attribute directed_edges.



136
137
138
# File 'lib/glymour.rb', line 136

def directed_edges
  @directed_edges
end

#nObject

Returns the value of attribute n.



136
137
138
# File 'lib/glymour.rb', line 136

def n
  @n
end

#netObject

Returns the value of attribute net.



136
137
138
# File 'lib/glymour.rb', line 136

def net
  @net
end

#p_valueObject (readonly)

Returns the value of attribute p_value.



137
138
139
# File 'lib/glymour.rb', line 137

def p_value
  @p_value
end

Instance Method Details

#compatible_orientationsObject

Gives a list of all orientations of @net compatible with @directed_edges (i.e., all directed acyclic graphs with edge structure given partially by @directed_edges)



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/glymour.rb', line 216

def compatible_orientations
  compat_list = []
  edges = net.edges.extend(PowerSet)
  
  # Every orientation of net corresponds to a subset of its edges
  edges.power_set.each do |subset|
    # Orient edges in subset as source => target, outside of it as target => source
    # Any edges conflicting with directed_edges will be cyclic and therefore not counted
    current_orientation = make_directed(net.vertices, @directed_edges)
    
    edges.each do |e|
      if subset.include? e
        current_orientation.add_edge(e.source, e.target)
      else
        current_orientation.add_edge(e.target, e.source)
      end
    end
    
    compat_list << current_orientation if current_orientation.acyclic?
  end
  
  compat_list
end

#direct_edgesObject

Direct remaining edges in @net as much as possible



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/glymour.rb', line 194

def direct_edges
  puts "Directing edges where possible..."
  
  net.non_transitive.each do |triple|
    a, b, c = *triple
    
    intersect = (net.adjacent_either(a, c) & net.verts_on_paths(a, c)).extend(PowerSet)
    if intersect.power_set.select {|s| s.include? b}.none? { |subset| 
      coindependent?(p_value, a, c, *subset)
    }
      puts "Adding directed edge #{a.name} => #{b.name}..."
      @directed_edges[a] = (@directed_edges[a] << b).uniq
      
      puts "Adding directed edge #{c.name} => #{b.name}..."
      @directed_edges[c] = (@directed_edges[c] << b).uniq
    end
  end
end

#learn_structureObject

Perform the PC algorithm in full



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/glymour.rb', line 178

def learn_structure
  puts "Learning undirected net structure..."
  # Perform step until every pair of adjacent variables is dependent, and
  # set final_net to the _second-to-last_ state of @net
  begin
    puts "n = #{n}"
    final_net = net
    step
  end while n < 1
  
  net = final_net
  
  direct_edges
end

#stepObject

Perform one step of the PC algorithm



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/glymour.rb', line 148

def step
  any_independent = false
  net.edges.each do |e|
    a, b = e.source, e.target
    intersect = (@net.adjacent_either(a, b) & @net.verts_on_paths(a, b)).extend(PowerSet)
    
    # Is |Aab ^ Uab| > n? 
    if intersect.length <= n
      next
    else
      # Are a and b independent conditioned on any subsets of Aab ^ Uab of cardinality n+1?
      valid_intersects = intersect.power_set.select {|s| s.length == n+1}.reject { |subset| subset.include?(a) || subset.include?(b) }
      if valid_intersects.any? { |subset|
        print "Testing independence between #{a.name} and #{b.name}, conditioning on #{(subset.any? ? subset.map(&:name).join(', ') : 'nothing') + '...'}"
        print (coindependent?(p_value, a, b, *subset) ? "[+]\n" : "[-]\n")
        coindependent?(p_value, a, b, *subset)
      }
        @net = remove_edge(net, e)
        net.edges.each do |e|
          puts "#{e.source.name} => #{e.target.name}"
        end
        any_independent = true
      end
    end
  end
  @n += 1
  any_independent
end