-
Notifications
You must be signed in to change notification settings - Fork 268
/
Copy pathgraph.py
167 lines (133 loc) · 4.82 KB
/
graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
"""Weighted undirected sparse graphs.
Original source:
https://github.com/marcharper/stationary/blob/master/stationary/utils/graph.py
"""
from collections import defaultdict
class Graph(object):
"""Weighted and directed graph class.
This class is intended for the graph associated to a Markov process,
since it gives easy access to the neighbors of a particular state.
Vertices can be any hashable Python object.
Initialize with a list of edges:
[[node1, node2, weights], ...]
Weights can be omitted for an undirected graph.
For efficiency, neighbors are cached in dictionaries. Undirected
graphs are implemented as directed graphs in which every edge (s, t)
has the opposite edge (t, s).
Attributes
----------
directed: Boolean indicating whether the graph is directed
original_edges: the edges passed into the initializer
out_mapping: a dictionary mapping all heads to dictionaries that map
all tails to their edge weights (None means no weight)
in_mapping: a dictionary mapping all tails to dictionaries that map
all heads to their edge weights (none means to weight)
Properties
----------
vertices: the set of vertices in the graph
edges: the set of current edges in the graph
"""
def __init__(self, edges=None, directed=False):
self.directed = directed
self.original_edges = edges
self.out_mapping = defaultdict(lambda: defaultdict(float))
self.in_mapping = defaultdict(lambda: defaultdict(float))
self._edges = []
if edges:
self._add_edges(edges)
def _add_edge(self, source, target, weight=None):
if (source, target) not in self._edges:
self._edges.append((source, target))
self.out_mapping[source][target] = weight
self.in_mapping[target][source] = weight
if (
not self.directed
and (source != target)
and (target, source) not in self._edges
):
self._edges.append((target, source))
self.out_mapping[target][source] = weight
self.in_mapping[source][target] = weight
def _add_edges(self, edges):
for edge in edges:
self._add_edge(*edge)
def add_loops(self):
"""
Add all loops to edges
"""
self._add_edges((x, x) for x in self.vertices)
@property
def edges(self):
return self._edges
@property
def vertices(self):
return list(self.out_mapping.keys())
def out_dict(self, source):
"""Returns a dictionary of the outgoing edges of source with weights."""
return self.out_mapping[source]
def out_vertices(self, source):
"""Returns a list of the outgoing vertices."""
return list(self.out_mapping[source].keys())
def in_dict(self, target):
"""Returns a dictionary of the incoming edges of source with weights."""
return self.in_mapping[target]
def in_vertices(self, source):
"""Returns a list of the outgoing vertices."""
return list(self.in_mapping[source].keys())
def __repr__(self):
s = "<Graph: {}>".format(repr(self.original_edges))
return s
# Example graph factories.
def cycle(length, directed=False):
"""Produces a cycle of a specified length.
Parameters
----------
length: int
Number of vertices in the cycle
directed: bool, False
Is the cycle directed?
Returns
-------
a Graph object for the cycle
"""
edges = [(i, i + 1) for i in range(length - 1)]
edges.append((length - 1, 0))
return Graph(edges=edges, directed=directed)
def complete_graph(size, loops=True, directed=False):
"""
Produces a complete graph of size `length`.
https://en.wikipedia.org/wiki/Complete_graph
Parameters
----------
size: int
Number of vertices in the cycle
loops: bool, True
attach loops at each node?
directed: bool, False
Is the graph directed?
Returns
-------
a Graph object for the complete graph
"""
edges = [(i, j) for i in range(size) for j in range(i + 1, size)]
graph = Graph(directed=directed, edges=edges)
if loops:
graph.add_loops()
return graph
def attached_complete_graphs(length, loops=True, directed=False):
"""Creates two complete undirected graphs of size `length`
attached by a single edge."""
edges = []
# Two complete graphs
for cluster in range(2):
for i in range(length):
for j in range(i + 1, length):
edges.append(
("{}:{}".format(cluster, i), "{}:{}".format(cluster, j))
)
# Attach at one node
edges.append(("0:0", "1:0"))
graph = Graph(directed=directed, edges=edges)
if loops:
graph.add_loops()
return graph