Skip to content

Commit 4aa2ab1

Browse files
authored
Merge pull request #674 from Axelrod-Python/655
Probabilistic ending spatial tournament.
2 parents 16ed7d2 + f890e36 commit 4aa2ab1

File tree

8 files changed

+549
-34
lines changed

8 files changed

+549
-34
lines changed

axelrod/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
from .strategies import *
1313
from .deterministic_cache import DeterministicCache
1414
from .match_generator import *
15-
from .tournament import Tournament, ProbEndTournament, SpatialTournament
15+
from .tournament import Tournament, ProbEndTournament, SpatialTournament, ProbEndSpatialTournament
1616
from .result_set import ResultSet, ResultSetFromFile
1717
from .ecosystem import Ecosystem

axelrod/match_generator.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,33 @@ def estimated_size(self):
168168
size = self.__len__() * (1. / self.prob_end) * self.repetitions
169169
return size
170170

171+
172+
def graph_is_connected(edges, players):
173+
"""
174+
Test if a set of edges defines a complete graph on a set of players.
175+
176+
This is used by the spatial tournaments.
177+
178+
Parameters:
179+
-----------
180+
edges : a list of 2 tuples
181+
players : a list of player names
182+
183+
Returns:
184+
--------
185+
boolean : True if the graph is connected
186+
"""
187+
# Check if all players are connected.
188+
player_indices = set(range(len(players)))
189+
node_indices = set()
190+
for edge in edges:
191+
for node in edge:
192+
node_indices.add(node)
193+
194+
return player_indices == node_indices
195+
196+
197+
171198
class SpatialMatches(RoundRobinMatches):
172199
"""
173200
A class that generates spatially-structured matches.
@@ -191,11 +218,8 @@ class SpatialMatches(RoundRobinMatches):
191218

192219
def __init__(self, players, turns, game, repetitions, edges):
193220

194-
player_indices = list(range(len(players)))
195-
node_indices = sorted(set([node for edge in edges for node in edge]))
196-
if player_indices != node_indices:
221+
if not graph_is_connected(edges, players):
197222
raise ValueError("The graph edges do not include all players.")
198-
199223
self.edges = edges
200224
super(SpatialMatches, self).__init__(players, turns, game, repetitions)
201225

@@ -207,3 +231,39 @@ def build_match_chunks(self):
207231

208232
def __len__(self):
209233
return len(self.edges)
234+
235+
236+
class ProbEndSpatialMatches(SpatialMatches, ProbEndRoundRobinMatches):
237+
"""
238+
A class that generates spatially-structured prob ending matches.
239+
In these matches, players interact only with their neighbors rather than the
240+
entire population. This reduces to a well-mixed population when the spatial
241+
graph is a complete graph.
242+
243+
Parameters
244+
----------
245+
players : list
246+
A list of axelrod.Player objects
247+
prob_end : float
248+
The probability that a turn of a Match is the last
249+
game : axelrod.Game
250+
The game object used to score the match
251+
repetitions : int
252+
The number of repetitions of a given match
253+
edges : list
254+
A list of tuples containing the existing edges
255+
"""
256+
257+
def __init__(self, players, prob_end, game, repetitions, noise, edges):
258+
259+
if not graph_is_connected(edges, players):
260+
raise ValueError("The graph edges do not include all players.")
261+
self.edges = edges
262+
ProbEndRoundRobinMatches.__init__(self, players, prob_end,
263+
game, repetitions, noise)
264+
265+
def build_single_match_params(self):
266+
"""
267+
Creates a single set of match parameters.
268+
"""
269+
return ProbEndRoundRobinMatches.build_single_match_params(self)

axelrod/tests/property.py

Lines changed: 138 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
"""
22
A module for creating hypothesis based strategies for property based testing
33
"""
4-
import axelrod
4+
from axelrod import (strategies, Match, Game,
5+
Tournament, ProbEndTournament,
6+
SpatialTournament, ProbEndSpatialTournament)
57
from hypothesis.strategies import (composite, sampled_from, integers,
68
floats, lists)
79

10+
import itertools
11+
812

913
@composite
10-
def strategy_lists(draw, strategies=axelrod.strategies, min_size=1,
11-
max_size=len(axelrod.strategies)):
14+
def strategy_lists(draw, strategies=strategies, min_size=1,
15+
max_size=len(strategies)):
1216
"""
1317
A hypothesis decorator to return a list of strategies
1418
@@ -23,13 +27,13 @@ def strategy_lists(draw, strategies=axelrod.strategies, min_size=1,
2327
max_size=max_size))
2428
return strategies
2529

30+
2631
@composite
27-
def matches(draw, strategies=axelrod.strategies,
32+
def matches(draw, strategies=strategies,
2833
min_turns=1, max_turns=200,
2934
min_noise=0, max_noise=1):
3035
"""
31-
A hypothesis decorator to return a random match as well as a random seed (to
32-
ensure reproducibility when instance of class need the random library).
36+
A hypothesis decorator to return a random match.
3337
3438
Parameters
3539
----------
@@ -52,20 +56,18 @@ def matches(draw, strategies=axelrod.strategies,
5256
players = [s() for s in strategies]
5357
turns = draw(integers(min_value=min_turns, max_value=max_turns))
5458
noise = draw(floats(min_value=min_noise, max_value=max_noise))
55-
match = axelrod.Match(players, turns=turns, noise=noise)
59+
match = Match(players, turns=turns, noise=noise)
5660
return match
5761

5862

5963
@composite
60-
def tournaments(draw, strategies=axelrod.strategies,
64+
def tournaments(draw, strategies=strategies,
6165
min_size=1, max_size=10,
6266
min_turns=1, max_turns=200,
6367
min_noise=0, max_noise=1,
6468
min_repetitions=1, max_repetitions=20):
6569
"""
66-
A hypothesis decorator to return a tournament and a random seed (to ensure
67-
reproducibility for strategies that make use of the random module when
68-
initiating).
70+
A hypothesis decorator to return a tournament.
6971
7072
Parameters
7173
----------
@@ -95,21 +97,119 @@ def tournaments(draw, strategies=axelrod.strategies,
9597
max_value=max_repetitions))
9698
noise = draw(floats(min_value=min_noise, max_value=max_noise))
9799

98-
tournament = axelrod.Tournament(players, turns=turns,
99-
repetitions=repetitions, noise=noise)
100+
tournament = Tournament(players, turns=turns, repetitions=repetitions,
101+
noise=noise)
100102
return tournament
101103

102104

103105
@composite
104-
def prob_end_tournaments(draw, strategies=axelrod.strategies,
106+
def prob_end_tournaments(draw, strategies=strategies,
107+
min_size=1, max_size=10,
108+
min_prob_end=0, max_prob_end=1,
109+
min_noise=0, max_noise=1,
110+
min_repetitions=1, max_repetitions=20):
111+
"""
112+
A hypothesis decorator to return a tournament,
113+
114+
Parameters
115+
----------
116+
min_size : integer
117+
The minimum number of strategies to include
118+
max_size : integer
119+
The maximum number of strategies to include
120+
min_prob_end : float
121+
The minimum probability of a match ending
122+
max_prob_end : float
123+
The maximum probability of a match ending
124+
min_noise : float
125+
The minimum noise value
126+
max_noise : float
127+
The maximum noise value
128+
min_repetitions : integer
129+
The minimum number of repetitions
130+
max_repetitions : integer
131+
The maximum number of repetitions
132+
"""
133+
strategies = draw(strategy_lists(strategies=strategies,
134+
min_size=min_size,
135+
max_size=max_size))
136+
players = [s() for s in strategies]
137+
prob_end = draw(floats(min_value=min_prob_end, max_value=max_prob_end))
138+
repetitions = draw(integers(min_value=min_repetitions,
139+
max_value=max_repetitions))
140+
noise = draw(floats(min_value=min_noise, max_value=max_noise))
141+
142+
tournament = ProbEndTournament(players, prob_end=prob_end,
143+
repetitions=repetitions, noise=noise)
144+
return tournament
145+
146+
147+
@composite
148+
def spatial_tournaments(draw, strategies=strategies,
105149
min_size=1, max_size=10,
106-
min_prob_end=0, max_prob_end=1,
150+
min_turns=1, max_turns=200,
107151
min_noise=0, max_noise=1,
108152
min_repetitions=1, max_repetitions=20):
109153
"""
110-
A hypothesis decorator to return a tournament and a random seed (to ensure
111-
reproducibility for strategies that make use of the random module when
112-
initiating).
154+
A hypothesis decorator to return a spatial tournament.
155+
156+
Parameters
157+
----------
158+
min_size : integer
159+
The minimum number of strategies to include
160+
max_size : integer
161+
The maximum number of strategies to include
162+
min_turns : integer
163+
The minimum number of turns
164+
max_turns : integer
165+
The maximum number of turns
166+
min_noise : float
167+
The minimum noise value
168+
max_noise : float
169+
The maximum noise value
170+
min_repetitions : integer
171+
The minimum number of repetitions
172+
max_repetitions : integer
173+
The maximum number of repetitions
174+
"""
175+
strategies = draw(strategy_lists(strategies=strategies,
176+
min_size=min_size,
177+
max_size=max_size))
178+
players = [s() for s in strategies]
179+
player_indices = list(range(len(players)))
180+
181+
all_potential_edges = list(itertools.combinations(player_indices, 2))
182+
all_potential_edges.extend([(i, i) for i in player_indices]) # Loops
183+
edges = draw(lists(sampled_from(all_potential_edges), unique=True,
184+
average_size=2 * len(players)))
185+
186+
# Ensure all players/nodes are connected:
187+
node_indices = sorted(set([node for edge in edges for node in edge]))
188+
missing_nodes = [index
189+
for index in player_indices if index not in node_indices]
190+
for index in missing_nodes:
191+
opponent = draw(sampled_from(player_indices))
192+
edges.append((index, opponent))
193+
194+
turns = draw(integers(min_value=min_turns, max_value=max_turns))
195+
repetitions = draw(integers(min_value=min_repetitions,
196+
max_value=max_repetitions))
197+
noise = draw(floats(min_value=min_noise, max_value=max_noise))
198+
199+
tournament = SpatialTournament(players, turns=turns,
200+
repetitions=repetitions, noise=noise,
201+
edges=edges)
202+
return tournament
203+
204+
205+
@composite
206+
def prob_end_spatial_tournaments(draw, strategies=strategies,
207+
min_size=1, max_size=10,
208+
min_prob_end=0, max_prob_end=1,
209+
min_noise=0, max_noise=1,
210+
min_repetitions=1, max_repetitions=20):
211+
"""
212+
A hypothesis decorator to return a probabilistic ending spatial tournament.
113213
114214
Parameters
115215
----------
@@ -123,7 +223,7 @@ def prob_end_tournaments(draw, strategies=axelrod.strategies,
123223
The maximum probability of a match ending
124224
min_noise : float
125225
The minimum noise value
126-
min_noise : float
226+
max_noise : float
127227
The maximum noise value
128228
min_repetitions : integer
129229
The minimum number of repetitions
@@ -134,13 +234,29 @@ def prob_end_tournaments(draw, strategies=axelrod.strategies,
134234
min_size=min_size,
135235
max_size=max_size))
136236
players = [s() for s in strategies]
237+
player_indices = list(range(len(players)))
238+
239+
all_potential_edges = list(itertools.combinations(player_indices, 2))
240+
all_potential_edges.extend([(i, i) for i in player_indices]) # Loops
241+
edges = draw(lists(sampled_from(all_potential_edges), unique=True,
242+
average_size=2 * len(players)))
243+
244+
# Ensure all players/nodes are connected:
245+
node_indices = sorted(set([node for edge in edges for node in edge]))
246+
missing_nodes = [index
247+
for index in player_indices if index not in node_indices]
248+
for index in missing_nodes:
249+
opponent = draw(sampled_from(player_indices))
250+
edges.append((index, opponent))
251+
137252
prob_end = draw(floats(min_value=min_prob_end, max_value=max_prob_end))
138253
repetitions = draw(integers(min_value=min_repetitions,
139254
max_value=max_repetitions))
140255
noise = draw(floats(min_value=min_noise, max_value=max_noise))
141256

142-
tournament = axelrod.ProbEndTournament(players, prob_end=prob_end,
143-
repetitions=repetitions, noise=noise)
257+
tournament = ProbEndSpatialTournament(players, prob_end=prob_end,
258+
repetitions=repetitions,
259+
noise=noise, edges=edges)
144260
return tournament
145261

146262

@@ -178,5 +294,5 @@ def games(draw, prisoners_dilemma=True, max_value=100):
178294
r = draw(integers(max_value=max_value))
179295
p = draw(integers(max_value=max_value))
180296

181-
game = axelrod.Game(r=r, s=s, t=t, p=p)
297+
game = Game(r=r, s=s, t=t, p=p)
182298
return game

0 commit comments

Comments
 (0)