Skip to content

Commit f32e14b

Browse files
langnerdrvinceknight
authored andcommitted
Sprice up graph and its unit tests
Major changes: - Change add_edge and add_edges to be internal since they're not used anywhere else - Turned edges and vertices into properties - Broke up unit tests into several classes - Grouped and deduped unit test cases for Graph - Added some coverage for untested methods (in_vertices, etc.)
1 parent f1287a8 commit f32e14b

File tree

3 files changed

+148
-176
lines changed

3 files changed

+148
-176
lines changed

axelrod/graph.py

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
"""
2-
Weighted undirected sparse graphs.
1+
"""Weighted undirected sparse graphs.
32
43
Original source:
54
https://github.com/marcharper/stationary/blob/master/stationary/utils/graph.py
@@ -9,18 +8,34 @@
98

109

1110
class Graph(object):
12-
"""Weighted and directed graph object intended for the graph associated to a
13-
Markov process. Gives easy access to the neighbors of a particular state
14-
needed for various calculations.
15-
16-
Vertices can be any hashable / immutable python object. Initialize with a
17-
list of edges:
11+
"""Weighted and directed graph class.
12+
13+
This class is intended for the graph associated to a Markov process,
14+
since it gives easy access to the neighbors of a particular state.
15+
16+
Vertices can be any hashable Python object.
17+
18+
Initialize with a list of edges:
1819
[[node1, node2, weights], ...]
1920
Weights can be omitted for an undirected graph.
2021
21-
For efficiency, neighbors are cached in dictionaries. Undirected graphs
22-
are implemented as directed graphs in which every edge (s, t) has the
23-
opposite edge (t, s).
22+
For efficiency, neighbors are cached in dictionaries. Undirected
23+
graphs are implemented as directed graphs in which every edge (s, t)
24+
has the opposite edge (t, s).
25+
26+
Attributes
27+
----------
28+
directed: Boolean indicating whether the graph is directed
29+
original_edges: the edges passed into the initializer
30+
out_mapping: a dictionary mapping all heads to dictionaries that map
31+
all tails to their edge weights (None means no weight)
32+
in_mapping: a dictionary mapping all tails to dictionaries that map
33+
all heads to their edge weights (none means to weight)
34+
35+
Properties
36+
----------
37+
vertices: the set of vertices in the graph
38+
edges: the set of current edges in the graph
2439
"""
2540

2641
def __init__(self, edges=None, directed=False):
@@ -30,9 +45,9 @@ def __init__(self, edges=None, directed=False):
3045
self.in_mapping = defaultdict(lambda: defaultdict(float))
3146
self._edges = []
3247
if edges:
33-
self.add_edges(edges)
48+
self._add_edges(edges)
3449

35-
def add_edge(self, source, target, weight=None):
50+
def _add_edge(self, source, target, weight=None):
3651
if (source, target) not in self._edges:
3752
self._edges.append((source, target))
3853
self.out_mapping[source][target] = weight
@@ -46,21 +61,20 @@ def add_edge(self, source, target, weight=None):
4661
self.out_mapping[target][source] = weight
4762
self.in_mapping[source][target] = weight
4863

49-
def add_edges(self, edges):
64+
def _add_edges(self, edges):
5065
for edge in edges:
51-
self.add_edge(*edge)
66+
self._add_edge(*edge)
5267

5368
def add_loops(self):
54-
"""
55-
Add all loops to edges.
56-
"""
57-
self.add_edges((x, x) for x in self.vertices())
69+
"""Add all loops to edges."""
70+
self._add_edges((x, x) for x in self.vertices)
5871

72+
@property
5973
def edges(self):
6074
return self._edges
6175

76+
@property
6277
def vertices(self):
63-
"""Returns the set of vertices of the graph."""
6478
return list(self.out_mapping.keys())
6579

6680
def out_dict(self, source):
@@ -84,53 +98,46 @@ def __repr__(self):
8498
return s
8599

86100

87-
# Example Graphs
101+
# Example graph factories.
88102

89103

90104
def cycle(length, directed=False):
91-
"""
92-
Produces a cycle of length `length`.
105+
"""Produces a cycle of a specified length.
106+
93107
Parameters
94108
----------
95109
length: int
96110
Number of vertices in the cycle
97111
directed: bool, False
98112
Is the cycle directed?
113+
99114
Returns
100115
-------
101-
a Graph object
116+
a Graph object for the cycle
102117
"""
103-
104-
graph = Graph(directed=directed)
105-
edges = []
106-
for i in range(length - 1):
107-
edges.append((i, i + 1))
118+
edges = [(i, i+1) for i in range(length-1)]
108119
edges.append((length - 1, 0))
109-
graph.add_edges(edges)
110-
return graph
120+
return Graph(edges=edges, directed=directed)
111121

112122

113-
def complete_graph(length, loops=True):
114-
"""
115-
Produces a complete graph of size `length`, with loops.
116-
https://en.wikipedia.org/wiki/Complete_graph
123+
def complete_graph(size, loops=True):
124+
""" Produces a complete graph of specificies size.
125+
126+
See https://en.wikipedia.org/wiki/Complete_graph for details.
117127
118128
Parameters
119129
----------
120-
length: int
130+
size: int
121131
Number of vertices in the cycle
122132
directed: bool, False
123133
Is the graph directed?
134+
124135
Returns
125136
-------
126-
a Graph object
137+
a Graph object for the complete graph
127138
"""
128-
graph = Graph(directed=False)
129-
edges = []
130-
for i in range(length):
131-
for j in range(i + 1, length):
132-
edges.append((i, j))
133-
graph.add_edges(edges)
139+
edges = [(i, j) for i in range(size) for j in range(i+1, size)]
140+
graph = Graph(edges=edges, directed=False)
134141

135142
if loops:
136143
graph.add_loops()

axelrod/moran.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,20 @@ def __init__(
144144
interaction_graph = complete_graph(len(players), loops=False)
145145
if reproduction_graph is None:
146146
reproduction_graph = Graph(
147-
interaction_graph.edges(), directed=interaction_graph.directed
147+
interaction_graph.edges, directed=interaction_graph.directed
148148
)
149149
reproduction_graph.add_loops()
150150
# Check equal vertices
151-
v1 = interaction_graph.vertices()
152-
v2 = reproduction_graph.vertices()
151+
v1 = interaction_graph.vertices
152+
v2 = reproduction_graph.vertices
153153
assert list(v1) == list(v2)
154154
self.interaction_graph = interaction_graph
155155
self.reproduction_graph = reproduction_graph
156156
self.fitness_transformation = fitness_transformation
157157
# Map players to graph vertices
158-
self.locations = sorted(interaction_graph.vertices())
158+
self.locations = sorted(interaction_graph.vertices)
159159
self.index = dict(
160-
zip(sorted(interaction_graph.vertices()), range(len(players)))
160+
zip(sorted(interaction_graph.vertices), range(len(players)))
161161
)
162162

163163
def set_players(self) -> None:

0 commit comments

Comments
 (0)