diff --git a/axelrod/__init__.py b/axelrod/__init__.py index 7cb2ebda6..482df852f 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -2,7 +2,7 @@ # The order of imports matters! from .actions import Actions, flip_action -from .random_ import random_choice +from .random_ import random_choice, seed from .plot import Plot from .game import DefaultGame, Game from .player import init_args, is_basic, obey_axelrod, update_history, Player diff --git a/axelrod/random_.py b/axelrod/random_.py index 0c3bc72eb..9dc783e5e 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -1,4 +1,5 @@ import random +import numpy from axelrod import Actions @@ -21,3 +22,9 @@ def randrange(a, b): c = b - a r = c * random.random() return a + int(r) + + +def seed(seed): + """Sets a seed""" + random.seed(seed) + numpy.random.seed(seed) diff --git a/axelrod/tests/integration/test_matches.py b/axelrod/tests/integration/test_matches.py index b62411452..d00181321 100644 --- a/axelrod/tests/integration/test_matches.py +++ b/axelrod/tests/integration/test_matches.py @@ -11,6 +11,10 @@ deterministic_strategies = [s for s in axelrod.ordinary_strategies if not s().classifier['stochastic']] # Well behaved strategies +stochastic_strategies = [s for s in axelrod.ordinary_strategies + if s().classifier['stochastic']] + + class TestMatchOutcomes(unittest.TestCase): @given(strategies=strategy_lists(strategies=deterministic_strategies, @@ -23,3 +27,19 @@ def test_outcome_repeats(self, strategies, turns): matches = [axelrod.Match(players, turns) for _ in range(3)] self.assertEqual(matches[0].play(), matches[1].play()) self.assertEqual(matches[1].play(), matches[2].play()) + + @given(strategies=strategy_lists(strategies=stochastic_strategies, + min_size=2, max_size=2), + turns=integers(min_value=1, max_value=20), + seed=integers(min_value=0, max_value=4294967295)) + def test_outcome_repeats_stochastic(self, strategies, turns, seed): + """a test to check that if a seed is set stochastic strategies give the + same result""" + results = [] + for _ in range(3): + axelrod.seed(seed) + players = [s() for s in strategies] + results.append(axelrod.Match(players, turns).play()) + + self.assertEqual(results[0], results[1]) + self.assertEqual(results[1], results[2]) diff --git a/axelrod/tests/integration/test_tournament.py b/axelrod/tests/integration/test_tournament.py index 7358c073c..e90e2384d 100644 --- a/axelrod/tests/integration/test_tournament.py +++ b/axelrod/tests/integration/test_tournament.py @@ -1,6 +1,7 @@ import unittest import axelrod import tempfile +import filecmp from axelrod.strategy_transformers import FinalTransformer @@ -60,6 +61,39 @@ def test_parallel_play(self): actual_outcome = sorted(zip(self.player_names, scores)) self.assertEqual(actual_outcome, self.expected_outcome) + def test_repeat_tournament_deterministic(self): + """A test to check that tournament gives same results.""" + deterministic_players = [s() for s in axelrod.ordinary_strategies + if not s().classifier['stochastic']] + files = [] + for _ in range(2): + tournament = axelrod.Tournament(name='test', + players=deterministic_players, + game=self.game, turns=2, + repetitions=2) + files.append(tempfile.NamedTemporaryFile()) + tournament.play(progress_bar=False, filename=files[-1].name, + build_results=False) + self.assertTrue(filecmp.cmp(files[0].name, files[1].name)) + + def test_repeat_tournament_stochastic(self): + """ + A test to check that tournament gives same results when setting seed. + """ + files = [] + for _ in range(2): + axelrod.seed(0) + stochastic_players = [s() for s in axelrod.ordinary_strategies + if s().classifier['stochastic']] + tournament = axelrod.Tournament(name='test', + players=stochastic_players, + game=self.game, turns=2, + repetitions=2) + files.append(tempfile.NamedTemporaryFile()) + tournament.play(progress_bar=False, filename=files[-1].name, + build_results=False) + self.assertTrue(filecmp.cmp(files[0].name, files[1].name)) + class TestNoisyTournament(unittest.TestCase): def test_noisy_tournament(self): diff --git a/axelrod/tests/unit/test_random_.py b/axelrod/tests/unit/test_random_.py index 16046617f..5ce4a4839 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -1,9 +1,10 @@ """Test for the random strategy.""" +import numpy import random import unittest -from axelrod import random_choice, Actions +from axelrod import random_choice, seed, Actions C, D = Actions.C, Actions.D @@ -16,3 +17,17 @@ def test_return_values(self): self.assertEqual(random_choice(), C) random.seed(2) self.assertEqual(random_choice(), D) + + def test_set_seed(self): + """Test that numpy and stdlib random seed is set by axelrod seed""" + + numpy_random_numbers = [] + stdlib_random_numbers = [] + for _ in range(2): + seed(0) + numpy_random_numbers.append(numpy.random.random()) + stdlib_random_numbers.append(random.random()) + + self.assertEqual(numpy_random_numbers[0], numpy_random_numbers[1]) + self.assertEqual(stdlib_random_numbers[0], stdlib_random_numbers[1]) + diff --git a/docs/tutorials/advanced/index.rst b/docs/tutorials/advanced/index.rst index dcefec088..ea8681061 100644 --- a/docs/tutorials/advanced/index.rst +++ b/docs/tutorials/advanced/index.rst @@ -13,3 +13,4 @@ Contents: making_tournaments.rst reading_and_writing_interactions.rst using_the_cache.rst + setting_a_seed.rst diff --git a/docs/tutorials/advanced/setting_a_seed.rst b/docs/tutorials/advanced/setting_a_seed.rst new file mode 100644 index 000000000..5459ee92b --- /dev/null +++ b/docs/tutorials/advanced/setting_a_seed.rst @@ -0,0 +1,35 @@ +.. _setting_a_seed: + +Setting a random seed +===================== + +The library has a variety of strategies whose behaviour is stochastic. To ensure +reproducible results a random seed should be set. As both Numpy and the standard +library are used for random number generation, both seeds need to be +set. To do this we can use the `seed` function:: + + >>> import axelrod as axl + >>> players = (axl.Random(), axl.MetaMixer()) # Two stochastic strategies + >>> axl.seed(0) + >>> axl.Match(players, turns=3).play() + [('D', 'C'), ('D', 'D'), ('C', 'D')] + +We obtain the same results is it is played with the same seed:: + + >>> axl.seed(0) + >>> axl.Match(players, turns=3).play() + [('D', 'C'), ('D', 'D'), ('C', 'D')] + +Note that this is equivalent to:: + + >>> import numpy + >>> import random + >>> players = (axl.Random(), axl.MetaMixer()) + >>> random.seed(0) + >>> numpy.random.seed(0) + >>> axl.Match(players, turns=3).play() + [('D', 'C'), ('D', 'D'), ('C', 'D')] + >>> numpy.random.seed(0) + >>> random.seed(0) + >>> axl.Match(players, turns=3).play() + [('D', 'C'), ('D', 'D'), ('C', 'D')]