File: theory.rb

package info (click to toggle)
ruby-graphviz 1.0.8-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 1,124 kB
  • ctags: 695
  • sloc: ruby: 7,656; xml: 26; makefile: 17
file content (321 lines) | stat: -rw-r--r-- 9,341 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
require 'graphviz/math/matrix'

class GraphViz
   class Theory
      def initialize( graph )
         @graph = graph
      end

      # Return the adjancy matrix of the graph
      def adjancy_matrix
         matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count )

         @graph.each_edge { |e|
            x = @graph.get_node(e.node_one( false )).index
            y = @graph.get_node(e.node_two( false )).index
            matrix[x+1, y+1] = 1
            matrix[y+1, x+1] = 1 if @graph.type == "graph"
         }

         return matrix
      end

      # Return the incidence matrix of the graph
      def incidence_matrix
         tail = (@graph.type == "digraph") ? -1 : 1
         matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.edge_count )

         @graph.each_edge { |e|
            x = e.index

            nstart = @graph.get_node(e.node_one( false )).index
            nend = @graph.get_node(e.node_two( false )).index

            matrix[nstart+1, x+1] = 1
            matrix[nend+1, x+1] = tail
            matrix[nend+1, x+1] = 2 if nstart == nend
         }

         return matrix
      end

      # Return the degree of the given node
      def degree( node )
         degree = 0
         name = node
         if node.kind_of?(GraphViz::Node)
            name = node.id
         end

         @graph.each_edge do |e|
            degree += 1 if e.node_one(false) == name or e.node_two(false) == name
         end

         return degree
      end

      # Return the laplacian matrix of the graph
      def laplacian_matrix
         return degree_matrix - adjancy_matrix
      end

      # Return <tt>true</tt> if the graph if symmetric, <tt>false</tt> otherwise
      def symmetric?
         adjancy_matrix == adjancy_matrix.transpose
      end

      # moore_dijkstra(source, destination)
      def moore_dijkstra( dep, arv )
         dep = @graph.get_node(dep) unless dep.kind_of?(GraphViz::Node)
         arv = @graph.get_node(arv) unless arv.kind_of?(GraphViz::Node)

         m = distance_matrix
         n = @graph.node_count
         # Table des sommets à choisir
         c = [dep.index]
         # Table des distances
         d = []
         d[dep.index] = 0

         # Table des predecesseurs
         pred = []

         @graph.each_node do |name, k|
            if k != dep
               d[k.index] = m[dep.index+1,k.index+1]
               pred[k.index] = dep
            end
         end

         while c.size < n
            # trouver y tel que d[y] = min{d[k];  k sommet tel que k n'appartient pas à c}
            mini = 1.0/0.0
            y = nil
            @graph.each_node do |name, k|
               next if c.include?(k.index)
               if d[k.index] < mini
                  mini = d[k.index]
                  y = k
               end
            end

            # si ce minimum est ∞ alors sortir de la boucle fin si
            break unless mini.to_f.infinite?.nil?

            c << y.index
            @graph.each_node do |name, k|
               next if c.include?(k.index)
               if d[k.index] > d[y.index] + m[y.index+1,k.index+1]
                  d[k.index] = d[y.index] + m[y.index+1,k.index+1]
                  pred[k.index] = y
               end
            end
         end

         # Construction du chemin le plus court
         ch = []
         k = arv
         while k.index != dep.index
            ch.unshift(k)
            k = pred[k.index]
         end
         ch.unshift(dep)

         if d[arv.index].to_f.infinite?
            return nil
         else
            return( {
               :path => ch,
               :distance => d[arv.index]
            } )
         end
      end

      # Return a liste of range
      #
      # If the returned array include nil values, there is one or more circuits in the graph
      def range
         matrix = adjancy_matrix
         unseen = (1..matrix.columns).to_a
         result = Array.new(matrix.columns)
         r = 0

         range_recursion( matrix, unseen, result, r )
      end

      # Return the critical path for a PERT network
      #
      # If the given graph is not a PERT network, return nul
      def critical_path
         return nil if range.include?(nil) or @graph.type != "digraph"
         r = [ [0, [1]] ]

         critical_path_recursion( distance_matrix, adjancy_matrix, r, [], 0 ).inject( {:distance => 0, :path => []} ) { |r, item|
            (r[:distance] < item[0]) ? { :distance => item[0], :path => item[1] } : r
         }
      end

      # Return the PageRank in an directed graph.
      #
      # * damping_factor: PageRank dumping factor.
      # * max_iterations: Maximum number of iterations.
      # * min_delta: Smallest variation required to have a new iteration.
      def pagerank(damping_factor = 0.85, max_iterations = 100, min_delta = 0.00001)
         return nil unless @graph.directed?

         min_value = (1.0-damping_factor)/@graph.node_count

         pagerank = {}
         @graph.each_node { |_, node|
            pagerank[node] = 1.0/@graph.node_count
         }

         max_iterations.times { |_|
            diff = 0
            @graph.each_node { |_, node|
               rank = min_value
               incidents(node).each { |referent|
                  rank += damping_factor * pagerank[referent] / neighbors(referent).size
               }

               diff += (pagerank[node] - rank).abs
               pagerank[node] = rank
            }
            break if diff < min_delta
         }

         return pagerank
      end

      # Return the list of nodes that are directly accessible from given node
      def neighbors(node)
         if node.class == String
            @graph.get_node(node).neighbors
         else
            node.neighbors
         end
      end

      # Return the list of nodes that are incident to the given node (in a directed graph neighbors == incidents)
      def incidents(node)
         if node.class == String
            @graph.get_node(node).incidents
         else
            node.incidents
         end
      end

      # Breadth First Search
      def bfs(node, &b)
         queue = []
         visited_nodes = []
         node = @graph.get_node(node) if node.kind_of? String
         queue << node
         visited_nodes << node

         while not queue.empty?
            node = queue.shift
            b.call(node)
            neighbors(node).each do |n|
               unless visited_nodes.include?(n)
                  visited_nodes << n
                  queue << n
               end
            end
         end
      end

      # Depth First Search
      def dfs(node, &b)
         visited_nodes = []
         recursive_dfs(node, visited_nodes, &b)
      end

      private
      def recursive_dfs(node, visited_nodes, &b)
         node = @graph.get_node(node) if node.kind_of? String
         b.call(node)
         visited_nodes << node
         neighbors(node).each do |n|
            recursive_dfs(n, visited_nodes, &b) unless visited_nodes.include?(n)
         end
      end

      def distance_matrix
         type = @graph.type
         matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count, (1.0/0.0) )

         @graph.each_edge { |e|
            x = @graph.get_node(e.node_one( false )).index
            y = @graph.get_node(e.node_two( false )).index
            unless x == y
               weight = ((e[:weight].nil?) ? 1 : e[:weight].to_f)
               matrix[x+1, y+1] = weight
               matrix[y+1, x+1] = weight if type == "graph"
            end
         }

         return matrix
      end

      def degree_matrix
         matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count )
         @graph.each_node do |name, node|
            i = node.index
            matrix[i+1, i+1] = degree(node)
         end
         return matrix
      end

      def range_recursion(matrix, unseen, result, r)
         remove = []
         matrix.columns.times do |c|
            if matrix.sum_of_column(c+1) == 0
               result[unseen[c]-1] = r
               remove.unshift( c + 1 )
            end
         end

         remove.each do |rem|
            matrix = matrix.remove_line(rem).remove_column(rem)
            unseen.delete_at(rem-1)
         end

         if matrix.columns == 1 and matrix.lines == 1
            if matrix.sum_of_column(1) == 0
               result[unseen[0]-1] = r+1
            end
         elsif remove.size > 0
            range_recursion( matrix, unseen, result, r+1 )
         end

         return result
      end

      def index_of_item( array, item )
         array.inject( [0,[]] ){|a,i|
            a[1] << a[0] if i == item
            a[0] += 1
            a
         }[1]
      end

      def critical_path_recursion( d, a, r, result, level )
         r.each do |p|
            node = p[1][-1]
            index_of_item( a.line(node), 1 ).each do |c|
               succ = c+1

               cpath = [ (p[0] + d[node,succ]), (p[1].clone << succ) ]

               if index_of_item( a.line(succ), 1 ).size > 0
                  capth = critical_path_recursion( d, a, [cpath], result, level+1 )
               else
                  result << cpath
               end
            end
         end
         return result
      end
   end
end