xref: /petsc/config/BuildSystem/graph.py (revision e76427aca198df9024375cd075a5aadd1d1b949a)
1179860b2SJed Brownfrom __future__ import generators
2179860b2SJed Brown
3179860b2SJed Brownclass DirectedGraph(object):
4179860b2SJed Brown  '''This class is for directed graphs with vertices of arbitrary type'''
5179860b2SJed Brown  def __init__(self, vertices = []):
6179860b2SJed Brown    '''Create a graph'''
7179860b2SJed Brown    self.vertices = []
8179860b2SJed Brown    self.inEdges  = {}
9179860b2SJed Brown    self.outEdges = {}
10179860b2SJed Brown    map(self.addVertex, vertices)
11179860b2SJed Brown    return
12179860b2SJed Brown
13179860b2SJed Brown  def __len__(self):
14179860b2SJed Brown    return len(self.vertices)
15179860b2SJed Brown
16179860b2SJed Brown  def __str__(self):
17179860b2SJed 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'
18179860b2SJed Brown
19179860b2SJed Brown  def addVertex(self, vertex):
20179860b2SJed Brown    '''Add a vertex if it does not already exist in the vertex list
21179860b2SJed Brown       - Should be able to use Set in Python 2.3'''
22179860b2SJed Brown    if vertex is None: return
23179860b2SJed Brown    if not vertex in self.vertices:
24179860b2SJed Brown      self.vertices.append(vertex)
25179860b2SJed Brown      self.clearEdges(vertex)
26179860b2SJed Brown    return
27179860b2SJed Brown
28179860b2SJed Brown  def addEdges(self, vertex, inputs = [], outputs = []):
29179860b2SJed Brown    '''Define the in and out edges for a vertex by listing the other vertices defining the edges
30179860b2SJed Brown       - If any vertex does not exist in the graph, it is created'''
31179860b2SJed Brown    self.addVertex(vertex)
32179860b2SJed Brown    for input in inputs:
33179860b2SJed Brown      self.addVertex(input)
34179860b2SJed Brown      if not vertex is None and not input is None:
35179860b2SJed Brown        if not input  in self.inEdges[vertex]: self.inEdges[vertex].append(input)
36179860b2SJed Brown        if not vertex in self.outEdges[input]: self.outEdges[input].append(vertex)
37179860b2SJed Brown    for output in outputs:
38179860b2SJed Brown      self.addVertex(output)
39179860b2SJed Brown      if not vertex is None and not output is None:
40179860b2SJed Brown        if not vertex in self.inEdges[output]:  self.inEdges[output].append(vertex)
41179860b2SJed Brown        if not output in self.outEdges[vertex]: self.outEdges[vertex].append(output)
42179860b2SJed Brown    return
43179860b2SJed Brown
44179860b2SJed Brown  def getEdges(self, vertex):
45179860b2SJed Brown    return (self.inEdges[vertex], self.outEdges[vertex])
46179860b2SJed Brown
47179860b2SJed Brown  def clearEdges(self, vertex, inOnly = 0, outOnly = 0):
48179860b2SJed Brown    if inOnly and outOnly:
49179860b2SJed Brown      raise RuntimeError('Inconsistent arguments')
50179860b2SJed Brown    if not outOnly:
51179860b2SJed Brown      self.inEdges[vertex]  = []
52179860b2SJed Brown    if not inOnly:
53179860b2SJed Brown      self.outEdges[vertex] = []
54179860b2SJed Brown    return
55179860b2SJed Brown
56179860b2SJed Brown  def removeVertex(self, vertex):
57179860b2SJed Brown    '''Remove a vertex if it already exists in the vertex list
58179860b2SJed Brown       - Also removes all associated edges'''
59179860b2SJed Brown    if vertex is None: return
60179860b2SJed Brown    if vertex in self.vertices:
61179860b2SJed Brown      self.vertices.remove(vertex)
62179860b2SJed Brown      del self.inEdges[vertex]
63179860b2SJed Brown      del self.outEdges[vertex]
64179860b2SJed Brown      for v in self.vertices:
65179860b2SJed Brown        if vertex in self.inEdges[v]:  self.inEdges[v].remove(vertex)
66179860b2SJed Brown        if vertex in self.outEdges[v]: self.outEdges[v].remove(vertex)
67179860b2SJed Brown    return
68179860b2SJed Brown
69179860b2SJed Brown  def replaceVertex(self, vertex, newVertex):
70179860b2SJed Brown    '''Replace a vertex with newVertex if it already exists in the vertex list
71179860b2SJed Brown       - Also transfers all associated edges'''
72179860b2SJed Brown    if vertex is None or newVertex is None: return
73179860b2SJed Brown    self.addEdges(newVertex, self.inEdges[vertex], self.outEdges[vertex])
74179860b2SJed Brown    self.removeVertex(vertex)
75179860b2SJed Brown    return
76179860b2SJed Brown
77179860b2SJed Brown  def addSubgraph(self, graph):
78179860b2SJed Brown    '''Add the vertices and edges of another graph into this one'''
79179860b2SJed Brown    map(self.addVertex, graph.vertices)
80*e76427acSJed Brown    map(lambda v: self.addEdges(v, *graph.getEdges(v)), graph.vertices)
81179860b2SJed Brown    return
82179860b2SJed Brown
83179860b2SJed Brown  def removeSubgraph(self, graph):
84179860b2SJed Brown    '''Remove the vertices and edges of a subgraph, and all the edges connected to it'''
85179860b2SJed Brown    map(self.removeVertex, graph.vertices)
86179860b2SJed Brown    return
87179860b2SJed Brown
88179860b2SJed Brown  def printIndent(self, indent):
89179860b2SJed Brown    import sys
90179860b2SJed Brown    for i in range(indent): sys.stdout.write('  ')
91179860b2SJed Brown
92179860b2SJed Brown  def display(self):
93179860b2SJed Brown    print 'I am a DirectedGraph with '+str(len(self.vertices))+' vertices'
94179860b2SJed Brown    for vertex in DirectedGraph.breadthFirstSearch(self):
95179860b2SJed Brown      self.printIndent(vertex.__level)
96179860b2SJed 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]))
97179860b2SJed Brown    return
98179860b2SJed Brown
99179860b2SJed Brown  def appendGraph(self, graph):
100179860b2SJed Brown    '''Join every leaf of this graph to every root of the input graph, leaving the result in this graph'''
101179860b2SJed Brown    leaves = DirectedGraph.getLeaves(self)
102179860b2SJed Brown    self.addSubgraph(graph)
103179860b2SJed Brown    map(lambda v: self.addEdges(v, outputs = DirectedGraph.getRoots(graph)), leaves)
104179860b2SJed Brown    return self
105179860b2SJed Brown
106179860b2SJed Brown  def prependGraph(self, graph):
107179860b2SJed Brown    '''Join every leaf of the input graph to every root of this graph, leaving the result in this graph'''
108179860b2SJed Brown    roots = DirectedGraph.getRoots(self)
109179860b2SJed Brown    self.addSubgraph(graph)
110179860b2SJed Brown    map(lambda v: self.addEdges(v, outputs = roots), DirectedGraph.getLeaves(graph))
111179860b2SJed Brown    return self
112179860b2SJed Brown
113179860b2SJed Brown  def getRoots(graph):
114179860b2SJed Brown    '''Return all the sources in the graph (nodes without entering edges)'''
115179860b2SJed Brown    return filter(lambda v: not len(graph.getEdges(v)[0]), graph.vertices)
116179860b2SJed Brown  getRoots = staticmethod(getRoots)
117179860b2SJed Brown
118179860b2SJed Brown  def getLeaves(graph):
119179860b2SJed Brown    '''Return all the sinks in the graph (nodes without exiting edges)'''
120179860b2SJed Brown    return filter(lambda v: not len(graph.getEdges(v)[1]), graph.vertices)
121179860b2SJed Brown  getLeaves = staticmethod(getLeaves)
122179860b2SJed Brown
123179860b2SJed Brown  def depthFirstVisit(graph, vertex, seen = None, returnFinished = 0, outEdges = 1):
124179860b2SJed Brown    '''This is a generator returning vertices in a depth-first traversal only for the subtree rooted at vertex
125179860b2SJed Brown       - If returnFinished is True, return a vertex when it finishes
126179860b2SJed Brown       - Otherwise, return a vertex when it is first seen
127179860b2SJed Brown       - If outEdges is True, proceed along these, otherwise use inEdges'''
128179860b2SJed Brown    if seen is None: seen = []
129179860b2SJed Brown    seen.append(vertex)
130179860b2SJed Brown    if not returnFinished:
131179860b2SJed Brown      yield vertex
132179860b2SJed Brown    # Cute trick since outEdges is index 1, and inEdges is index 0
133179860b2SJed Brown    for v in graph.getEdges(vertex)[outEdges]:
134179860b2SJed Brown      if not v in seen:
135179860b2SJed Brown        try:
136179860b2SJed Brown          for v2 in DirectedGraph.depthFirstVisit(graph, v, seen, returnFinished, outEdges):
137179860b2SJed Brown            yield v2
138179860b2SJed Brown        except StopIteration:
139179860b2SJed Brown          pass
140179860b2SJed Brown    if returnFinished:
141179860b2SJed Brown      yield vertex
142179860b2SJed Brown    return
143179860b2SJed Brown  depthFirstVisit = staticmethod(depthFirstVisit)
144179860b2SJed Brown
145179860b2SJed Brown  def depthFirstSearch(graph, returnFinished = 0, outEdges = 1):
146179860b2SJed Brown    '''This is a generator returning vertices in a depth-first traversal
147179860b2SJed Brown       - If returnFinished is True, return a vertex when it finishes
148179860b2SJed Brown       - Otherwise, return a vertex when it is first seen
149179860b2SJed Brown       - If outEdges is True, proceed along these, otherwise use inEdges'''
150179860b2SJed Brown    seen = []
151179860b2SJed Brown    for vertex in graph.vertices:
152179860b2SJed Brown      if not vertex in seen:
153179860b2SJed Brown        try:
154179860b2SJed Brown          for v in DirectedGraph.depthFirstVisit(graph, vertex, seen, returnFinished, outEdges):
155179860b2SJed Brown            yield v
156179860b2SJed Brown        except StopIteration:
157179860b2SJed Brown          pass
158179860b2SJed Brown    return
159179860b2SJed Brown  depthFirstSearch = staticmethod(depthFirstSearch)
160179860b2SJed Brown
161179860b2SJed Brown  def breadthFirstSearch(graph, returnFinished = 0):
162179860b2SJed Brown    '''This is a generator returning vertices in a breadth-first traversal
163179860b2SJed Brown       - If returnFinished is True, return a vertex when it finishes
164179860b2SJed Brown       - Otherwise, return a vertex when it is first seen'''
165179860b2SJed Brown    queue = DirectedGraph.getRoots(graph)[0:1]
166179860b2SJed Brown    if not len(queue): return
167179860b2SJed Brown    seen  = [queue[0]]
168179860b2SJed Brown    if not returnFinished:
169179860b2SJed Brown      queue[0].__level = 0
170179860b2SJed Brown      yield queue[0]
171179860b2SJed Brown    while len(queue):
172179860b2SJed Brown      vertex = queue[-1]
173179860b2SJed Brown      for v in graph.getEdges(vertex)[1]:
174179860b2SJed Brown        if not v in seen:
175179860b2SJed Brown          seen.append(v)
176179860b2SJed Brown          v.__level = vertex.__level + 1
177179860b2SJed Brown          queue.insert(0, v)
178179860b2SJed Brown          if not returnFinished:
179179860b2SJed Brown            yield v
180179860b2SJed Brown      vertex = queue.pop()
181179860b2SJed Brown      if returnFinished:
182179860b2SJed Brown        yield vertex
183179860b2SJed Brown    return
184179860b2SJed Brown
185179860b2SJed Brown  def topologicalSort(graph, start = None, outEdges = 1):
186179860b2SJed Brown    '''Reorder the vertices using topological sort'''
187179860b2SJed Brown    if start is None:
188179860b2SJed Brown      vertices = [vertex for vertex in DirectedGraph.depthFirstSearch(graph, returnFinished = 1, outEdges = outEdges)]
189179860b2SJed Brown    else:
190179860b2SJed Brown      vertices = [vertex for vertex in DirectedGraph.depthFirstVisit(graph, start, returnFinished = 1, outEdges = outEdges)]
191179860b2SJed Brown    vertices.reverse()
192179860b2SJed Brown    for vertex in vertices:
193179860b2SJed Brown      yield vertex
194179860b2SJed Brown    return
195179860b2SJed Brown  topologicalSort = staticmethod(topologicalSort)
196