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 true if the graph if symmetric, false 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.id)
k = pred[k.index]
end
ch.unshift(dep.id)
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
private
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