Skip to content

Commit 68a1f06

Browse files
zewaywongdrvinceknight
authored andcommitted
Heterogeneous play unit tests
1 parent 88ec7b2 commit 68a1f06

File tree

1 file changed

+364
-0
lines changed

1 file changed

+364
-0
lines changed
Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
import filecmp
2+
import pathlib
3+
import unittest
4+
5+
import axelrod as axl
6+
from axelrod import MoranProcess
7+
from axelrod.load_data_ import axl_filename
8+
from axelrod.strategy_transformers import FinalTransformer
9+
from axelrod.tests.property import tournaments
10+
from hypothesis import given, settings
11+
12+
import unittest
13+
from collections import Counter
14+
15+
C, D = axl.Action.C, axl.Action.D
16+
random = axl.RandomGenerator()
17+
18+
masses = [1 * i for i in range(20)]
19+
20+
class MassBaseMatch(axl.Match):
21+
"""Axelrod Match object with a modified final score function to enable mass to influence the final score as a multiplier"""
22+
def final_score_per_turn(self):
23+
base_scores = axl.Match.final_score_per_turn(self)
24+
return [player.mass * score for player, score in zip(self.players, base_scores)]
25+
26+
def set_player_mass(players, masses):
27+
"""Add mass attribute to player strategy classes to be accessable via self.mass"""
28+
for player, mass in zip(players, masses):
29+
setattr(player, "mass", mass)
30+
31+
class TestTournament(unittest.TestCase):
32+
@classmethod
33+
def setUpClass(cls):
34+
cls.game = axl.Game()
35+
cls.players = [
36+
axl.Cooperator(),
37+
axl.TitForTat(),
38+
axl.Defector(),
39+
axl.Grudger(),
40+
axl.GoByMajority(),
41+
]
42+
cls.masses = [100, -150, 0, 100, 500]
43+
cls.player_names = [str(p) for p in cls.players]
44+
cls.test_name = "test"
45+
cls.test_repetitions = 3
46+
set_player_mass(cls.players, cls.masses)
47+
48+
cls.expected_outcome = [
49+
("Cooperator", [45, 45, 45]),
50+
("Defector", [52, 52, 52]),
51+
("Grudger", [49, 49, 49]),
52+
("Soft Go By Majority", [49, 49, 49]),
53+
("Tit For Tat", [49, 49, 49]),
54+
]
55+
cls.expected_outcome.sort()
56+
57+
@given(
58+
tournaments(
59+
strategies=axl.short_run_time_strategies,
60+
min_size=10,
61+
max_size=30,
62+
min_turns=2,
63+
max_turns=210,
64+
min_repetitions=1,
65+
max_repetitions=4,
66+
)
67+
)
68+
@settings(max_examples=1)
69+
def test_big_tournaments(self, tournament):
70+
"""A test to check that tournament runs with a sample of non-cheating
71+
strategies."""
72+
path = pathlib.Path("test_outputs/test_tournament.csv")
73+
filename = axl_filename(path)
74+
self.assertIsNone(
75+
tournament.play(
76+
progress_bar=False, filename=filename, build_results=False
77+
)
78+
)
79+
80+
def test_serial_play(self):
81+
tournament = axl.Tournament(
82+
name=self.test_name,
83+
players=self.players,
84+
game=self.game,
85+
turns=5,
86+
repetitions=self.test_repetitions,
87+
match_class=MassBaseMatch,
88+
)
89+
scores = tournament.play(progress_bar=False).scores
90+
actual_outcome = sorted(zip(self.player_names, scores))
91+
self.assertEqual(actual_outcome, self.expected_outcome)
92+
93+
def test_parallel_play(self):
94+
tournament = axl.Tournament(
95+
name=self.test_name,
96+
players=self.players,
97+
game=self.game,
98+
turns=5,
99+
repetitions=self.test_repetitions,
100+
match_class=MassBaseMatch,
101+
)
102+
scores = tournament.play(processes=2, progress_bar=False).scores
103+
actual_outcome = sorted(zip(self.player_names, scores))
104+
self.assertEqual(actual_outcome, self.expected_outcome)
105+
106+
def test_repeat_tournament_deterministic(self):
107+
"""A test to check that tournament gives same results."""
108+
deterministic_players = [
109+
s()
110+
for s in axl.short_run_time_strategies
111+
if not axl.Classifiers["stochastic"](s())
112+
]
113+
files = []
114+
for _ in range(2):
115+
tournament = axl.Tournament(
116+
name="test",
117+
players=deterministic_players,
118+
game=self.game,
119+
turns=2,
120+
repetitions=2,
121+
match_class=MassBaseMatch,
122+
)
123+
path = pathlib.Path(
124+
"test_outputs/stochastic_tournament_{}.csv".format(_)
125+
)
126+
files.append(axl_filename(path))
127+
tournament.play(
128+
progress_bar=False, filename=files[-1], build_results=False
129+
)
130+
self.assertTrue(filecmp.cmp(files[0], files[1]))
131+
132+
def test_repeat_tournament_stochastic(self):
133+
"""
134+
A test to check that tournament gives same results when setting seed.
135+
"""
136+
files = []
137+
for _ in range(2):
138+
stochastic_players = [
139+
s()
140+
for s in axl.short_run_time_strategies
141+
if axl.Classifiers["stochastic"](s())
142+
]
143+
tournament = axl.Tournament(
144+
name="test",
145+
players=stochastic_players,
146+
game=self.game,
147+
turns=2,
148+
repetitions=2,
149+
seed=17,
150+
match_class=MassBaseMatch,
151+
)
152+
path = pathlib.Path(
153+
"test_outputs/stochastic_tournament_{}.csv".format(_)
154+
)
155+
files.append(axl_filename(path))
156+
tournament.play(
157+
progress_bar=False, filename=files[-1], build_results=False
158+
)
159+
self.assertTrue(filecmp.cmp(files[0], files[1]))
160+
161+
162+
class TestNoisyTournament(unittest.TestCase):
163+
def test_noisy_tournament(self):
164+
# Defector should win for low noise
165+
players = [axl.Cooperator(), axl.Defector()]
166+
tournament = axl.Tournament(players, turns=5, repetitions=3, noise=0.0, match_class=MassBaseMatch)
167+
results = tournament.play(progress_bar=False)
168+
self.assertEqual(results.ranked_names[0], "Defector")
169+
170+
# If the noise is large enough, cooperator should win
171+
players = [axl.Cooperator(), axl.Defector()]
172+
tournament = axl.Tournament(players, turns=5, repetitions=3, noise=0.75, match_class=MassBaseMatch)
173+
results = tournament.play(progress_bar=False)
174+
self.assertEqual(results.ranked_names[0], "Cooperator")
175+
176+
177+
class TestProbEndTournament(unittest.TestCase):
178+
def test_players_do_not_know_match_length(self):
179+
"""Create two players who should cooperate on last two turns if they
180+
don't know when those last two turns are.
181+
"""
182+
p1 = FinalTransformer(["D", "D"])(axl.Cooperator)()
183+
p2 = FinalTransformer(["D", "D"])(axl.Cooperator)()
184+
players = [p1, p2]
185+
tournament = axl.Tournament(players, prob_end=0.5, repetitions=1, match_class=MassBaseMatch)
186+
results = tournament.play(progress_bar=False)
187+
# Check that both plays always cooperated
188+
for rating in results.cooperating_rating:
189+
self.assertEqual(rating, 1)
190+
191+
def test_matches_have_different_length(self):
192+
"""
193+
A match between two players should have variable length across the
194+
repetitions
195+
"""
196+
p1 = axl.Cooperator()
197+
p2 = axl.Cooperator()
198+
p3 = axl.Cooperator()
199+
players = [p1, p2, p3]
200+
tournament = axl.Tournament(
201+
players, prob_end=0.5, repetitions=2, seed=3, match_class=MassBaseMatch
202+
)
203+
results = tournament.play(progress_bar=False)
204+
# Check that match length are different across the repetitions
205+
self.assertNotEqual(results.match_lengths[0], results.match_lengths[1])
206+
207+
#Moran process tests
208+
class MassBasedMoranProcess(axl.MoranProcess):
209+
"""Axelrod MoranProcess class """
210+
def __next__(self):
211+
set_player_mass(self.players, masses)
212+
super().__next__()
213+
return self
214+
215+
class TestMoranProcess(unittest.TestCase):
216+
def test_init(self):
217+
players = axl.Cooperator(), axl.Defector()
218+
masses = [10, 20]
219+
set_player_mass(players, masses)
220+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch)
221+
self.assertEqual(mp.turns, axl.DEFAULT_TURNS)
222+
self.assertIsNone(mp.prob_end)
223+
self.assertIsNone(mp.game)
224+
self.assertEqual(mp.noise, 0)
225+
self.assertEqual(mp.initial_players, players)
226+
self.assertEqual(mp.players, list(players))
227+
self.assertEqual(
228+
mp.populations, [Counter({"Cooperator": 1, "Defector": 1})]
229+
)
230+
self.assertIsNone(mp.winning_strategy_name)
231+
self.assertEqual(mp.mutation_rate, 0)
232+
self.assertEqual(mp.mode, "bd")
233+
self.assertEqual(mp.deterministic_cache, axl.DeterministicCache())
234+
self.assertEqual(
235+
mp.mutation_targets,
236+
{"Cooperator": [players[1]], "Defector": [players[0]]},
237+
)
238+
self.assertEqual(mp.interaction_graph._edges, [(0, 1), (1, 0)])
239+
self.assertEqual(
240+
mp.reproduction_graph._edges, [(0, 1), (1, 0), (0, 0), (1, 1)]
241+
)
242+
self.assertEqual(mp.fitness_transformation, None)
243+
self.assertEqual(mp.locations, [0, 1])
244+
self.assertEqual(mp.index, {0: 0, 1: 1})
245+
246+
# Test non default graph cases
247+
players = axl.Cooperator(), axl.Defector(), axl.TitForTat()
248+
masses = [10, 20, 10]
249+
set_player_mass(players, masses)
250+
edges = [(0, 1), (2, 0), (1, 2)]
251+
graph = axl.graph.Graph(edges, directed=True)
252+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch, interaction_graph=graph)
253+
self.assertEqual(mp.interaction_graph._edges, [(0, 1), (2, 0), (1, 2)])
254+
self.assertEqual(
255+
sorted(mp.reproduction_graph._edges),
256+
sorted([(0, 1), (2, 0), (1, 2), (0, 0), (1, 1), (2, 2)]),
257+
)
258+
259+
mp = MassBasedMoranProcess(
260+
players, interaction_graph=graph, reproduction_graph=graph
261+
)
262+
self.assertEqual(mp.interaction_graph._edges, [(0, 1), (2, 0), (1, 2)])
263+
self.assertEqual(mp.reproduction_graph._edges, [(0, 1), (2, 0), (1, 2)])
264+
265+
def test_set_players(self):
266+
"""Test that set players resets all players"""
267+
players = axl.Cooperator(), axl.Defector()
268+
masses = [10, 20]
269+
set_player_mass(players, masses)
270+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch)
271+
players[0].history.append(C, D)
272+
mp.set_players()
273+
self.assertEqual(players[0].cooperations, 0)
274+
275+
def test_death_in_db(self):
276+
players = axl.Cooperator(), axl.Defector(), axl.TitForTat()
277+
masses = [10, 20, 10]
278+
set_player_mass(players, masses)
279+
mp = MoranProcess(players, match_class=MassBaseMatch, mutation_rate=0.5, mode="db", seed=1)
280+
self.assertEqual(mp.death(), 2)
281+
self.assertEqual(mp.dead, 2)
282+
mp = MoranProcess(players, match_class=MassBaseMatch, mutation_rate=0.5, mode="db", seed=2)
283+
self.assertEqual(mp.death(), 0)
284+
self.assertEqual(mp.dead, 0)
285+
mp = MoranProcess(players, match_class=MassBaseMatch, mutation_rate=0.5, mode="db", seed=9)
286+
self.assertEqual(mp.death(), 1)
287+
self.assertEqual(mp.dead, 1)
288+
289+
def test_death_in_bd(self):
290+
players = axl.Cooperator(), axl.Defector(), axl.TitForTat()
291+
masses = [10, 20, 10]
292+
set_player_mass(players, masses)
293+
edges = [(0, 1), (2, 0), (1, 2)]
294+
graph = axl.graph.Graph(edges, directed=True)
295+
mp = MoranProcess(players, match_class=MassBaseMatch, mode="bd", interaction_graph=graph, seed=1)
296+
self.assertEqual(mp.death(0), 1)
297+
mp = MoranProcess(players, match_class=MassBaseMatch, mode="bd", interaction_graph=graph, seed=2)
298+
self.assertEqual(mp.death(0), 1)
299+
mp = MoranProcess(players, match_class=MassBaseMatch, mode="bd", interaction_graph=graph, seed=3)
300+
self.assertEqual(mp.death(0), 0)
301+
302+
def test_birth_in_db(self):
303+
players = axl.Cooperator(), axl.Defector(), axl.TitForTat()
304+
masses = [10, 20, 10]
305+
set_player_mass(players, masses)
306+
mp = MoranProcess(players, match_class=MassBaseMatch, mode="db", seed=1)
307+
self.assertEqual(mp.death(), 2)
308+
self.assertEqual(mp.birth(0), 2)
309+
310+
def test_birth_in_bd(self):
311+
players = axl.Cooperator(), axl.Defector(), axl.TitForTat()
312+
masses = [1, 1, 1]
313+
set_player_mass(players, masses)
314+
mp = MoranProcess(players, match_class=MassBaseMatch, mode="bd", seed=2)
315+
self.assertEqual(mp.birth(), 0)
316+
317+
def test_fixation_check(self):
318+
players = axl.Cooperator(), axl.Cooperator()
319+
masses = [10, 20]
320+
set_player_mass(players, masses)
321+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch)
322+
self.assertTrue(mp.fixation_check())
323+
players = axl.Cooperator(), axl.Defector()
324+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch)
325+
self.assertFalse(mp.fixation_check())
326+
327+
def test_next(self):
328+
players = axl.Cooperator(), axl.Defector()
329+
masses = [10, 20]
330+
set_player_mass(players, masses)
331+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch)
332+
self.assertIsInstance(next(mp), axl.MoranProcess)
333+
334+
def test_matchup_indices(self):
335+
players = axl.Cooperator(), axl.Defector()
336+
masses = [10, 20]
337+
set_player_mass(players, masses)
338+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch)
339+
self.assertEqual(mp._matchup_indices(), {(0, 1)})
340+
341+
players = axl.Cooperator(), axl.Defector(), axl.TitForTat()
342+
masses = [10, 20, 10]
343+
set_player_mass(players, masses)
344+
edges = [(0, 1), (2, 0), (1, 2)]
345+
graph = axl.graph.Graph(edges, directed=True)
346+
mp = MassBasedMoranProcess(players, match_class=MassBaseMatch, mode="bd", interaction_graph=graph)
347+
self.assertEqual(mp._matchup_indices(), {(0, 1), (1, 2), (2, 0)})
348+
349+
def test_fps(self):
350+
players = axl.Cooperator(), axl.Defector()
351+
masses = [10, 20]
352+
set_player_mass(players, masses)
353+
mp = MoranProcess(players, match_class=MassBaseMatch, seed=1)
354+
self.assertEqual(mp.fitness_proportionate_selection([0, 0, 1]), 2)
355+
self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 2)
356+
self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 0)
357+
358+
def test_exit_condition(self):
359+
p1, p2 = axl.Cooperator(), axl.Cooperator()
360+
masses = [10, 20]
361+
set_player_mass([p1, p2], masses)
362+
mp = MassBasedMoranProcess((p1, p2), match_class=MassBaseMatch)
363+
mp.play()
364+
self.assertEqual(len(mp), 1)

0 commit comments

Comments
 (0)