ruby-graphviz 0.9.16 → 0.9.17

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.
@@ -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