Skip to content

Commit c1d6333

Browse files
Merge pull request #1288 from Axelrod-Python/random_seed
Implement reproducible seeding
2 parents c89df5f + 82a22ca commit c1d6333

File tree

117 files changed

+3651
-2557
lines changed

Some content is hidden

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

117 files changed

+3651
-2557
lines changed

.github/workflows/config.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ on:
99

1010
jobs:
1111
build:
12-
1312
runs-on: ${{ matrix.os }}
1413
strategy:
1514
max-parallel: 4
@@ -37,7 +36,6 @@ jobs:
3736
- name: Run tests
3837
run: |
3938
python -m pip install coverage
40-
python -m pip install hypothesis==3.2
4139
coverage run --source=axelrod -m unittest discover
4240
- name: Report coverage
4341
run: |

README.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,8 @@ Quick Start
8989
The following runs a basic tournament::
9090

9191
>>> import axelrod as axl
92-
>>> axl.seed(0) # Set a seed
9392
>>> players = [s() for s in axl.demo_strategies] # Create players
94-
>>> tournament = axl.Tournament(players) # Create a tournament
93+
>>> tournament = axl.Tournament(players, seed=1) # Create a tournament
9594
>>> results = tournament.play() # Play the tournament
9695
>>> results.ranked_names
9796
['Defector', 'Grudger', 'Tit For Tat', 'Cooperator', 'Random: 0.5']

axelrod/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@
33

44
# The order of imports matters!
55
from axelrod.version import __version__
6+
from axelrod.action import Action
7+
from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator
8+
9+
# Initialize module level Random
10+
# This is initially seeded by the clock / OS entropy pool
11+
# It is not used if user specifies seeds everywhere and should only be
12+
# used internally by the library and in certain tests that need to set
13+
# its seed.
14+
_module_random = RandomGenerator()
15+
616
from axelrod.load_data_ import load_pso_tables, load_weights
717
from axelrod import graph
8-
from axelrod.action import Action
9-
from axelrod.random_ import random_choice, random_flip, seed, Pdf
1018
from axelrod.plot import Plot
1119
from axelrod.game import DefaultGame, Game
1220
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/action.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from enum import Enum
1010
from functools import total_ordering
11-
from typing import Iterable
11+
from typing import Iterable, Tuple
1212

1313

1414
class UnknownActionError(ValueError):
@@ -70,7 +70,7 @@ def from_char(cls, character):
7070
raise UnknownActionError('Character must be "C" or "D".')
7171

7272

73-
def str_to_actions(actions: str) -> tuple:
73+
def str_to_actions(actions: str) -> Tuple[Action, ...]:
7474
"""Converts a string to a tuple of actions.
7575
7676
Parameters

axelrod/classifier.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
Union,
1414
)
1515

16+
import yaml
1617
from axelrod.makes_use_of import makes_use_of
1718
from axelrod.player import Player
18-
import yaml
1919

2020
ALL_CLASSIFIERS_PATH = "data/all_classifiers.yml"
2121

axelrod/data/all_classifiers.yml

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -961,39 +961,47 @@ Meta Hunter Aggressive:
961961
Meta Majority:
962962
inspects_source: false
963963
long_run_time: true
964-
makes_use_of: !!set {}
964+
makes_use_of: !!set
965+
game: null
966+
length: null
965967
manipulates_source: false
966968
manipulates_state: false
967969
memory_depth: .inf
968970
stochastic: true
969971
Meta Majority Finite Memory:
970972
inspects_source: false
971973
long_run_time: true
972-
makes_use_of: !!set {}
974+
makes_use_of: !!set
975+
game: null
973976
manipulates_source: false
974977
manipulates_state: false
975978
memory_depth: .inf
976979
stochastic: true
977980
Meta Majority Long Memory:
978981
inspects_source: false
979982
long_run_time: true
980-
makes_use_of: !!set {}
983+
makes_use_of: !!set
984+
game: null
985+
length: null
981986
manipulates_source: false
982987
manipulates_state: false
983988
memory_depth: .inf
984989
stochastic: true
985990
Meta Majority Memory One:
986991
inspects_source: false
987992
long_run_time: true
988-
makes_use_of: !!set {}
993+
makes_use_of: !!set
994+
game: null
989995
manipulates_source: false
990996
manipulates_state: false
991997
memory_depth: .inf
992998
stochastic: true
993999
Meta Minority:
9941000
inspects_source: false
9951001
long_run_time: true
996-
makes_use_of: !!set {}
1002+
makes_use_of: !!set
1003+
game: null
1004+
length: null
9971005
manipulates_source: false
9981006
manipulates_state: false
9991007
memory_depth: .inf
@@ -1011,6 +1019,7 @@ Meta Winner:
10111019
long_run_time: true
10121020
makes_use_of: !!set
10131021
game: null
1022+
length: null
10141023
manipulates_source: false
10151024
manipulates_state: false
10161025
memory_depth: .inf
@@ -1020,6 +1029,7 @@ Meta Winner Deterministic:
10201029
long_run_time: true
10211030
makes_use_of: !!set
10221031
game: null
1032+
length: null
10231033
manipulates_source: false
10241034
manipulates_state: false
10251035
memory_depth: .inf
@@ -1029,6 +1039,7 @@ Meta Winner Ensemble:
10291039
long_run_time: true
10301040
makes_use_of: !!set
10311041
game: null
1042+
length: null
10321043
manipulates_source: false
10331044
manipulates_state: false
10341045
memory_depth: .inf
@@ -1047,6 +1058,7 @@ Meta Winner Long Memory:
10471058
long_run_time: true
10481059
makes_use_of: !!set
10491060
game: null
1061+
length: null
10501062
manipulates_source: false
10511063
manipulates_state: false
10521064
memory_depth: .inf
@@ -1065,6 +1077,7 @@ Meta Winner Stochastic:
10651077
long_run_time: true
10661078
makes_use_of: !!set
10671079
game: null
1080+
length: null
10681081
manipulates_source: false
10691082
manipulates_state: false
10701083
memory_depth: .inf
@@ -1130,40 +1143,47 @@ N Tit(s) For M Tat(s):
11301143
NMWE Deterministic:
11311144
inspects_source: false
11321145
long_run_time: true
1133-
makes_use_of: &id001 !!set
1146+
makes_use_of: !!set
11341147
game: null
1148+
length: null
11351149
manipulates_source: false
11361150
manipulates_state: false
11371151
memory_depth: .inf
11381152
stochastic: true
11391153
NMWE Finite Memory:
11401154
inspects_source: false
11411155
long_run_time: true
1142-
makes_use_of: *id001
1156+
makes_use_of: !!set
1157+
game: null
11431158
manipulates_source: false
11441159
manipulates_state: false
11451160
memory_depth: .inf
11461161
stochastic: true
11471162
NMWE Long Memory:
11481163
inspects_source: false
11491164
long_run_time: true
1150-
makes_use_of: *id001
1165+
makes_use_of: !!set
1166+
game: null
1167+
length: null
11511168
manipulates_source: false
11521169
manipulates_state: false
11531170
memory_depth: .inf
11541171
stochastic: true
11551172
NMWE Memory One:
11561173
inspects_source: false
11571174
long_run_time: true
1158-
makes_use_of: *id001
1175+
makes_use_of: !!set
1176+
game: null
11591177
manipulates_source: false
11601178
manipulates_state: false
11611179
memory_depth: .inf
11621180
stochastic: true
11631181
NMWE Stochastic:
11641182
inspects_source: false
11651183
long_run_time: true
1166-
makes_use_of: *id001
1184+
makes_use_of: !!set
1185+
game: null
1186+
length: null
11671187
manipulates_source: false
11681188
manipulates_state: false
11691189
memory_depth: .inf
@@ -1197,14 +1217,17 @@ Nice Meta Winner:
11971217
long_run_time: true
11981218
makes_use_of: !!set
11991219
game: null
1220+
length: null
12001221
manipulates_source: false
12011222
manipulates_state: false
12021223
memory_depth: .inf
12031224
stochastic: true
12041225
Nice Meta Winner Ensemble:
12051226
inspects_source: false
12061227
long_run_time: true
1207-
makes_use_of: *id001
1228+
makes_use_of: !!set
1229+
game: null
1230+
length: null
12081231
manipulates_source: false
12091232
manipulates_state: false
12101233
memory_depth: .inf

axelrod/eigen.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77

88
from typing import Tuple
99

10-
import numpy
10+
import numpy as np
1111

1212

13-
def _normalise(nvec: numpy.ndarray) -> numpy.ndarray:
13+
def _normalise(nvec: np.ndarray) -> np.ndarray:
1414
"""Normalises the given numpy array."""
15-
with numpy.errstate(invalid="ignore"):
16-
result = nvec / numpy.sqrt((nvec @ nvec))
15+
with np.errstate(invalid="ignore"):
16+
result = nvec / np.sqrt((nvec @ nvec))
1717
return result
1818

1919

20-
def _squared_error(vector_1: numpy.ndarray, vector_2: numpy.ndarray) -> float:
20+
def _squared_error(vector_1: np.ndarray, vector_2: np.ndarray) -> float:
2121
"""Computes the squared error between two numpy arrays."""
2222
diff = vector_1 - vector_2
2323
s = diff @ diff
24-
return numpy.sqrt(s)
24+
return np.sqrt(s)
2525

2626

27-
def _power_iteration(mat: numpy.array, initial: numpy.ndarray) -> numpy.ndarray:
27+
def _power_iteration(mat: np.array, initial: np.ndarray) -> np.ndarray:
2828
"""
2929
Generator of successive approximations.
3030
@@ -33,7 +33,7 @@ def _power_iteration(mat: numpy.array, initial: numpy.ndarray) -> numpy.ndarray:
3333
mat: numpy.array
3434
The matrix to use for multiplication iteration
3535
initial: numpy.array, None
36-
The initial state. Will be set to numpy.array([1, 1, ...]) if None
36+
The initial state. Will be set to np.array([1, 1, ...]) if None
3737
3838
Yields
3939
------
@@ -42,13 +42,13 @@ def _power_iteration(mat: numpy.array, initial: numpy.ndarray) -> numpy.ndarray:
4242

4343
vec = initial
4444
while True:
45-
vec = _normalise(numpy.dot(mat, vec))
45+
vec = _normalise(np.dot(mat, vec))
4646
yield vec
4747

4848

4949
def principal_eigenvector(
50-
mat: numpy.array, maximum_iterations=1000, max_error=1e-3
51-
) -> Tuple[numpy.ndarray, float]:
50+
mat: np.array, maximum_iterations=1000, max_error=1e-3
51+
) -> Tuple[np.ndarray, float]:
5252
"""
5353
Computes the (normalised) principal eigenvector of the given matrix.
5454
@@ -66,12 +66,12 @@ def principal_eigenvector(
6666
ndarray
6767
Eigenvector estimate for the input matrix
6868
float
69-
Eigenvalue corresonding to the returned eigenvector
69+
Eigenvalue corresponding to the returned eigenvector
7070
"""
7171

72-
mat_ = numpy.array(mat)
72+
mat_ = np.array(mat)
7373
size = mat_.shape[0]
74-
initial = numpy.ones(size)
74+
initial = np.ones(size)
7575

7676
# Power iteration
7777
if not maximum_iterations:

axelrod/evolvable_player.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import base64
22
from pickle import dumps, loads
3-
from random import randrange
43
from typing import Dict, List
54

65
from .player import Player
@@ -22,15 +21,26 @@ class EvolvablePlayer(Player):
2221
parent_class = Player
2322
parent_kwargs = [] # type: List[str]
2423

24+
def __init__(self, seed=None):
25+
# Parameter seed is required for reproducibility. Player will throw
26+
# a warning to the user otherwise.
27+
super().__init__()
28+
self.set_seed(seed=seed)
29+
2530
def overwrite_init_kwargs(self, **kwargs):
2631
"""Use to overwrite parameters for proper cloning and testing."""
2732
for k, v in kwargs.items():
2833
self.init_kwargs[k] = v
2934

3035
def create_new(self, **kwargs):
31-
"""Creates a new variant with parameters overwritten by kwargs."""
36+
"""Creates a new variant with parameters overwritten by kwargs. This differs from
37+
cloning the Player because it propagates a seed forward, and is intended to be
38+
used by the mutation and crossover methods."""
3239
init_kwargs = self.init_kwargs.copy()
3340
init_kwargs.update(kwargs)
41+
# Propagate seed forward for reproducibility.
42+
if "seed" not in kwargs:
43+
init_kwargs["seed"] = self._random.random_seed_int()
3444
return self.__class__(**init_kwargs)
3545

3646
# Serialization and deserialization. You may overwrite to obtain more human readable serializations
@@ -74,15 +84,15 @@ def copy_lists(lists: List[List]) -> List[List]:
7484
return list(map(list, lists))
7585

7686

77-
def crossover_lists(list1: List, list2: List) -> List:
78-
cross_point = randrange(len(list1))
87+
def crossover_lists(list1: List, list2: List, rng) -> List:
88+
cross_point = rng.randint(0, len(list1))
7989
new_list = list(list1[:cross_point]) + list(list2[cross_point:])
8090
return new_list
8191

8292

83-
def crossover_dictionaries(table1: Dict, table2: Dict) -> Dict:
93+
def crossover_dictionaries(table1: Dict, table2: Dict, rng) -> Dict:
8494
keys = list(table1.keys())
85-
cross_point = randrange(len(keys))
95+
cross_point = rng.randint(0, len(keys))
8696
new_items = [(k, table1[k]) for k in keys[:cross_point]]
8797
new_items += [(k, table2[k]) for k in keys[cross_point:]]
8898
new_table = dict(new_items)

0 commit comments

Comments
 (0)