ruby-graphviz 0.9.16 → 0.9.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,221 @@
1
+ class GraphViz
2
+ class Math
3
+ def self.Matrix( line, column = 0, val = 0 )
4
+ GraphViz::Math::Matrix.new(line, column, val)
5
+ end
6
+
7
+ class CoordinateError < RuntimeError
8
+ end
9
+ class ValueError < RuntimeError
10
+ end
11
+
12
+ class Matrix
13
+ def initialize( line_or_array, column = 0, val = 0 )
14
+ if line_or_array.kind_of?(Array)
15
+ line = line_or_array.size
16
+ column = nil
17
+ line_or_array.size.times do |l|
18
+ unless line_or_array[l].kind_of?(Array)
19
+ raise ArgumentError, "Wrong matrix definition"
20
+ end
21
+ column = line_or_array[l].size if column.nil?
22
+ unless line_or_array[l].size == column
23
+ raise ArgumentError, "Wrong matrix definition"
24
+ end
25
+ line_or_array[l].size.times do |c|
26
+ unless line_or_array[l][c].kind_of?(Numeric)
27
+ raise ValueError, "Element at [#{l+1}, #{c+1}] is not a number"
28
+ end
29
+ end
30
+ end
31
+
32
+ @matrix = line_or_array
33
+ @line = line
34
+ @column = column
35
+ elsif line_or_array.kind_of?(Integer) and column > 0
36
+ @matrix = Array.new(line_or_array)
37
+ @matrix.size.times do |l|
38
+ @matrix[l] = Array.new(column)
39
+ @matrix[l].size.times do |c|
40
+ @matrix[l][c] = val
41
+ end
42
+ end
43
+ @line = line_or_array
44
+ @column = column
45
+ else
46
+ raise ArgumentError, "Wrong matrix definition"
47
+ end
48
+ end
49
+
50
+ def [](line, column)
51
+ unless (0...@line).to_a.include?(line-1)
52
+ raise CoordinateError, "Line out of range (#{line} for 1..#{@line})!"
53
+ end
54
+ unless (0...@column).to_a.include?(column-1)
55
+ raise CoordinateError, "Column out of range (#{column} for 1..#{@column})!"
56
+ end
57
+ @matrix[line-1][column-1]
58
+ end
59
+
60
+ def []=( line, column, val )
61
+ unless (0...@line).to_a.include?(line-1)
62
+ raise CoordinateError, "Line out of range (#{line} for 1..#{@line})!"
63
+ end
64
+ unless (0...@column).to_a.include?(column-1)
65
+ raise CoordinateError, "Column out of range (#{column} for 1..#{@column})!"
66
+ end
67
+ @matrix[line-1][column-1] = val
68
+ end
69
+
70
+ def matrix
71
+ @matrix
72
+ end
73
+ alias :to_a :matrix
74
+
75
+ def columns
76
+ @column
77
+ end
78
+
79
+ def lines
80
+ @line
81
+ end
82
+
83
+ def to_s
84
+ size = bigger
85
+ out = ""
86
+ @line.times do |line|
87
+ out << "["
88
+ @column.times do |column|
89
+ out << sprintf(" %1$*2$s", @matrix[line][column].to_s, size)
90
+ end
91
+ out << "]\n"
92
+ end
93
+ return out
94
+ end
95
+
96
+ def -(m)
97
+ matrix = GraphViz::Math::Matrix.new( @line, @column )
98
+ @line.times do |line|
99
+ @column.times do |column|
100
+ matrix[line+1, column+1] = self[line+1, column+1] - m[line+1, column+1]
101
+ end
102
+ end
103
+ return matrix
104
+ end
105
+
106
+ def *(m)
107
+ matrix = GraphViz::Math::Matrix.new( @line, @line )
108
+
109
+ @line.times do |line|
110
+ @line.times do |column|
111
+ l = self.line(line+1)
112
+ c = m.column(column+1)
113
+ v = 0
114
+ l.size.times do |i|
115
+ v += l[i]*c[i]
116
+ end
117
+ matrix[line+1,column+1] = v
118
+ end
119
+ end
120
+
121
+ return matrix
122
+ end
123
+
124
+ def line( line )
125
+ unless (0...@line).to_a.include?(line-1)
126
+ raise CoordinateError, "Line out of range (#{line} for 1..#{@line})!"
127
+ end
128
+ @matrix[line-1]
129
+ end
130
+
131
+ def column( column )
132
+ col = []
133
+ unless (0...@column).to_a.include?(column-1)
134
+ raise CoordinateError, "Column out of range (#{column} for 1..#{@column})!"
135
+ end
136
+ @line.times do |line|
137
+ col << self[line+1, column]
138
+ end
139
+
140
+ return col
141
+ end
142
+
143
+ def transpose
144
+ matrix = GraphViz::Math::Matrix.new( @column, @line )
145
+ @line.times do |line|
146
+ @column.times do |column|
147
+ matrix[column+1, line+1] = self[line+1, column+1]
148
+ end
149
+ end
150
+ return matrix
151
+ end
152
+
153
+ def ==(m)
154
+ equal = true
155
+ @line.times do |line|
156
+ @column.times do |column|
157
+ equal &&= (m[line+1, column+1] == self[line+1, column+1])
158
+ end
159
+ end
160
+ return equal
161
+ end
162
+
163
+ def remove_line(n)
164
+ unless (0...@line).to_a.include?(n-1)
165
+ raise CoordinateError, "Line out of range (#{n} for 1..#{@line})!"
166
+ end
167
+
168
+ matrix = GraphViz::Math::Matrix.new( @line - 1, @column )
169
+ nline = 0
170
+ @line.times do |line|
171
+ next if line == n - 1
172
+ @column.times do |column|
173
+ matrix[nline+1, column+1] = self[line+1, column+1]
174
+ end
175
+ nline += 1
176
+ end
177
+ return matrix
178
+ end
179
+
180
+ def remove_column(n)
181
+ unless (0...@column).to_a.include?(n-1)
182
+ raise CoordinateError, "Column out of range (#{n} for 1..#{@column})!"
183
+ end
184
+
185
+ matrix = GraphViz::Math::Matrix.new( @line, @column - 1 )
186
+ @line.times do |line|
187
+ ncolumn = 0
188
+ @column.times do |column|
189
+ next if column == n - 1
190
+ matrix[line+1, ncolumn+1] = self[line+1, column+1]
191
+ ncolumn += 1
192
+ end
193
+ end
194
+ return matrix
195
+ end
196
+
197
+ def sum_of_column(n)
198
+ column(n).inject(0){|sum,item| sum + item}
199
+ end
200
+
201
+ def sum_of_line(n)
202
+ line(n).inject(0){|sum,item| sum + item}
203
+ end
204
+
205
+ def set_matrix(m) #:nodoc:
206
+ @matrix = m
207
+ end
208
+
209
+ private
210
+ def bigger
211
+ b = 0
212
+ @matrix.each do |line|
213
+ line.each do |column|
214
+ b = column.to_s.size if column.to_s.size > b
215
+ end
216
+ end
217
+ return b
218
+ end
219
+ end
220
+ end
221
+ end
data/lib/graphviz/node.rb CHANGED
@@ -35,6 +35,7 @@ class GraphViz
35
35
  @xNodeName = xNodeName
36
36
  @oGParrent = oGParrent
37
37
  @oAttrNode = GraphViz::Attrs::new( nil, "node", NODESATTRS )
38
+ @index = nil
38
39
  end
39
40
 
40
41
  #
@@ -51,6 +52,16 @@ class GraphViz
51
52
  def id
52
53
  @xNodeName.clone
53
54
  end
55
+
56
+ #
57
+ # Return the index of the node
58
+ #
59
+ def index
60
+ @index
61
+ end
62
+ def index=(i) #:nodoc:
63
+ @index = i if @index == nil
64
+ end
54
65
 
55
66
  #
56
67
  # Set value +xAttrValue+ to the node attribut +xAttrName+
@@ -0,0 +1,252 @@
1
+ require 'graphviz/math/matrix'
2
+
3
+ class GraphViz
4
+ class Theory
5
+ def initialize( graph )
6
+ @graph = graph
7
+ end
8
+
9
+ #
10
+ # Return the adjancy matrix of the graph
11
+ #
12
+ def adjancy_matrix
13
+ matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count )
14
+
15
+ @graph.each_edge { |e|
16
+ x = @graph.get_node(e.node_one( false )).index
17
+ y = @graph.get_node(e.node_two( false )).index
18
+ matrix[x+1, y+1] = 1
19
+ matrix[y+1, x+1] = 1 if @graph.type == "graph"
20
+ }
21
+
22
+ return matrix
23
+ end
24
+
25
+ #
26
+ # Return the incidence matrix of the graph
27
+ #
28
+ def incidence_matrix
29
+ tail = (@graph.type == "digraph") ? -1 : 1
30
+ matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.edge_count )
31
+
32
+ @graph.each_edge { |e|
33
+ x = e.index
34
+
35
+ nstart = @graph.get_node(e.node_one( false )).index
36
+ nend = @graph.get_node(e.node_two( false )).index
37
+
38
+ matrix[nstart+1, x+1] = 1
39
+ matrix[nend+1, x+1] = tail
40
+ matrix[nend+1, x+1] = 2 if nstart == nend
41
+ }
42
+
43
+ return matrix
44
+ end
45
+
46
+ #
47
+ # Return the degree of the given node
48
+ #
49
+ def degree( node )
50
+ degree = 0
51
+ name = node
52
+ if node.kind_of?(GraphViz::Node)
53
+ name = node.id
54
+ end
55
+
56
+ @graph.each_edge do |e|
57
+ degree += 1 if e.node_one(false) == name or e.node_two(false) == name
58
+ end
59
+
60
+ return degree
61
+ end
62
+
63
+ #
64
+ # Return the laplacian matrix of the graph
65
+ #
66
+ def laplacian_matrix
67
+ return degree_matrix - adjancy_matrix
68
+ end
69
+
70
+ #
71
+ # Return <tt>true</tt> if the graph if symmetric, <tt>false</tt> otherwise
72
+ #
73
+ def symmetric?
74
+ adjancy_matrix == adjancy_matrix.transpose
75
+ end
76
+
77
+ #
78
+ # moore_dijkstra(source, destination)
79
+ #
80
+ def moore_dijkstra( dep, arv )
81
+ dep = @graph.get_node(dep) unless dep.kind_of?(GraphViz::Node)
82
+ arv = @graph.get_node(arv) unless arv.kind_of?(GraphViz::Node)
83
+
84
+ m = distance_matrix
85
+ n = @graph.node_count
86
+ # Table des sommets à choisir
87
+ c = [dep.index]
88
+ # Table des distances
89
+ d = []
90
+ d[dep.index] = 0
91
+
92
+ # Table des predecesseurs
93
+ pred = []
94
+
95
+ @graph.each_node do |name, k|
96
+ if k != dep
97
+ d[k.index] = m[dep.index+1,k.index+1]
98
+ pred[k.index] = dep
99
+ end
100
+ end
101
+
102
+ while c.size < n
103
+ # trouver y tel que d[y] = min{d[k]; k sommet tel que k n'appartient pas à c}
104
+ mini = 1.0/0.0
105
+ y = nil
106
+ @graph.each_node do |name, k|
107
+ next if c.include?(k.index)
108
+ if d[k.index] < mini
109
+ mini = d[k.index]
110
+ y = k
111
+ end
112
+ end
113
+
114
+ # si ce minimum est ∞ alors sortir de la boucle fin si
115
+ break unless mini.to_f.infinite?.nil?
116
+
117
+ c << y.index
118
+ @graph.each_node do |name, k|
119
+ next if c.include?(k.index)
120
+ if d[k.index] > d[y.index] + m[y.index+1,k.index+1]
121
+ d[k.index] = d[y.index] + m[y.index+1,k.index+1]
122
+ pred[k.index] = y
123
+ end
124
+ end
125
+ end
126
+
127
+ # Construction du chemin le plus court
128
+ ch = []
129
+ k = arv
130
+ while k.index != dep.index
131
+ ch.unshift(k.id)
132
+ k = pred[k.index]
133
+ end
134
+ ch.unshift(dep.id)
135
+
136
+ if d[arv.index].to_f.infinite?
137
+ return nil
138
+ else
139
+ return( {
140
+ :path => ch,
141
+ :distance => d[arv.index]
142
+ } )
143
+ end
144
+ end
145
+
146
+ #
147
+ # Return a liste of range
148
+ #
149
+ # If the returned array include nil values, there is one or more circuits in the graph
150
+ #
151
+ def range
152
+ matrix = adjancy_matrix
153
+ unseen = (1..matrix.columns).to_a
154
+ result = Array.new(matrix.columns)
155
+ r = 0
156
+
157
+ range_recursion( matrix, unseen, result, r )
158
+ end
159
+
160
+ #
161
+ # Return the critical path for a PERT network
162
+ #
163
+ # If the given graph is not a PERT network, return nul
164
+ #
165
+ def critical_path
166
+ return nil if range.include?(nil) or @graph.type != "digraph"
167
+ r = [ [0, [1]] ]
168
+
169
+ critical_path_recursion( distance_matrix, adjancy_matrix, r, [], 0 ).inject( {:distance => 0, :path => []} ) { |r, item|
170
+ (r[:distance] < item[0]) ? { :distance => item[0], :path => item[1] } : r
171
+ }
172
+ end
173
+
174
+ private
175
+ def distance_matrix
176
+ type = @graph.type
177
+ matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count, (1.0/0.0) )
178
+
179
+ @graph.each_edge { |e|
180
+ x = @graph.get_node(e.node_one( false )).index
181
+ y = @graph.get_node(e.node_two( false )).index
182
+ unless x == y
183
+ weight = ((e[:weight].nil?) ? 1 : e[:weight].to_f)
184
+ matrix[x+1, y+1] = weight
185
+ matrix[y+1, x+1] = weight if type == "graph"
186
+ end
187
+ }
188
+
189
+ return matrix
190
+ end
191
+
192
+ def degree_matrix
193
+ matrix = GraphViz::Math::Matrix.new( @graph.node_count, @graph.node_count )
194
+ @graph.each_node do |name, node|
195
+ i = node.index
196
+ matrix[i+1, i+1] = degree(node)
197
+ end
198
+ return matrix
199
+ end
200
+
201
+ def range_recursion(matrix, unseen, result, r)
202
+ remove = []
203
+ matrix.columns.times do |c|
204
+ if matrix.sum_of_column(c+1) == 0
205
+ result[unseen[c]-1] = r
206
+ remove.unshift( c + 1 )
207
+ end
208
+ end
209
+
210
+ remove.each do |rem|
211
+ matrix = matrix.remove_line(rem).remove_column(rem)
212
+ unseen.delete_at(rem-1)
213
+ end
214
+
215
+ if matrix.columns == 1 and matrix.lines == 1
216
+ if matrix.sum_of_column(1) == 0
217
+ result[unseen[0]-1] = r+1
218
+ end
219
+ elsif remove.size > 0
220
+ range_recursion( matrix, unseen, result, r+1 )
221
+ end
222
+
223
+ return result
224
+ end
225
+
226
+ def index_of_item( array, item )
227
+ array.inject( [0,[]] ){|a,i|
228
+ a[1] << a[0] if i == item
229
+ a[0] += 1
230
+ a
231
+ }[1]
232
+ end
233
+
234
+ def critical_path_recursion( d, a, r, result, level )
235
+ r.each do |p|
236
+ node = p[1][-1]
237
+ index_of_item( a.line(node), 1 ).each do |c|
238
+ succ = c+1
239
+
240
+ cpath = [ (p[0] + d[node,succ]), (p[1].clone << succ) ]
241
+
242
+ if index_of_item( a.line(succ), 1 ).size > 0
243
+ capth = critical_path_recursion( d, a, [cpath], result, level+1 )
244
+ else
245
+ result << cpath
246
+ end
247
+ end
248
+ end
249
+ return result
250
+ end
251
+ end
252
+ end