1616# --David Minor April 9, 2018 -from here on check git hub
1717# --https://github.com/dahvid/graphutil.git
1818
19+ #this breaks subgraphs from version 2
20+ version = '3.0.1'
21+
1922has_graphviz = True
2023try :
2124 import graphviz as pgv
@@ -253,6 +256,11 @@ def clear(self):
253256 def set_attribute (self , name , value ):
254257 self .graph_attributes [name ] = value
255258
259+ def add_attributes (self , key_values ):
260+ for k ,v in key_values .items ():
261+ self .graph_attributes [k ] = v
262+
263+
256264 def get_attribute (self , name ):
257265 return self .graph_attributes .get (name )
258266
@@ -288,11 +296,30 @@ def substitute(self, node, g):
288296 self .ccs_dirty = True
289297 self .topo_dirty = True
290298
299+ """
300+ This inserts a node after the from_node, interrupting and re-routing all outputs
301+ through the after_node. attributes will be preserved until from_attr or to_attr are given
302+ assumes the after_node is already added to the graph
303+ """
304+ def insert_node_after (self ,from_node , after_node , from_attr = None , to_attr = None ):
305+ from_arcs = self .out_arcs (from_node )
306+ if from_arcs :
307+ for f in from_arcs :
308+ to_node = self .tail (f )
309+ to_data = to_attr if to_attr else self .edge_data (f )[2 ]
310+ from_data = from_attr if from_attr else self .edge_data (f )[2 ]
311+ self .delete_edge (f )
312+ #duplicate insertion means nothing here
313+ self .add_edge (from_node ,after_node ,edge_data = from_data , no_except = True )
314+ self .add_edge (after_node , to_node , edge_data = to_data )
315+ else : #is leaf node, create an edge from scratch
316+ self .add_edge (from_node ,after_node ,edge_data = from_attr )
317+
291318
292319
293320
294321 #TODO this should use a dfs for effeciency, instead of duplicating edges
295- def induce (self , nodes , label = None ):
322+ def induce (self , nodes , label = None , attributes = None ):
296323 """
297324 creates an induced graph from the passed in set of tasks
298325 :return graph, dangling out edges, dangling in edges
@@ -321,6 +348,8 @@ def induce(self, nodes, label=None):
321348 dangling_out_edges += [(head ,tail ,data )]
322349 for (head ,tail ),data in edges .items ():
323350 g .add_edge (head ,tail ,data )
351+ if attributes :
352+ g .add_attributes (attributes )
324353 return (g , dangling_in_edges , dangling_out_edges )
325354
326355
@@ -545,6 +574,10 @@ def add_edge(self, head_id, tail_id, edge_data=None, no_except=False):
545574 self .ccs_dirty = True
546575 return edge_id
547576
577+ def set_edge_data (self ,edge_id , data ):
578+ ed = self .edge_data (edge_id )
579+ self .edges [edge_id ] = (ed [0 ],ed [1 ],data )
580+
548581
549582 # --Removes the edge from the normal graph, but does not delete
550583 # --its information. The edge is held in a separate structure
@@ -655,6 +688,9 @@ def get_edges(self, head_id, tail_id):
655688 return edges
656689
657690
691+ def get_edge_attributes (self ,edge_id ):
692+ d = self .edge_data (edge_id )
693+ return d [2 ]
658694 # print "WARNING: No edge to return."
659695
660696 def number_of_nodes (self ):
@@ -740,6 +776,21 @@ def set_node_data(self, node_id, data):
740776 def edge_data (self , edge_id ):
741777 return self .edges [edge_id ]
742778
779+ def is_linear (self ):
780+ return not self .has_splits () and not self .has_joins ()
781+
782+ # --Returns true if graph has any splits
783+ def has_splits (self ):
784+ for n in self .node_list ():
785+ if len (self .out_arcs (n )) > 1 :
786+ return True
787+ return False
788+
789+ def has_joins (self ):
790+ for n in self .node_list ():
791+ if len (self .in_arcs (n )) > 1 :
792+ return True
793+ return False
743794
744795 # --Returns a reference to the head of the edge. (A reference to the head id)
745796 def head (self , edge ):
@@ -766,7 +817,7 @@ def tail_data(self, edge):
766817 def out_arcs (self , node_id ):
767818 if node_id not in self .nodes .keys ():
768819 return []
769- return self .nodes [node_id ][1 ]
820+ return copy . copy ( self .nodes [node_id ][1 ])
770821
771822
772823 # --Returns a copy of the list of edge data of the node's out arcs.
@@ -778,7 +829,7 @@ def out_arcs_data(self, node_id):
778829 def in_arcs (self , node_id ):
779830 if node_id not in self .nodes .keys ():
780831 return []
781- return self .nodes [node_id ][0 ]
832+ return copy . copy ( self .nodes [node_id ][0 ])
782833
783834
784835 # --Returns list of adjacent nodes on input arcs
@@ -800,8 +851,6 @@ def out_adjacency_list(self):
800851 return dict ([(node , self .out_adjacent (node )) for node in self .nodes ])
801852
802853
803- # return { node : self.out_adjacent(node) for node in self.nodes}
804-
805854 # --Similar to above.
806855 def in_arcs_data (self , node_id ):
807856 return [self .edge_data (edge_id ) for edge_id in self .in_arcs (node_id )]
@@ -833,7 +882,7 @@ def degree(self, node_id):
833882
834883 # --merges graph into this graph
835884 # --overwriting any shared nodes
836- def merge (self , graph ):
885+ def merge (self , graph , remove_old_edges = False ):
837886 for node ,data in graph .node_dict ().items ():
838887 if node in self .node_list ():
839888 self .set_node_data (node ,data )
@@ -843,6 +892,13 @@ def merge(self, graph):
843892 for edge ,data in graph .edge_dict ().items ():
844893 if edge not in self .edge_dict ():
845894 self .add_edge (edge [0 ],edge [1 ],data [2 ])
895+ if remove_old_edges :
896+ for edge , data in self .edge_dict ().items ():
897+ if edge [0 ] in graph .node_list () and (edge [1 ] in graph .node_list ()):
898+ if edge not in graph .edge_dict ():
899+ edges = self .get_edges (edge [0 ],edge [1 ])
900+ for e in edges :
901+ self .delete_edge (e )
846902
847903 # location of each node in topo list
848904 def make_topo_node_finder (self ):
@@ -968,9 +1024,6 @@ def connected_components(self):
9681024 else :
9691025 return self .ccs
9701026
971-
972-
973-
9741027 # --Tells dfs to stop searching this branch and go on to the next one
9751028 class SkipBranch (Exception ):
9761029 pass
@@ -1004,6 +1057,25 @@ def dfs(self, source_id, visitor=None, repeat=False):
10041057 nodes_already_stacked [self .tail (edge )] = 0
10051058 dfs_stack .push (self .tail (edge ))
10061059 return dfs_list
1060+ """
1061+ Combindes dfs with topo, will do dfs but insure topo order followed
1062+ """
1063+ def dfs_topo_sort (self ):
1064+ visited = { n : False for n in self .node_list ()}
1065+ result = []
1066+ def DFS (node ):
1067+ if visited [node ]:
1068+ return
1069+ visited [node ] = True
1070+ for adj in self .out_adjacent (node ):
1071+ DFS (adj )
1072+ result .append (node )
1073+
1074+ for i in self .topological_sort ():
1075+ DFS (i )
1076+
1077+ result .reverse ()
1078+ return result
10071079
10081080
10091081 class Counter :
@@ -1286,3 +1358,4 @@ def robust_topological_sort(self):
12861358 sorted_components = robust_topological_sort (graph )
12871359 # print 'robust topo sort', sorted_components
12881360 return sorted_components
1361+
0 commit comments