From 26eee3947e1ca85ccf2336a09ce7317eaf8631f8 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 11 Mar 2020 17:02:26 -0700 Subject: [PATCH 1/9] Update random number generation to use a reproducible seed in line with the Axelrod library --- src/axelrod_fortran/player.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/axelrod_fortran/player.py b/src/axelrod_fortran/player.py index 19198a7..2f46f74 100644 --- a/src/axelrod_fortran/player.py +++ b/src/axelrod_fortran/player.py @@ -51,7 +51,6 @@ def __init__(self, original_name): if is_stochastic is not None: self.classifier['stochastic'] = is_stochastic - def __enter__(self): return self @@ -105,7 +104,7 @@ def strategy(self, opponent): my_last_move = original_actions[self.history[-1]] move_number = len(self.history) + 1 if self.classifier["stochastic"]: - random_value = random.random() + random_value = self._random.random() else: random_value = 0 original_action = self.original_strategy( From 721fc91d1baeef022c7b2b68951f8e6564f1c6d3 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 11 Mar 2020 18:18:53 -0700 Subject: [PATCH 2/9] Add new tests and update some existing tests --- src/axelrod_fortran/player.py | 2 +- src/axelrod_fortran/shared_library_manager.py | 2 +- src/axelrod_fortran/strategies.py | 2 +- tests/test_player.py | 75 ++++++++++++++----- 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/axelrod_fortran/player.py b/src/axelrod_fortran/player.py index 2f46f74..468f396 100644 --- a/src/axelrod_fortran/player.py +++ b/src/axelrod_fortran/player.py @@ -45,7 +45,7 @@ def __init__(self, original_name): self.index, self.shared_library_filename = \ shared_library_manager.get_filename_for_player(original_name) self.shared_library = load_library(self.shared_library_filename) - self.original_name = original_name + self.__original_name = original_name self.original_function = self.original_name is_stochastic = characteristics[self.original_name]['stochastic'] if is_stochastic is not None: diff --git a/src/axelrod_fortran/shared_library_manager.py b/src/axelrod_fortran/shared_library_manager.py index 8fadf6b..6a004f1 100644 --- a/src/axelrod_fortran/shared_library_manager.py +++ b/src/axelrod_fortran/shared_library_manager.py @@ -59,7 +59,7 @@ def create_library_copy(self): # Copy the library file to a new (temp) location. temp_directory = tempfile.gettempdir() copy_number = len(self.filenames) - filename = "{}-{}-{}".format( + filename = "{}-{}-{}".format( self.prefix, str(copy_number), self.shared_library_name) diff --git a/src/axelrod_fortran/strategies.py b/src/axelrod_fortran/strategies.py index 8d5817d..c4e0264 100644 --- a/src/axelrod_fortran/strategies.py +++ b/src/axelrod_fortran/strategies.py @@ -348,7 +348,7 @@ 'original_rank': None}, } -all_strategies = characteristics.keys() +all_strategies = list(characteristics.keys()) # Players from Axelrod's second tournament. second_tournament_strategies = [ diff --git a/tests/test_player.py b/tests/test_player.py index 8425697..d5f7f32 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -5,7 +5,7 @@ from axelrod_fortran import Player, characteristics, all_strategies from axelrod import (Alternator, Cooperator, Defector, Match, MoranProcess, - Game, basic_strategies, seed) + Game, RandomGenerator, Tournament, basic_strategies) from axelrod.action import Action @@ -22,7 +22,7 @@ def test_init(): POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_float)) assert player.original_function.restype == c_int - with pytest.raises(ValueError): + with pytest.raises(AttributeError): player = Player('test') @@ -122,16 +122,22 @@ def test_implemented_strategies(): """ for strategy, dictionary in characteristics.items(): axelrod_class = dictionary["axelrod-python_class"] - player = Player(strategy) - if (axelrod_class is not None and - player.classifier["stochastic"] is False): - axl_player = axelrod_class() + stochastic = Player(strategy).classifier["stochastic"] + if axelrod_class is not None and not stochastic: for opponent_strategy in basic_strategies: + player = Player(strategy) opponent = opponent_strategy() match = Match((player, opponent)) interactions = match.play() + + axl_player = axelrod_class() + opponent = opponent_strategy() axl_match = Match((axl_player, opponent)) - assert interactions == axl_match.play(), (player, opponent) + axl_interactions = axl_match.play() + print(player, axl_player, opponent) + print(interactions) + print(axl_interactions) + assert interactions == axl_interactions def test_champion_v_alternator(): @@ -140,15 +146,12 @@ def test_champion_v_alternator(): """ player = Player("k61r") opponent = Alternator() - match = Match((player, opponent)) - seed(0) - interactions = match.play() + seed = 0 + interactions = match.play(seed=seed) assert interactions[25:30] == [(C, D), (C, C), (C, D), (D, C), (C, D)] - - seed(0) - assert interactions == match.play() + assert interactions == match.play(seed=seed) def test_warning_for_self_interaction(recwarn): @@ -157,9 +160,7 @@ def test_warning_for_self_interaction(recwarn): """ player = Player("k42r") opponent = player - match = Match((player, opponent)) - interactions = match.play() assert len(recwarn) == 1 @@ -168,13 +169,10 @@ def test_no_warning_for_normal_interaction(recwarn): """ Test that a warning is not given for a normal interaction """ - player = Player("k42r") - opponent = Alternator() for players in [(Player("k42r"), Alternator()), (Player("k42r"), Player("k41r"))]: match = Match(players) - interactions = match.play() assert len(recwarn) == 0 @@ -185,3 +183,44 @@ def test_multiple_copies(recwarn): mp = MoranProcess(players) mp.play() mp.populations_plot() + + +def test_match_reproducibility(): + for _ in range(10): + rng = RandomGenerator() + seed = rng.random_seed_int() + strategies = rng.choice(all_strategies, size=2) + print(strategies) + players1 = [Player(strategy) for strategy in strategies] + # players1 = (p() for p in strategies) + match1 = Match(players1, turns=200, noise=0.1, seed=seed) + results1 = match1.play() + players2 = [Player(strategy) for strategy in strategies] + # players2 = (p() for p in strategies) + match2 = Match(players2, turns=200, noise=0.1, seed=seed) + results2 = match2.play() + assert (results1 == results2) + + +def test_tournament_reproducibility(): + rng = RandomGenerator() + seed = rng.random_seed_int() + strategies = rng.choice(all_strategies, size=2) + players1 = [Player(strategy) for strategy in strategies] + tournament1 = Tournament(players1, seed=seed, repetitions=2) + results1 = tournament1.play(processes=2) + + players2 = [Player(strategy) for strategy in strategies] + tournament2 = Tournament(players2, seed=seed, repetitions=2) + results2 = tournament2.play(processes=2) + + assert (results1.ranked_names == results2.ranked_names) + + +if __name__ == "__main__": + test_init() + test_matches() + test_noisy_matches() + test_implemented_strategies() + test_match_reproducibility() + test_tournament_reproducibility() From fe705e1c3510e9709a0223b0b3fa914ba08e2529 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 11 Mar 2020 20:47:51 -0700 Subject: [PATCH 3/9] Fix a bug for Player.__init__ and Player.__del__ where, if a bad strategy name is supplied, the object doesn't teardown correctly --- src/axelrod_fortran/player.py | 27 ++++++++++++++++++++------- tests/test_player.py | 14 ++++++++------ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/axelrod_fortran/player.py b/src/axelrod_fortran/player.py index 468f396..5d65aaa 100644 --- a/src/axelrod_fortran/player.py +++ b/src/axelrod_fortran/player.py @@ -42,11 +42,15 @@ def __init__(self, original_name): A instance of an axelrod Game """ super().__init__() + # The order of the next 4 lines is important. We must first check that + # the player name is valid, then grab a copy of the shared library, + # and then setup the actual strategy function. + self.original_name = original_name self.index, self.shared_library_filename = \ - shared_library_manager.get_filename_for_player(original_name) + shared_library_manager.get_filename_for_player(self.original_name) self.shared_library = load_library(self.shared_library_filename) - self.__original_name = original_name self.original_function = self.original_name + is_stochastic = characteristics[self.original_name]['stochastic'] if is_stochastic is not None: self.classifier['stochastic'] = is_stochastic @@ -62,11 +66,11 @@ def original_name(self): return self.__original_name @original_name.setter - def original_name(self, value): - if value in characteristics: - self.__original_name = value + def original_name(self, key): + if key in characteristics: + self.__original_name = key else: - raise ValueError('{} is not a valid Fortran function'.format(value)) + raise ValueError('{} is not a valid Fortran function'.format(key)) @property def original_function(self): @@ -118,7 +122,16 @@ def _release_shared_library(self): # thread closes before the player class is garbage collected, which # tends to happen at the end of a script. try: - shared_library_manager.release(self.original_name, self.index) + name = self.original_name + index = self.index + except AttributeError: + # If the Player does finish __init__, because the name of a + # non-existent strategy is supplied, a copy of the shared library + # won't be loaded, nor will self.original_name or self.index + # exist. In that case there's nothing to do. + return + try: + shared_library_manager.release(name, index) except FileNotFoundError: pass diff --git a/tests/test_player.py b/tests/test_player.py index d5f7f32..a18959b 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -22,7 +22,7 @@ def test_init(): POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_float)) assert player.original_function.restype == c_int - with pytest.raises(AttributeError): + with pytest.raises(ValueError): player = Player('test') @@ -186,19 +186,21 @@ def test_multiple_copies(recwarn): def test_match_reproducibility(): - for _ in range(10): + for _ in range(100): rng = RandomGenerator() seed = rng.random_seed_int() strategies = rng.choice(all_strategies, size=2) - print(strategies) players1 = [Player(strategy) for strategy in strategies] - # players1 = (p() for p in strategies) match1 = Match(players1, turns=200, noise=0.1, seed=seed) results1 = match1.play() + players2 = [Player(strategy) for strategy in strategies] - # players2 = (p() for p in strategies) match2 = Match(players2, turns=200, noise=0.1, seed=seed) results2 = match2.play() + if results1 != results2: + print(strategies) + print(results1) + print(results2) assert (results1 == results2) @@ -221,6 +223,6 @@ def test_tournament_reproducibility(): test_init() test_matches() test_noisy_matches() - test_implemented_strategies() + # test_implemented_strategies() test_match_reproducibility() test_tournament_reproducibility() From f901a76f661d704e80aac3302aa93af686243780 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 11 Mar 2020 21:20:54 -0700 Subject: [PATCH 4/9] Cleanup of tests --- .travis.yml | 2 +- tests/test_player.py | 23 ++++------------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index a125bb9..fafe7c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ install: - pip install -r requirements.txt - pip install pytest script: - - pytest + - pytest tests/ diff --git a/tests/test_player.py b/tests/test_player.py index a18959b..9fdb93b 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -134,9 +134,6 @@ def test_implemented_strategies(): opponent = opponent_strategy() axl_match = Match((axl_player, opponent)) axl_interactions = axl_match.play() - print(player, axl_player, opponent) - print(interactions) - print(axl_interactions) assert interactions == axl_interactions @@ -146,12 +143,11 @@ def test_champion_v_alternator(): """ player = Player("k61r") opponent = Alternator() - match = Match((player, opponent)) - seed = 0 - interactions = match.play(seed=seed) + match = Match((player, opponent), seed=seed) + interactions = match.play() assert interactions[25:30] == [(C, D), (C, C), (C, D), (D, C), (C, D)] - assert interactions == match.play(seed=seed) + assert interactions == match.play() def test_warning_for_self_interaction(recwarn): @@ -197,10 +193,7 @@ def test_match_reproducibility(): players2 = [Player(strategy) for strategy in strategies] match2 = Match(players2, turns=200, noise=0.1, seed=seed) results2 = match2.play() - if results1 != results2: - print(strategies) - print(results1) - print(results2) + assert (results1 == results2) @@ -218,11 +211,3 @@ def test_tournament_reproducibility(): assert (results1.ranked_names == results2.ranked_names) - -if __name__ == "__main__": - test_init() - test_matches() - test_noisy_matches() - # test_implemented_strategies() - test_match_reproducibility() - test_tournament_reproducibility() From dc7fe63bb28f5a21446a5561013b56a7909d6cfa Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 11 Mar 2020 21:25:17 -0700 Subject: [PATCH 5/9] Tweak tournament test --- tests/test_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_player.py b/tests/test_player.py index 9fdb93b..026ea70 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -200,7 +200,7 @@ def test_match_reproducibility(): def test_tournament_reproducibility(): rng = RandomGenerator() seed = rng.random_seed_int() - strategies = rng.choice(all_strategies, size=2) + strategies = rng.choice(all_strategies, size=10) players1 = [Player(strategy) for strategy in strategies] tournament1 = Tournament(players1, seed=seed, repetitions=2) results1 = tournament1.play(processes=2) From c0b630f50b3c5209f23cf3392d02144f2cdbcfc9 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 10 Aug 2020 19:59:45 -0700 Subject: [PATCH 6/9] Fix test_champion_v_alternator --- src/axelrod_fortran/player.py | 1 - tests/test_player.py | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/axelrod_fortran/player.py b/src/axelrod_fortran/player.py index 5d65aaa..e6c4afb 100644 --- a/src/axelrod_fortran/player.py +++ b/src/axelrod_fortran/player.py @@ -1,5 +1,4 @@ from ctypes import byref, c_float, c_int, POINTER -import random import warnings import axelrod as axl diff --git a/tests/test_player.py b/tests/test_player.py index 026ea70..55f96f3 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -1,4 +1,4 @@ -from ctypes import c_int, c_float, POINTER, CDLL +from ctypes import c_int, c_float, POINTER import itertools import pytest @@ -139,14 +139,16 @@ def test_implemented_strategies(): def test_champion_v_alternator(): """ - Specific regression test for a bug. + Specific regression test for a bug. See: + https://github.com/Axelrod-Python/axelrod-fortran/issues/62 """ player = Player("k61r") opponent = Alternator() - seed = 0 + seed = 3 match = Match((player, opponent), seed=seed) interactions = match.play() assert interactions[25:30] == [(C, D), (C, C), (C, D), (D, C), (C, D)] + match = Match((player, opponent), seed=seed) assert interactions == match.play() From 55b7323b2f6fd30c44f7e5207dcf3c66740da131 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 10 Aug 2020 20:14:11 -0700 Subject: [PATCH 7/9] Add install command for travis tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index fafe7c6..9841c5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,5 +19,6 @@ before_install: install: - pip install -r requirements.txt - pip install pytest + - python setup.py install script: - pytest tests/ From 6d544288158a862856d7a623d35ea37e16bf6656 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 10 Aug 2020 20:29:28 -0700 Subject: [PATCH 8/9] Bump versions up to 3.6 and 3.7 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9841c5f..74ba544 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - - 3.5 - 3.6 + - 3.7 dist: xenial sudo: required services: From e30437c6285ba23680965772767b26a0f4d3440d Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 12 Aug 2020 07:45:02 -0700 Subject: [PATCH 9/9] Increase requirement to axelrod >= 4.10.0 for reproducible seeding --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 11803af..661761b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -axelrod>=4.8.0 +axelrod>=4.10.0