Skip to content

Commit 9beb4bd

Browse files
committed
Rewrite many tests to use new seeding method. Rewrite Evolveable player classes to use new seeding methods.
1 parent 59af2aa commit 9beb4bd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+640
-556
lines changed

axelrod/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator
77

88
# Initialize module level Random
9-
# This is seeded by the clock / OS entropy pool
9+
# This is initially seeded by the clock / OS entropy pool
1010
# It is not used if user specifies seeds everywhere and should only be
11-
# used internally by the library
11+
# used internally by the library and in certain tests that need to set
12+
# its seed.
1213
_module_random = RandomGenerator()
1314

1415
from axelrod.load_data_ import load_pso_tables, load_weights
1516
from axelrod import graph
16-
from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator
1717
from axelrod.plot import Plot
1818
from axelrod.game import DefaultGame, Game
1919
from axelrod.history import History, LimitedHistory

axelrod/_strategy_utils.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def _calculate_scores(p1, p2, game):
137137
return s1, s2
138138

139139

140-
def look_ahead(player_1, player_2, game, rounds=10):
140+
def look_ahead(player1, player2, game, rounds=10):
141141
"""Returns a constant action that maximizes score by looking ahead.
142142
143143
Parameters
@@ -160,8 +160,10 @@ def look_ahead(player_1, player_2, game, rounds=10):
160160
possible_strategies = {C: Cooperator(), D: Defector()}
161161
for action, player in possible_strategies.items():
162162
# Instead of a deepcopy, create a new opponent and replay the history to it.
163-
opponent_ = player_2.clone()
164-
for h in player_1.history:
163+
opponent_ = player2.clone()
164+
if opponent_.classifier["stochastic"]:
165+
opponent_.set_seed(player2._seed)
166+
for h in player1.history:
165167
_limited_simulate_play(player, opponent_, h)
166168

167169
# Now play forward with the constant strategy.

axelrod/evolvable_player.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import base64
2+
import copy
23
from pickle import dumps, loads
3-
from random import randrange
44
from typing import Dict, List
55
from .player import Player
66

@@ -21,6 +21,10 @@ class EvolvablePlayer(Player):
2121
parent_class = Player
2222
parent_kwargs = [] # type: List[str]
2323

24+
def __init__(self, seed=None):
25+
super().__init__()
26+
self.set_seed(seed=seed)
27+
2428
def overwrite_init_kwargs(self, **kwargs):
2529
"""Use to overwrite parameters for proper cloning and testing."""
2630
for k, v in kwargs.items():
@@ -32,6 +36,24 @@ def create_new(self, **kwargs):
3236
init_kwargs.update(kwargs)
3337
return self.__class__(**init_kwargs)
3438

39+
# def clone(self):
40+
# """Clones the player without history, reapplying configuration
41+
# parameters as necessary.
42+
#
43+
# We're overriding Player.clone to also propagate the seed because EvolvablePlayers
44+
# currently randomize themselves on initialization.
45+
# """
46+
#
47+
# # You may be tempted to re-implement using the `copy` module
48+
# # Note that this would require a deepcopy in some cases and there may
49+
# # be significant changes required throughout the library.
50+
# # Consider overriding in special cases only if necessary
51+
# cls = self.__class__
52+
# new_player = cls(**self.init_kwargs, seed=self._seed)
53+
# new_player.match_attributes = copy.copy(self.match_attributes)
54+
# # new_player.set_seed(self._seed)
55+
# return new_player
56+
3557
# Serialization and deserialization. You may overwrite to obtain more human readable serializations
3658
# but you must overwrite both.
3759

@@ -73,15 +95,15 @@ def copy_lists(lists: List[List]) -> List[List]:
7395
return list(map(list, lists))
7496

7597

76-
def crossover_lists(list1: List, list2: List) -> List:
77-
cross_point = randrange(len(list1))
98+
def crossover_lists(list1: List, list2: List, rng) -> List:
99+
cross_point = rng.randint(0, len(list1))
78100
new_list = list(list1[:cross_point]) + list(list2[cross_point:])
79101
return new_list
80102

81103

82-
def crossover_dictionaries(table1: Dict, table2: Dict) -> Dict:
104+
def crossover_dictionaries(table1: Dict, table2: Dict, rng) -> Dict:
83105
keys = list(table1.keys())
84-
cross_point = randrange(len(keys))
106+
cross_point = rng.randint(0, len(keys))
85107
new_items = [(k, table1[k]) for k in keys[:cross_point]]
86108
new_items += [(k, table2[k]) for k in keys[cross_point:]]
87109
new_table = dict(new_items)

axelrod/fingerprint.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ def _construct_tournament_elements(
245245
progress_bar : bool
246246
Whether or not to create a progress bar which will be updated
247247
248-
249248
Returns
250249
----------
251250
edges : list of tuples
@@ -279,6 +278,7 @@ def fingerprint(
279278
processes: int = None,
280279
filename: str = None,
281280
progress_bar: bool = True,
281+
seed: int = None
282282
) -> dict:
283283
"""Build and play the spatial tournament.
284284
@@ -322,7 +322,8 @@ def fingerprint(
322322

323323
self.step = step
324324
self.spatial_tournament = axl.Tournament(
325-
tourn_players, turns=turns, repetitions=repetitions, edges=edges
325+
tourn_players, turns=turns, repetitions=repetitions, edges=edges,
326+
seed=seed
326327
)
327328
self.spatial_tournament.play(
328329
build_results=False,

axelrod/match.py

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

7272
self.seed = seed
73+
self._random = RandomGenerator(seed=self.seed)
7374

7475
if game is None:
7576
self.game = Game()
@@ -166,8 +167,6 @@ def play(self):
166167
167168
i.e. One entry per turn containing a pair of actions.
168169
"""
169-
# if self._stochastic:
170-
self._random = RandomGenerator(seed=self.seed)
171170
if self.prob_end:
172171
r = self._random.random()
173172
turns = min(sample_length(self.prob_end, r), self.turns)
@@ -182,6 +181,7 @@ def play(self):
182181
p.set_match_attributes(**self.match_attributes)
183182
# if p.classifier["stochastic"]:
184183
# Generate a random seed for the player
184+
# TODO: Seeds only for stochastic players
185185
p.set_seed(self._random.random_seed_int())
186186
result = []
187187
for _ in range(turns):

axelrod/match_generator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(
3838
Mapping attribute names to values which should be passed to players.
3939
The default is to use the correct values for turns, game and noise
4040
but these can be overridden if desired.
41+
seed : int
4142
"""
4243
self.players = players
4344
self.turns = turns

axelrod/moran.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from .deterministic_cache import DeterministicCache
1111
from .graph import Graph, complete_graph
1212
from .match import Match
13-
from .random_ import RandomGenerator
13+
from .random_ import RandomGenerator, BulkRandomGenerator
1414

1515

1616
class MoranProcess(object):
@@ -101,6 +101,7 @@ def __init__(
101101
self.mutation_rate = mutation_rate
102102
self.stop_on_fixation = stop_on_fixation
103103
self._random = RandomGenerator(seed=seed)
104+
self._bulk_random = BulkRandomGenerator(self._random.random_seed_int())
104105
m = mutation_method.lower()
105106
if m in ["atomic", "transition"]:
106107
self.mutation_method = m
@@ -249,13 +250,13 @@ def birth(self, index: int = None) -> int:
249250
# possible choices
250251
scores.pop(index)
251252
# Make sure to get the correct index post-pop
252-
j = fitness_proportionate_selection(
253+
j = self.fitness_proportionate_selection(
253254
scores, fitness_transformation=self.fitness_transformation
254255
)
255256
if j >= index:
256257
j += 1
257258
else:
258-
j = fitness_proportionate_selection(
259+
j = self.fitness_proportionate_selection(
259260
scores, fitness_transformation=self.fitness_transformation
260261
)
261262
return j
@@ -362,7 +363,7 @@ def score_all(self) -> List:
362363
noise=self.noise,
363364
game=self.game,
364365
deterministic_cache=self.deterministic_cache,
365-
seed=self._random.random_seed_int()
366+
seed=next(self._bulk_random)
366367
)
367368
match.play()
368369
match_scores = match.final_score_per_turn()

axelrod/random_.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,23 +119,26 @@ class BulkRandomGenerator(object):
119119
"""Bulk generator of random integers for tournament seeding
120120
and reproducibility. Use like a generator."""
121121
def __init__(self, seed=None, batch_size=1000):
122-
self.random_generator = RandomState()
123-
self.random_generator.seed(seed)
122+
self._random_generator = RandomState()
123+
self._random_generator.seed(seed)
124124
self._ints = None
125-
self.batch_size = batch_size
125+
self._batch_size = batch_size
126+
self._index = 0
126127
self._fill_ints()
127128

128129
def _fill_ints(self):
129-
ints = self.random_generator.randint(
130+
ints = self._random_generator.randint(
130131
low=0,
131132
high=2**32 - 1,
132-
size=self.batch_size)
133-
self._ints = (x for x in ints)
133+
size=self._batch_size)
134+
self._ints = [x for x in ints]
135+
self._index = 0
134136

135137
def __next__(self):
136138
try:
137-
x = next(self._ints)
139+
x = self._ints[self._index]
140+
self._index += 1
138141
return x
139-
except StopIteration:
142+
except IndexError:
140143
self._fill_ints()
141-
return next(self._ints)
144+
return self.__next__()

axelrod/result_set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import numpy as np
77
import tqdm
8-
from axelrod.action import Action
98

109
import dask as da
1110
import dask.dataframe as dd
1211

12+
from axelrod.action import Action
1313
from . import eigen
1414

1515
C, D = Action.C, Action.D

axelrod/strategies/ann.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import List, Tuple
22
import numpy as np
3-
import numpy.random as random
43
from axelrod.action import Action
54
from axelrod.load_data_ import load_weights
65
from axelrod.evolvable_player import EvolvablePlayer, InsufficientParametersError, crossover_lists
@@ -229,7 +228,9 @@ def __init__(
229228
weights: List[float] = None,
230229
mutation_probability: float = None,
231230
mutation_distance: int = 5,
231+
seed: int = None
232232
) -> None:
233+
self.set_seed(seed=seed)
233234
num_features, num_hidden, weights, mutation_probability = self._normalize_parameters(
234235
num_features, num_hidden, weights, mutation_probability)
235236
ANN.__init__(self,
@@ -245,25 +246,23 @@ def __init__(
245246
weights=weights,
246247
mutation_probability=mutation_probability)
247248

248-
@classmethod
249-
def _normalize_parameters(cls, num_features=None, num_hidden=None, weights=None, mutation_probability=None):
249+
def _normalize_parameters(self, num_features=None, num_hidden=None, weights=None, mutation_probability=None):
250250
if not (num_features and num_hidden):
251251
raise InsufficientParametersError("Insufficient Parameters to instantiate EvolvableANN")
252252
size = num_weights(num_features, num_hidden)
253253
if not weights:
254-
weights = [random.uniform(-1, 1) for _ in range(size)]
254+
weights = [self._random.uniform(-1, 1) for _ in range(size)]
255255
if mutation_probability is None:
256256
mutation_probability = 10. / size
257257
return num_features, num_hidden, weights, mutation_probability
258258

259-
@staticmethod
260-
def mutate_weights(weights, num_features, num_hidden, mutation_probability,
259+
def mutate_weights(self, weights, num_features, num_hidden, mutation_probability,
261260
mutation_distance):
262261
size = num_weights(num_features, num_hidden)
263-
randoms = random.random(size)
262+
randoms = self._random.random(size)
264263
for i, r in enumerate(randoms):
265264
if r < mutation_probability:
266-
p = 1 + random.uniform(-1, 1) * mutation_distance
265+
p = 1 + self._random.uniform(-1, 1) * mutation_distance
267266
weights[i] *= p
268267
return weights
269268

@@ -276,7 +275,7 @@ def mutate(self):
276275
def crossover(self, other):
277276
if other.__class__ != self.__class__:
278277
raise TypeError("Crossover must be between the same player classes.")
279-
weights = crossover_lists(self.weights, other.weights)
278+
weights = crossover_lists(self.weights, other.weights, self._random)
280279
return self.create_new(weights=weights)
281280

282281

0 commit comments

Comments
 (0)