Skip to content

Commit 67856f9

Browse files
authored
Merge pull request #799 from Axelrod-Python/graph_moran
Implemented the Moran process on graphs
2 parents dc4928e + 496fe41 commit 67856f9

File tree

7 files changed

+664
-47
lines changed

7 files changed

+664
-47
lines changed

axelrod/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
# The order of imports matters!
77
from .version import __version__
8+
from . import graph
89
from .actions import Actions, flip_action
910
from .random_ import random_choice, seed
1011
from .plot import Plot
@@ -13,7 +14,7 @@
1314
obey_axelrod, update_history, update_state_distribution, Player
1415
from .mock_player import MockPlayer, simulate_play
1516
from .match import Match
16-
from .moran import MoranProcess
17+
from .moran import MoranProcess, MoranProcessGraph
1718
from .strategies import *
1819
from .deterministic_cache import DeterministicCache
1920
from .match_generator import *

axelrod/graph.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
Weighted undirected sparse graphs.
3+
4+
Original source:
5+
https://github.com/marcharper/stationary/blob/master/stationary/utils/graph.py
6+
"""
7+
8+
from collections import defaultdict
9+
10+
11+
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:
18+
[[node1, node2, weights], ...]
19+
Weights can be omitted for an undirected graph.
20+
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).
24+
"""
25+
26+
def __init__(self, edges=None, directed=False):
27+
self.directed = directed
28+
self.original_edges = edges
29+
self.out_mapping = defaultdict(lambda: defaultdict(float))
30+
self.in_mapping = defaultdict(lambda: defaultdict(float))
31+
self._edges = []
32+
if edges:
33+
self.add_edges(edges)
34+
35+
def add_edge(self, source, target, weight=None):
36+
if (source, target) not in self._edges:
37+
self._edges.append((source, target))
38+
self.out_mapping[source][target] = weight
39+
self.in_mapping[target][source] = weight
40+
if not self.directed and (source != target) and \
41+
(target, source) not in self._edges:
42+
self._edges.append((target, source))
43+
self.out_mapping[target][source] = weight
44+
self.in_mapping[source][target] = weight
45+
46+
def add_edges(self, edges):
47+
for edge in edges:
48+
self.add_edge(*edge)
49+
50+
def edges(self):
51+
return self._edges
52+
53+
def vertices(self):
54+
"""Returns the set of vertices of the graph."""
55+
return list(self.out_mapping.keys())
56+
57+
def out_dict(self, source):
58+
"""Returns a dictionary of the outgoing edges of source with weights."""
59+
return self.out_mapping[source]
60+
61+
def out_vertices(self, source):
62+
"""Returns a list of the outgoing vertices."""
63+
return list(self.out_mapping[source].keys())
64+
65+
def in_dict(self, target):
66+
"""Returns a dictionary of the incoming edges of source with weights."""
67+
return self.in_mapping[target]
68+
69+
def in_vertices(self, source):
70+
"""Returns a list of the outgoing vertices."""
71+
return list(self.in_mapping[source].keys())
72+
73+
def __repr__(self):
74+
s = "<Graph: {}>".format(repr(self.original_edges))
75+
return s
76+
77+
78+
## Example Graphs
79+
80+
81+
def cycle(length, directed=False):
82+
"""
83+
Produces a cycle of length `length`.
84+
Parameters
85+
----------
86+
length: int
87+
Number of vertices in the cycle
88+
directed: bool, False
89+
Is the cycle directed?
90+
Returns
91+
-------
92+
a Graph object
93+
"""
94+
95+
graph = Graph(directed=directed)
96+
edges = []
97+
for i in range(length - 1):
98+
edges.append((i, i+1))
99+
edges.append((length - 1, 0))
100+
graph.add_edges(edges)
101+
return graph
102+
103+
104+
def complete_graph(length, loops=True):
105+
"""
106+
Produces a complete graph of size `length`, with loops.
107+
https://en.wikipedia.org/wiki/Complete_graph
108+
109+
Parameters
110+
----------
111+
length: int
112+
Number of vertices in the cycle
113+
directed: bool, False
114+
Is the graph directed?
115+
Returns
116+
-------
117+
a Graph object
118+
"""
119+
offset = 1
120+
if loops:
121+
offset = 0
122+
graph = Graph(directed=False)
123+
edges = []
124+
for i in range(length):
125+
for j in range(i + offset, length):
126+
edges.append((i, j))
127+
graph.add_edges(edges)
128+
return graph

0 commit comments

Comments
 (0)