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