Skip to content

Commit

Permalink
Merge pull request #1288 from Axelrod-Python/random_seed
Browse files Browse the repository at this point in the history
Implement reproducible seeding
  • Loading branch information
drvinceknight authored Aug 11, 2020
2 parents c89df5f + 82a22ca commit c1d6333
Show file tree
Hide file tree
Showing 117 changed files with 3,651 additions and 2,557 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:

jobs:
build:

runs-on: ${{ matrix.os }}
strategy:
max-parallel: 4
Expand Down Expand Up @@ -37,7 +36,6 @@ jobs:
- name: Run tests
run: |
python -m pip install coverage
python -m pip install hypothesis==3.2
coverage run --source=axelrod -m unittest discover
- name: Report coverage
run: |
Expand Down
3 changes: 1 addition & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ Quick Start
The following runs a basic tournament::

>>> import axelrod as axl
>>> axl.seed(0) # Set a seed
>>> players = [s() for s in axl.demo_strategies] # Create players
>>> tournament = axl.Tournament(players) # Create a tournament
>>> tournament = axl.Tournament(players, seed=1) # Create a tournament
>>> results = tournament.play() # Play the tournament
>>> results.ranked_names
['Defector', 'Grudger', 'Tit For Tat', 'Cooperator', 'Random: 0.5']
Expand Down
12 changes: 10 additions & 2 deletions axelrod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@

# The order of imports matters!
from axelrod.version import __version__
from axelrod.action import Action
from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator

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

from axelrod.load_data_ import load_pso_tables, load_weights
from axelrod import graph
from axelrod.action import Action
from axelrod.random_ import random_choice, random_flip, seed, Pdf
from axelrod.plot import Plot
from axelrod.game import DefaultGame, Game
from axelrod.history import History, LimitedHistory
Expand Down
8 changes: 5 additions & 3 deletions axelrod/_strategy_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def _calculate_scores(p1, p2, game):
return s1, s2


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

# Now play forward with the constant strategy.
Expand Down
4 changes: 2 additions & 2 deletions axelrod/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from enum import Enum
from functools import total_ordering
from typing import Iterable
from typing import Iterable, Tuple


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


def str_to_actions(actions: str) -> tuple:
def str_to_actions(actions: str) -> Tuple[Action, ...]:
"""Converts a string to a tuple of actions.
Parameters
Expand Down
2 changes: 1 addition & 1 deletion axelrod/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
Union,
)

import yaml
from axelrod.makes_use_of import makes_use_of
from axelrod.player import Player
import yaml

ALL_CLASSIFIERS_PATH = "data/all_classifiers.yml"

Expand Down
45 changes: 34 additions & 11 deletions axelrod/data/all_classifiers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -961,39 +961,47 @@ Meta Hunter Aggressive:
Meta Majority:
inspects_source: false
long_run_time: true
makes_use_of: !!set {}
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
Meta Majority Finite Memory:
inspects_source: false
long_run_time: true
makes_use_of: !!set {}
makes_use_of: !!set
game: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
Meta Majority Long Memory:
inspects_source: false
long_run_time: true
makes_use_of: !!set {}
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
Meta Majority Memory One:
inspects_source: false
long_run_time: true
makes_use_of: !!set {}
makes_use_of: !!set
game: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
Meta Minority:
inspects_source: false
long_run_time: true
makes_use_of: !!set {}
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand All @@ -1011,6 +1019,7 @@ Meta Winner:
long_run_time: true
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand All @@ -1020,6 +1029,7 @@ Meta Winner Deterministic:
long_run_time: true
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand All @@ -1029,6 +1039,7 @@ Meta Winner Ensemble:
long_run_time: true
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand All @@ -1047,6 +1058,7 @@ Meta Winner Long Memory:
long_run_time: true
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand All @@ -1065,6 +1077,7 @@ Meta Winner Stochastic:
long_run_time: true
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand Down Expand Up @@ -1130,40 +1143,47 @@ N Tit(s) For M Tat(s):
NMWE Deterministic:
inspects_source: false
long_run_time: true
makes_use_of: &id001 !!set
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
NMWE Finite Memory:
inspects_source: false
long_run_time: true
makes_use_of: *id001
makes_use_of: !!set
game: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
NMWE Long Memory:
inspects_source: false
long_run_time: true
makes_use_of: *id001
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
NMWE Memory One:
inspects_source: false
long_run_time: true
makes_use_of: *id001
makes_use_of: !!set
game: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
NMWE Stochastic:
inspects_source: false
long_run_time: true
makes_use_of: *id001
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand Down Expand Up @@ -1197,14 +1217,17 @@ Nice Meta Winner:
long_run_time: true
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
stochastic: true
Nice Meta Winner Ensemble:
inspects_source: false
long_run_time: true
makes_use_of: *id001
makes_use_of: !!set
game: null
length: null
manipulates_source: false
manipulates_state: false
memory_depth: .inf
Expand Down
28 changes: 14 additions & 14 deletions axelrod/eigen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@

from typing import Tuple

import numpy
import numpy as np


def _normalise(nvec: numpy.ndarray) -> numpy.ndarray:
def _normalise(nvec: np.ndarray) -> np.ndarray:
"""Normalises the given numpy array."""
with numpy.errstate(invalid="ignore"):
result = nvec / numpy.sqrt((nvec @ nvec))
with np.errstate(invalid="ignore"):
result = nvec / np.sqrt((nvec @ nvec))
return result


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


def _power_iteration(mat: numpy.array, initial: numpy.ndarray) -> numpy.ndarray:
def _power_iteration(mat: np.array, initial: np.ndarray) -> np.ndarray:
"""
Generator of successive approximations.
Expand All @@ -33,7 +33,7 @@ def _power_iteration(mat: numpy.array, initial: numpy.ndarray) -> numpy.ndarray:
mat: numpy.array
The matrix to use for multiplication iteration
initial: numpy.array, None
The initial state. Will be set to numpy.array([1, 1, ...]) if None
The initial state. Will be set to np.array([1, 1, ...]) if None
Yields
------
Expand All @@ -42,13 +42,13 @@ def _power_iteration(mat: numpy.array, initial: numpy.ndarray) -> numpy.ndarray:

vec = initial
while True:
vec = _normalise(numpy.dot(mat, vec))
vec = _normalise(np.dot(mat, vec))
yield vec


def principal_eigenvector(
mat: numpy.array, maximum_iterations=1000, max_error=1e-3
) -> Tuple[numpy.ndarray, float]:
mat: np.array, maximum_iterations=1000, max_error=1e-3
) -> Tuple[np.ndarray, float]:
"""
Computes the (normalised) principal eigenvector of the given matrix.
Expand All @@ -66,12 +66,12 @@ def principal_eigenvector(
ndarray
Eigenvector estimate for the input matrix
float
Eigenvalue corresonding to the returned eigenvector
Eigenvalue corresponding to the returned eigenvector
"""

mat_ = numpy.array(mat)
mat_ = np.array(mat)
size = mat_.shape[0]
initial = numpy.ones(size)
initial = np.ones(size)

# Power iteration
if not maximum_iterations:
Expand Down
22 changes: 16 additions & 6 deletions axelrod/evolvable_player.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import base64
from pickle import dumps, loads
from random import randrange
from typing import Dict, List

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

def __init__(self, seed=None):
# Parameter seed is required for reproducibility. Player will throw
# a warning to the user otherwise.
super().__init__()
self.set_seed(seed=seed)

def overwrite_init_kwargs(self, **kwargs):
"""Use to overwrite parameters for proper cloning and testing."""
for k, v in kwargs.items():
self.init_kwargs[k] = v

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

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


def crossover_lists(list1: List, list2: List) -> List:
cross_point = randrange(len(list1))
def crossover_lists(list1: List, list2: List, rng) -> List:
cross_point = rng.randint(0, len(list1))
new_list = list(list1[:cross_point]) + list(list2[cross_point:])
return new_list


def crossover_dictionaries(table1: Dict, table2: Dict) -> Dict:
def crossover_dictionaries(table1: Dict, table2: Dict, rng) -> Dict:
keys = list(table1.keys())
cross_point = randrange(len(keys))
cross_point = rng.randint(0, len(keys))
new_items = [(k, table1[k]) for k in keys[:cross_point]]
new_items += [(k, table2[k]) for k in keys[cross_point:]]
new_table = dict(new_items)
Expand Down
Loading

0 comments on commit c1d6333

Please sign in to comment.