def edge_style(self, head, tail, **kwargs): ''' Modifies an edge style to the dot representation. ''' if tail not in self.nodes: raise GraphError("invalid node %s" % (tail, )) try: if tail not in self.edges[head]: self.edges[head][tail] = {} self.edges[head][tail] = kwargs except KeyError: raise GraphError("invalid edge %s -> %s " % (head, tail))
def add_edge(self, head_id, tail_id, edge_data=1, create_nodes=True): """ Adds a directed edge going from head_id to tail_id. Arbitrary data can be attached to the edge via edge_data. It may create the nodes if adding edges between nonexisting ones. :param head_id: head node :param tail_id: tail node :param edge_data: (optional) data attached to the edge :param create_nodes: (optional) creates the head_id or tail_id node in case they did not exist """ # shorcut edge = self.next_edge # add nodes if on automatic node creation if create_nodes: self.add_node(head_id) self.add_node(tail_id) # update the corresponding incoming and outgoing lists in the nodes # index 0 -> incoming edges # index 1 -> outgoing edges try: self.nodes[tail_id][0].append(edge) self.nodes[head_id][1].append(edge) except KeyError: raise GraphError('Invalid nodes %s -> %s' % (head_id, tail_id)) # store edge information self.edges[edge] = (head_id, tail_id, edge_data) self.next_edge += 1
def inc_edges(self, node): """ Returns a list of the incoming edges """ try: return list(self.nodes[node][0]) except KeyError: raise GraphError('Invalid node %s' % node)
def out_edges(self, node): """ Returns a list of the outgoing edges """ try: return list(self.nodes[node][1]) except KeyError: raise GraphError('Invalid node %s' % node)
def edge_by_id(self, edge): """ Returns the edge that connects the head_id and tail_id nodes """ try: head, tail, data = self.edges[edge] except KeyError: head, tail = None, None raise GraphError('Invalid edge %s' % edge) return (head, tail)
def restore_edge(self, edge): """ Restores a previously hidden edge back into the graph. """ try: self.edges[edge] = head_id, tail_id, data = self.hidden_edges[edge] self.nodes[tail_id][0].append(edge) self.nodes[head_id][1].append(edge) del self.hidden_edges[edge] except KeyError: raise GraphError('Invalid edge %s' % edge)
def restore_node(self, node): """ Restores a previously hidden node back into the graph and restores all of its incoming and outgoing edges. """ try: self.nodes[node], all_edges = self.hidden_nodes[node] for edge in all_edges: self.restore_edge(edge) del self.hidden_nodes[node] except KeyError: raise GraphError('Invalid node %s' % node)
def hide_edge(self, edge): """ Hides an edge from the graph. The edge may be unhidden at some later time. """ try: head_id, tail_id, edge_data = self.hidden_edges[edge] = self.edges[edge] self.nodes[tail_id][0].remove(edge) self.nodes[head_id][1].remove(edge) del self.edges[edge] except KeyError: raise GraphError('Invalid edge %s' % edge)
def hide_node(self, node): """ Hides a node from the graph. The incoming and outgoing edges of the node will also be hidden. The node may be unhidden at some later time. """ try: all_edges = self.all_edges(node) self.hidden_nodes[node] = (self.nodes[node], all_edges) for edge in all_edges: self.hide_edge(edge) del self.nodes[node] except KeyError: raise GraphError('Invalid node %s' % node)
def dijkstra(graph, start, end=None): """ Dijkstra's algorithm for shortest paths `David Eppstein, UC Irvine, 4 April 2002 <http://www.ics.uci.edu/~eppstein/161/python/>`_ `Python Cookbook Recipe <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/119466>`_ Find shortest paths from the start node to all nodes nearer than or equal to the end node. Dijkstra's algorithm is only guaranteed to work correctly when all edge lengths are positive. This code does not verify this property for all edges (only the edges examined until the end vertex is reached), but will correctly compute shortest paths even for some graphs with negative edges, and will raise an exception if it discovers that a negative edge has caused it to make a mistake. Adapted to altgraph by Istvan Albert, Pennsylvania State University - June, 9 2004 """ D = {} # dictionary of final distances P = {} # dictionary of predecessors Q = _priorityDictionary() # estimated distances of non-final vertices Q[start] = 0 for v in Q: D[v] = Q[v] if v == end: break for w in graph.out_nbrs(v): edge_id = graph.edge_by_node(v, w) vwLength = D[v] + graph.edge_data(edge_id) if w in D: if vwLength < D[w]: raise GraphError( "Dijkstra: found better path to already-final vertex") elif w not in Q or vwLength < Q[w]: Q[w] = vwLength P[w] = v return (D, P)
def __init__(self, edges=None): """ Initialization """ self.next_edge = 0 self.nodes, self.edges = {}, {} self.hidden_edges, self.hidden_nodes = {}, {} if edges is not None: for item in edges: if len(item) == 2: head, tail = item self.add_edge(head, tail) elif len(item) == 3: head, tail, data = item self.add_edge(head, tail, data) else: raise GraphError("Cannot create edge from %s" % (item, ))
def generate_random_graph(node_num, edge_num, self_loops=False, multi_edges=False): ''' Generates and returns a :py:class:`~altgraph.Graph.Graph` instance with *node_num* nodes randomly connected by *edge_num* edges. ''' g = Graph.Graph() if not multi_edges: if self_loops: max_edges = node_num * node_num else: max_edges = node_num * (node_num - 1) if edge_num > max_edges: raise GraphError( "inconsistent arguments to 'generate_random_graph'") nodes = range(node_num) for node in nodes: g.add_node(node) while 1: head = random.choice(nodes) tail = random.choice(nodes) # loop defense if head == tail and not self_loops: continue # multiple edge defense if g.edge_by_node(head, tail) is not None and not multi_edges: continue # add the edge g.add_edge(head, tail) if g.number_of_edges() >= edge_num: break return g
def iterdot(self): # write graph title if self.type == 'digraph': yield 'digraph %s {\n' % (self.name, ) elif self.type == 'graph': yield 'graph %s {\n' % (self.name, ) else: raise GraphError("unsupported graphtype %s" % (self.type, )) # write overall graph attributes for attr_name, attr_value in self.attr.iteritems(): yield '%s="%s";' % (attr_name, attr_value) yield '\n' # some reusable patterns cpatt = '%s="%s",' # to separate attributes epatt = '];\n' # to end attributes # write node attributes for node_name, node_attr in self.nodes.iteritems(): yield '\t"%s" [' % (node_name, ) for attr_name, attr_value in node_attr.iteritems(): yield cpatt % (attr_name, attr_value) yield epatt # write edge attributes for head in self.edges: for tail in self.edges[head]: if self.type == 'digraph': yield '\t"%s" -> "%s" [' % (head, tail) else: yield '\t"%s" -- "%s" [' % (head, tail) for attr_name, attr_value in self.edges[head][tail].iteritems( ): yield cpatt % (attr_name, attr_value) yield epatt # finish file yield '}\n'
def __init__(self, edges=None): """ Initialization """ self.next_edge = 0 self.nodes, self.edges = {}, {} self.hidden_edges, self.hidden_nodes = {}, {} try: # instantiate graph from iterable data if edges: cols = len(edges[0]) if cols == 2: for head, tail in edges: self.add_edge(head, tail) elif cols == 3: for head, tail, data in edges: self.add_edge(head, tail, data) except Exception, exc: raise GraphError('%s -> Cannot create graph from edges=%s' % (exc, edges))
def dijkstra(self, graph, start, end=None, usedByYenKSP=False, car_speed=0, usedByScheduler=False): """ Dijkstra's algorithm for shortest paths `David Eppstein, UC Irvine, 4 April 2002 <http://www.ics.uci.edu/~eppstein/161/python/>`_ `Python Cookbook Recipe <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/119466>`_ Find shortest paths from the start node to all nodes nearer than or equal to the end node. Dijkstra's algorithm is only guaranteed to work correctly when all edge lengths are positive. This code does not verify this property for all edges (only the edges examined until the end vertex is reached), but will correctly compute shortest paths even for some graphs with negative edges, and will raise an exception if it discovers that a negative edge has caused it to make a mistake. Adapted to altgraph by Istvan Albert, Pennsylvania State University - June, 9 2004 """ D = {} # dictionary of final distances P = {} # dictionary of predecessors Q = self._priorityDictionary() # estimated distances of non-final vertices Q[start] = 0 for v in Q: D[v] = Q[v] if v == end: break # graph.out_nbrs(v): Return a list of all nodes connected by outgoing edges. for w in graph.out_nbrs(v): edge_id = graph.edge_by_node(v, w) # edge_weight = road_length/speed_limit/channel_number # max_speed = min(car_speed, graph.edge_data(edge_id)[2]) # vwLength = D[v] + graph.edge_data(edge_id)[1] / max_speed / graph.edge_data(edge_id)[3] # vwLength = D[v] + graph.edge_data(edge_id)[1] / graph.edge_data(edge_id)[2] / graph.edge_data(edge_id)[3] vwLength = D[v] + graph.edge_data(edge_id)[1] / graph.edge_data(edge_id)[3] # vwLength = D[v] + graph.edge_data(edge_id) if usedByScheduler: (id, length, speed, channel, num) = graph.edge_data(edge_id) vwLength = D[v] + (num + length) / channel # vwLength = D[v] + graph.edge_data(edge_id) if w in D: if vwLength < D[w]: raise GraphError( "Dijkstra: found better path to already-final vertex") elif w not in Q or vwLength < Q[w]: Q[w] = vwLength P[w] = v if usedByYenKSP: if end: if end not in graph.forw_bfs(start): # can not reach end_node from start_node return {'cost': D[start], 'path': []} return {'cost': D[end], 'path': self.path(P, start, end)} else: return (D, P) else: return (D, P)