Skip to content

Commit 59af2aa

Browse files
committed
Fix many tests and errors for seed setting and propagation
1 parent da3c26c commit 59af2aa

19 files changed

+111
-90
lines changed

axelrod/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33
# The order of imports matters!
44
from axelrod.version import __version__
5+
from axelrod.action import Action
6+
from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator
7+
8+
# Initialize module level Random
9+
# This is seeded by the clock / OS entropy pool
10+
# It is not used if user specifies seeds everywhere and should only be
11+
# used internally by the library
12+
_module_random = RandomGenerator()
13+
514
from axelrod.load_data_ import load_pso_tables, load_weights
615
from axelrod import graph
7-
from axelrod.action import Action
816
from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator
917
from axelrod.plot import Plot
1018
from axelrod.game import DefaultGame, Game

axelrod/match.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ def __init__(
7070
self.noise = noise
7171

7272
self.seed = seed
73-
self._random = RandomGenerator(seed=self.seed)
7473

7574
if game is None:
7675
self.game = Game()
@@ -143,8 +142,8 @@ def simultaneous_play(self, player, coplayer, noise=0):
143142
# Note this uses the Match classes random generator, not either
144143
# player's random generator. A player shouldn't be able to
145144
# predict the outcome of this noise flip.
146-
s1 = self.random_flip(s1, noise)
147-
s2 = self.random_flip(s2, noise)
145+
s1 = self._random.random_flip(s1, noise)
146+
s2 = self._random.random_flip(s2, noise)
148147
player.update_history(s1, s2)
149148
coplayer.update_history(s2, s1)
150149
return s1, s2
@@ -167,18 +166,23 @@ def play(self):
167166
168167
i.e. One entry per turn containing a pair of actions.
169168
"""
169+
# if self._stochastic:
170170
self._random = RandomGenerator(seed=self.seed)
171-
r = self._random.random()
172-
turns = min(sample_length(self.prob_end, r), self.turns)
171+
if self.prob_end:
172+
r = self._random.random()
173+
turns = min(sample_length(self.prob_end, r), self.turns)
174+
else:
175+
turns = self.turns
173176
cache_key = (self.players[0], self.players[1])
174177

175178
if self._stochastic or not self._cached_enough_turns(cache_key, turns):
176179
for p in self.players:
177180
if self.reset:
178181
p.reset()
179182
p.set_match_attributes(**self.match_attributes)
180-
# Generate a random seed for each player
181-
p.set_seed(self._random.randint(0, 100000000))
183+
# if p.classifier["stochastic"]:
184+
# Generate a random seed for the player
185+
p.set_seed(self._random.random_seed_int())
182186
result = []
183187
for _ in range(turns):
184188
plays = self.simultaneous_play(

axelrod/moran.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def __init__(
2929
fitness_transformation: Callable = None,
3030
mutation_method="transition",
3131
stop_on_fixation=True,
32-
seed = None
32+
seed=None
3333
) -> None:
3434
"""
3535
An agent based Moran process class. In each round, each player plays a
@@ -362,6 +362,7 @@ def score_all(self) -> List:
362362
noise=self.noise,
363363
game=self.game,
364364
deterministic_cache=self.deterministic_cache,
365+
seed=self._random.random_seed_int()
365366
)
366367
match.play()
367368
match_scores = match.final_score_per_turn()

axelrod/player.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import numpy as np
88

9+
from axelrod import _module_random
910
from axelrod.action import Action
1011
from axelrod.game import DefaultGame
1112
from axelrod.history import History
@@ -70,14 +71,26 @@ def __new__(cls, *args, **kwargs):
7071
"""Caches arguments for Player cloning."""
7172
obj = super().__new__(cls)
7273
obj.init_kwargs = cls.init_params(*args, **kwargs)
74+
# # Set up random seed from the module level random seed
75+
# # in case the user doesn't specific one later.
76+
# need_seed = False
77+
# try:
78+
# seed = kwargs["seed"]
79+
# if seed is None:
80+
# need_seed = True
81+
# except KeyError:
82+
# need_seed = True
83+
# if need_seed:
84+
# seed = _module_random.randint(0, 2**32-1)
85+
# obj._seed = seed
7386
return obj
7487

7588
@classmethod
7689
def init_params(cls, *args, **kwargs):
7790
"""
7891
Return a dictionary containing the init parameters of a strategy
7992
(without 'self').
80-
Use *args and *kwargs as value if specified
93+
Use *args and **kwargs as value if specified
8194
and complete the rest with the default values.
8295
"""
8396
sig = inspect.signature(cls.__init__)
@@ -99,7 +112,7 @@ def __init__(self):
99112
if dimension not in self.classifier:
100113
self.classifier[dimension] = self.default_classifier[dimension]
101114
self.set_match_attributes()
102-
self.set_seed()
115+
# self.set_seed(seed=self._seed)
103116

104117
def __eq__(self, other):
105118
"""
@@ -113,8 +126,8 @@ def __eq__(self, other):
113126
value = getattr(self, attribute, None)
114127
other_value = getattr(other, attribute, None)
115128

116-
if attribute == "_random":
117-
# Don't compare the random seeds.
129+
if attribute in ["_random", "_seed"]:
130+
# Don't compare the random generators.
118131
continue
119132

120133
if isinstance(value, np.ndarray):
@@ -164,9 +177,13 @@ def set_match_attributes(self, length=-1, game=None, noise=0):
164177
self.receive_match_attributes()
165178

166179
def set_seed(self, seed=None):
167-
"""Set a random seed for the player's random number
168-
generator."""
169-
self._random = RandomGenerator(seed=seed)
180+
"""Set a random seed for the player's random number generator."""
181+
if seed is None:
182+
# Warning: using global seed
183+
self._seed = _module_random.random_seed_int()
184+
else:
185+
self._seed = seed
186+
self._random = RandomGenerator(seed=self._seed)
170187

171188
def __repr__(self):
172189
"""The string method for the strategy.
@@ -207,6 +224,7 @@ def clone(self):
207224
cls = self.__class__
208225
new_player = cls(**self.init_kwargs)
209226
new_player.match_attributes = copy.copy(self.match_attributes)
227+
# new_player.set_seed(self._seed)
210228
return new_player
211229

212230
def reset(self):
@@ -218,6 +236,7 @@ def reset(self):
218236
"""
219237
# This also resets the history.
220238
self.__init__(**self.init_kwargs)
239+
# self.set_seed(self._seed)
221240

222241
def update_history(self, play, coplay):
223242
self.history.append(play, coplay)

axelrod/random_.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def random(self, *args, **kwargs):
2727
def randint(self, *args, **kwargs):
2828
return self._random.randint(*args, **kwargs)
2929

30+
def random_seed_int(self):
31+
return self.randint(0, 2**32-1)
32+
3033
def choice(self, *args, **kwargs):
3134
return self._random.choice(*args, **kwargs)
3235

axelrod/strategies/hmm.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ class SimpleHMM(object):
5858
"""
5959

6060
def __init__(
61-
self, transitions_C, transitions_D, emission_probabilities, initial_state,
62-
seed=None
61+
self, transitions_C, transitions_D, emission_probabilities, initial_state
6362
) -> None:
6463
"""
6564
Params
@@ -73,7 +72,6 @@ def __init__(
7372
self.transitions_D = transitions_D
7473
self.emission_probabilities = emission_probabilities
7574
self.state = initial_state
76-
self._random = RandomGenerator(seed=seed)
7775

7876
def is_well_formed(self) -> bool:
7977
"""
@@ -161,8 +159,7 @@ def __init__(
161159
self.initial_state = initial_state
162160
self.initial_action = initial_action
163161
self.hmm = SimpleHMM(
164-
copy_lists(transitions_C), copy_lists(transitions_D), list(emission_probabilities), initial_state,
165-
seed=self._random.randint(0, 10000000)
162+
copy_lists(transitions_C), copy_lists(transitions_D), list(emission_probabilities), initial_state
166163
)
167164
assert self.hmm.is_well_formed()
168165
self.state = self.hmm.state
@@ -190,6 +187,11 @@ def strategy(self, opponent: Player) -> Action:
190187
self.state = self.hmm.state
191188
return action
192189

190+
def set_seed(self, seed=None):
191+
super().set_seed(seed=seed)
192+
# Share RNG with HMM
193+
self.hmm._random = self._random
194+
193195

194196
class EvolvableHMMPlayer(HMMPlayer, EvolvablePlayer):
195197
"""Evolvable version of HMMPlayer."""

axelrod/strategies/memoryone.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ def strategy(self, opponent: Player) -> Action:
9191
# Determine which probability to use
9292
p = self._four_vector[(self.history[-1], opponent.history[-1])]
9393
# Draw a random number in [0, 1] to decide
94-
return self._random.random_choice(p)
94+
try:
95+
return self._random.random_choice(p)
96+
except AttributeError:
97+
return D if p == 0 else C
9598

9699

97100
class WinStayLoseShift(MemoryOnePlayer):

axelrod/strategies/memorytwo.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ def strategy(self, opponent: Player) -> Action:
102102
(tuple(self.history[-2:]), tuple(opponent.history[-2:]))
103103
]
104104
# Draw a random number in [0, 1] to decide
105-
return self._random.random_choice(p)
105+
try:
106+
return self._random.random_choice(p)
107+
except AttributeError:
108+
return D if p == 0 else C
106109

107110

108111
class AON2(MemoryTwoPlayer):

axelrod/strategies/meta.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ def __init__(self, team=None):
5757
self.team = [t() for t in self.team]
5858

5959
super().__init__()
60-
self.set_seed()
6160

6261
# This player inherits the classifiers of its team.
6362
# Note that memory_depth is not simply the max memory_depth of the team.
@@ -75,9 +74,10 @@ def __init__(self, team=None):
7574
self._last_results = None
7675

7776
def set_seed(self, seed=None):
78-
super().set_seed(seed)
77+
super().set_seed(seed=seed)
78+
# Seed the team as well
7979
for t in self.team:
80-
t.set_seed(self._random.randint(0, 100000000))
80+
t.set_seed(self._random.random_seed_int())
8181

8282
def receive_match_attributes(self):
8383
for t in self.team:

axelrod/strategies/oncebitten.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import random
2-
31
from axelrod.action import Action
42
from axelrod.player import Player
53

@@ -118,7 +116,7 @@ def __init__(self, forget_probability: float = 0.05) -> None:
118116
self.forget_probability = forget_probability
119117

120118
def strategy(self, opponent: Player) -> Action:
121-
r = random.random()
119+
r = self._random.random()
122120
if not opponent.history:
123121
return self._initial
124122
if opponent.history[-1] == D:

0 commit comments

Comments
 (0)