xref: /petsc/config/BuildSystem/graph.py (revision 179860b23afbef20daed3359c1645679d1efa988)
1*179860b2SJed Brownfrom __future__ import generators
2*179860b2SJed Brown
3*179860b2SJed Brownclass DirectedGraph(object):
4*179860b2SJed Brown  '''This class is for directed graphs with vertices of arbitrary type'''
5*179860b2SJed Brown  def __init__(self, vertices = []):
6*179860b2SJed Brown    '''Create a graph'''
7*179860b2SJed Brown    self.vertices = []
8*179860b2SJed Brown    self.inEdges  = {}
9*179860b2SJed Brown    self.outEdges = {}
10*179860b2SJed Brown    map(self.addVertex, vertices)
11*179860b2SJed Brown    return
12*179860b2SJed Brown
13*179860b2SJed Brown  def __len__(self):
14*179860b2SJed Brown    return len(self.vertices)
15*179860b2SJed Brown
16*179860b2SJed Brown  def __str__(self):
17*179860b2SJed Brown    return 'DirectedGraph with '+str(len(self.vertices))+' vertices and '+str(reduce(lambda k,l: k+l, [len(edgeList) for edgeList in self.inEdges.values()], 0))+' edges'
18*179860b2SJed Brown
19*179860b2SJed Brown  def addVertex(self, vertex):
20*179860b2SJed Brown    '''Add a vertex if it does not already exist in the vertex list
21*179860b2SJed Brown       - Should be able to use Set in Python 2.3'''
22*179860b2SJed Brown    if vertex is None: return
23*179860b2SJed Brown    if not vertex in self.vertices:
24*179860b2SJed Brown      self.vertices.append(vertex)
25*179860b2SJed Brown      self.clearEdges(vertex)
26*179860b2SJed Brown    return
27*179860b2SJed Brown
28*179860b2SJed Brown  def addEdges(self, vertex, inputs = [], outputs = []):
29*179860b2SJed Brown    '''Define the in and out edges for a vertex by listing the other vertices defining the edges
30*179860b2SJed Brown       - If any vertex does not exist in the graph, it is created'''
31*179860b2SJed Brown    self.addVertex(vertex)
32*179860b2SJed Brown    for input in inputs:
33*179860b2SJed Brown      self.addVertex(input)
34*179860b2SJed Brown      if not vertex is None and not input is None:
35*179860b2SJed Brown        if not input  in self.inEdges[vertex]: self.inEdges[vertex].append(input)
36*179860b2SJed Brown        if not vertex in self.outEdges[input]: self.outEdges[input].append(vertex)
37*179860b2SJed Brown    for output in outputs:
38*179860b2SJed Brown      self.addVertex(output)
39*179860b2SJed Brown      if not vertex is None and not output is None:
40*179860b2SJed Brown        if not vertex in self.inEdges[output]:  self.inEdges[output].append(vertex)
41*179860b2SJed Brown        if not output in self.outEdges[vertex]: self.outEdges[vertex].append(output)
42*179860b2SJed Brown    return
43*179860b2SJed Brown
44*179860b2SJed Brown  def getEdges(self, vertex):
45*179860b2SJed Brown    return (self.inEdges[vertex], self.outEdges[vertex])
46*179860b2SJed Brown
47*179860b2SJed Brown  def clearEdges(self, vertex, inOnly = 0, outOnly = 0):
48*179860b2SJed Brown    if inOnly and outOnly:
49*179860b2SJed Brown      raise RuntimeError('Inconsistent arguments')
50*179860b2SJed Brown    if not outOnly:
51*179860b2SJed Brown      self.inEdges[vertex]  = []
52*179860b2SJed Brown    if not inOnly:
53*179860b2SJed Brown      self.outEdges[vertex] = []
54*179860b2SJed Brown    return
55*179860b2SJed Brown
56*179860b2SJed Brown  def removeVertex(self, vertex):
57*179860b2SJed Brown    '''Remove a vertex if it already exists in the vertex list
58*179860b2SJed Brown       - Also removes all associated edges'''
59*179860b2SJed Brown    if vertex is None: return
60*179860b2SJed Brown    if vertex in self.vertices:
61*179860b2SJed Brown      self.vertices.remove(vertex)
62*179860b2SJed Brown      del self.inEdges[vertex]
63*179860b2SJed Brown      del self.outEdges[vertex]
64*179860b2SJed Brown      for v in self.vertices:
65*179860b2SJed Brown        if vertex in self.inEdges[v]:  self.inEdges[v].remove(vertex)
66*179860b2SJed Brown        if vertex in self.outEdges[v]: self.outEdges[v].remove(vertex)
67*179860b2SJed Brown    return
68*179860b2SJed Brown
69*179860b2SJed Brown  def replaceVertex(self, vertex, newVertex):
70*179860b2SJed Brown    '''Replace a vertex with newVertex if it already exists in the vertex list
71*179860b2SJed Brown       - Also transfers all associated edges'''
72*179860b2SJed Brown    if vertex is None or newVertex is None: return
73*179860b2SJed Brown    self.addEdges(newVertex, self.inEdges[vertex], self.outEdges[vertex])
74*179860b2SJed Brown    self.removeVertex(vertex)
75*179860b2SJed Brown    return
76*179860b2SJed Brown
77*179860b2SJed Brown  def addSubgraph(self, graph):
78*179860b2SJed Brown    '''Add the vertices and edges of another graph into this one'''
79*179860b2SJed Brown    map(self.addVertex, graph.vertices)
80*179860b2SJed Brown    map(lambda v: apply(self.addEdges, (v,)+graph.getEdges(v)), graph.vertices)
81*179860b2SJed Brown    return
82*179860b2SJed Brown
83*179860b2SJed Brown  def removeSubgraph(self, graph):
84*179860b2SJed Brown    '''Remove the vertices and edges of a subgraph, and all the edges connected to it'''
85*179860b2SJed Brown    map(self.removeVertex, graph.vertices)
86*179860b2SJed Brown    return
87*179860b2SJed Brown
88*179860b2SJed Brown  def printIndent(self, indent):
89*179860b2SJed Brown    import sys
90*179860b2SJed Brown    for i in range(indent): sys.stdout.write('  ')
91*179860b2SJed Brown
92*179860b2SJed Brown  def display(self):
93*179860b2SJed Brown    print 'I am a DirectedGraph with '+str(len(self.vertices))+' vertices'
94*179860b2SJed Brown    for vertex in DirectedGraph.breadthFirstSearch(self):
95*179860b2SJed Brown      self.printIndent(vertex.__level)
96*179860b2SJed Brown      print '('+str(self.vertices.index(vertex))+') '+str(vertex)+' in: '+str(map(self.vertices.index, self.inEdges[vertex]))+' out: '+str(map(self.vertices.index, self.outEdges[vertex]))
97*179860b2SJed Brown    return
98*179860b2SJed Brown
99*179860b2SJed Brown  def appendGraph(self, graph):
100*179860b2SJed Brown    '''Join every leaf of this graph to every root of the input graph, leaving the result in this graph'''
101*179860b2SJed Brown    leaves = DirectedGraph.getLeaves(self)
102*179860b2SJed Brown    self.addSubgraph(graph)
103*179860b2SJed Brown    map(lambda v: self.addEdges(v, outputs = DirectedGraph.getRoots(graph)), leaves)
104*179860b2SJed Brown    return self
105*179860b2SJed Brown
106*179860b2SJed Brown  def prependGraph(self, graph):
107*179860b2SJed Brown    '''Join every leaf of the input graph to every root of this graph, leaving the result in this graph'''
108*179860b2SJed Brown    roots = DirectedGraph.getRoots(self)
109*179860b2SJed Brown    self.addSubgraph(graph)
110*179860b2SJed Brown    map(lambda v: self.addEdges(v, outputs = roots), DirectedGraph.getLeaves(graph))
111*179860b2SJed Brown    return self
112*179860b2SJed Brown
113*179860b2SJed Brown  def getRoots(graph):
114*179860b2SJed Brown    '''Return all the sources in the graph (nodes without entering edges)'''
115*179860b2SJed Brown    return filter(lambda v: not len(graph.getEdges(v)[0]), graph.vertices)
116*179860b2SJed Brown  getRoots = staticmethod(getRoots)
117*179860b2SJed Brown
118*179860b2SJed Brown  def getLeaves(graph):
119*179860b2SJed Brown    '''Return all the sinks in the graph (nodes without exiting edges)'''
120*179860b2SJed Brown    return filter(lambda v: not len(graph.getEdges(v)[1]), graph.vertices)
121*179860b2SJed Brown  getLeaves = staticmethod(getLeaves)
122*179860b2SJed Brown
123*179860b2SJed Brown  def depthFirstVisit(graph, vertex, seen = None, returnFinished = 0, outEdges = 1):
124*179860b2SJed Brown    '''This is a generator returning vertices in a depth-first traversal only for the subtree rooted at vertex
125*179860b2SJed Brown       - If returnFinished is True, return a vertex when it finishes
126*179860b2SJed Brown       - Otherwise, return a vertex when it is first seen
127*179860b2SJed Brown       - If outEdges is True, proceed along these, otherwise use inEdges'''
128*179860b2SJed Brown    if seen is None: seen = []
129*179860b2SJed Brown    seen.append(vertex)
130*179860b2SJed Brown    if not returnFinished:
131*179860b2SJed Brown      yield vertex
132*179860b2SJed Brown    # Cute trick since outEdges is index 1, and inEdges is index 0
133*179860b2SJed Brown    for v in graph.getEdges(vertex)[outEdges]:
134*179860b2SJed Brown      if not v in seen:
135*179860b2SJed Brown        try:
136*179860b2SJed Brown          for v2 in DirectedGraph.depthFirstVisit(graph, v, seen, returnFinished, outEdges):
137*179860b2SJed Brown            yield v2
138*179860b2SJed Brown        except StopIteration:
139*179860b2SJed Brown          pass
140*179860b2SJed Brown    if returnFinished:
141*179860b2SJed Brown      yield vertex
142*179860b2SJed Brown    return
143*179860b2SJed Brown  depthFirstVisit = staticmethod(depthFirstVisit)
144*179860b2SJed Brown
145*179860b2SJed Brown  def depthFirstSearch(graph, returnFinished = 0, outEdges = 1):
146*179860b2SJed Brown    '''This is a generator returning vertices in a depth-first traversal
147*179860b2SJed Brown       - If returnFinished is True, return a vertex when it finishes
148*179860b2SJed Brown       - Otherwise, return a vertex when it is first seen
149*179860b2SJed Brown       - If outEdges is True, proceed along these, otherwise use inEdges'''
150*179860b2SJed Brown    seen = []
151*179860b2SJed Brown    for vertex in graph.vertices:
152*179860b2SJed Brown      if not vertex in seen:
153*179860b2SJed Brown        try:
154*179860b2SJed Brown          for v in DirectedGraph.depthFirstVisit(graph, vertex, seen, returnFinished, outEdges):
155*179860b2SJed Brown            yield v
156*179860b2SJed Brown        except StopIteration:
157*179860b2SJed Brown          pass
158*179860b2SJed Brown    return
159*179860b2SJed Brown  depthFirstSearch = staticmethod(depthFirstSearch)
160*179860b2SJed Brown
161*179860b2SJed Brown  def breadthFirstSearch(graph, returnFinished = 0):
162*179860b2SJed Brown    '''This is a generator returning vertices in a breadth-first traversal
163*179860b2SJed Brown       - If returnFinished is True, return a vertex when it finishes
164*179860b2SJed Brown       - Otherwise, return a vertex when it is first seen'''
165*179860b2SJed Brown    queue = DirectedGraph.getRoots(graph)[0:1]
166*179860b2SJed Brown    if not len(queue): return
167*179860b2SJed Brown    seen  = [queue[0]]
168*179860b2SJed Brown    if not returnFinished:
169*179860b2SJed Brown      queue[0].__level = 0
170*179860b2SJed Brown      yield queue[0]
171*179860b2SJed Brown    while len(queue):
172*179860b2SJed Brown      vertex = queue[-1]
173*179860b2SJed Brown      for v in graph.getEdges(vertex)[1]:
174*179860b2SJed Brown        if not v in seen:
175*179860b2SJed Brown          seen.append(v)
176*179860b2SJed Brown          v.__level = vertex.__level + 1
177*179860b2SJed Brown          queue.insert(0, v)
178*179860b2SJed Brown          if not returnFinished:
179*179860b2SJed Brown            yield v
180*179860b2SJed Brown      vertex = queue.pop()
181*179860b2SJed Brown      if returnFinished:
182*179860b2SJed Brown        yield vertex
183*179860b2SJed Brown    return
184*179860b2SJed Brown
185*179860b2SJed Brown  def topologicalSort(graph, start = None, outEdges = 1):
186*179860b2SJed Brown    '''Reorder the vertices using topological sort'''
187*179860b2SJed Brown    if start is None:
188*179860b2SJed Brown      vertices = [vertex for vertex in DirectedGraph.depthFirstSearch(graph, returnFinished = 1, outEdges = outEdges)]
189*179860b2SJed Brown    else:
190*179860b2SJed Brown      vertices = [vertex for vertex in DirectedGraph.depthFirstVisit(graph, start, returnFinished = 1, outEdges = outEdges)]
191*179860b2SJed Brown    vertices.reverse()
192*179860b2SJed Brown    for vertex in vertices:
193*179860b2SJed Brown      yield vertex
194*179860b2SJed Brown    return
195*179860b2SJed Brown  topologicalSort = staticmethod(topologicalSort)
196