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