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