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) 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 critical_path_recursion( d, a, [cpath], result, level+1 ) else result << cpath end end end return result end end end