Skip to content

Random seeding #84

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 12, 2020
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: python
python:
- 3.5
- 3.6
- 3.7
dist: xenial
sudo: required
services:
Expand All @@ -19,5 +19,6 @@ before_install:
install:
- pip install -r requirements.txt
- pip install pytest
- python setup.py install
script:
- pytest
- pytest tests/
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
axelrod>=4.8.0
axelrod>=4.10.0
31 changes: 21 additions & 10 deletions src/axelrod_fortran/player.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from ctypes import byref, c_float, c_int, POINTER
import random
import warnings

import axelrod as axl
Expand Down Expand Up @@ -42,16 +41,19 @@ def __init__(self, original_name):
A instance of an axelrod Game
"""
super().__init__()
# The order of the next 4 lines is important. We must first check that
# the player name is valid, then grab a copy of the shared library,
# and then setup the actual strategy function.
self.original_name = original_name
self.index, self.shared_library_filename = \
shared_library_manager.get_filename_for_player(original_name)
shared_library_manager.get_filename_for_player(self.original_name)
self.shared_library = load_library(self.shared_library_filename)
self.original_name = original_name
self.original_function = self.original_name

is_stochastic = characteristics[self.original_name]['stochastic']
if is_stochastic is not None:
self.classifier['stochastic'] = is_stochastic


def __enter__(self):
return self

Expand All @@ -63,11 +65,11 @@ def original_name(self):
return self.__original_name

@original_name.setter
def original_name(self, value):
if value in characteristics:
self.__original_name = value
def original_name(self, key):
if key in characteristics:
self.__original_name = key
else:
raise ValueError('{} is not a valid Fortran function'.format(value))
raise ValueError('{} is not a valid Fortran function'.format(key))

@property
def original_function(self):
Expand Down Expand Up @@ -105,7 +107,7 @@ def strategy(self, opponent):
my_last_move = original_actions[self.history[-1]]
move_number = len(self.history) + 1
if self.classifier["stochastic"]:
random_value = random.random()
random_value = self._random.random()
else:
random_value = 0
original_action = self.original_strategy(
Expand All @@ -119,7 +121,16 @@ def _release_shared_library(self):
# thread closes before the player class is garbage collected, which
# tends to happen at the end of a script.
try:
shared_library_manager.release(self.original_name, self.index)
name = self.original_name
index = self.index
except AttributeError:
# If the Player does finish __init__, because the name of a
# non-existent strategy is supplied, a copy of the shared library
# won't be loaded, nor will self.original_name or self.index
# exist. In that case there's nothing to do.
return
try:
shared_library_manager.release(name, index)
except FileNotFoundError:
pass

Expand Down
2 changes: 1 addition & 1 deletion src/axelrod_fortran/shared_library_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def create_library_copy(self):
# Copy the library file to a new (temp) location.
temp_directory = tempfile.gettempdir()
copy_number = len(self.filenames)
filename = "{}-{}-{}".format(
filename = "{}-{}-{}".format(
self.prefix,
str(copy_number),
self.shared_library_name)
Expand Down
2 changes: 1 addition & 1 deletion src/axelrod_fortran/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
'original_rank': None},
}

all_strategies = characteristics.keys()
all_strategies = list(characteristics.keys())

# Players from Axelrod's second tournament.
second_tournament_strategies = [
Expand Down
66 changes: 47 additions & 19 deletions tests/test_player.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from ctypes import c_int, c_float, POINTER, CDLL
from ctypes import c_int, c_float, POINTER
import itertools

import pytest

from axelrod_fortran import Player, characteristics, all_strategies
from axelrod import (Alternator, Cooperator, Defector, Match, MoranProcess,
Game, basic_strategies, seed)
Game, RandomGenerator, Tournament, basic_strategies)
from axelrod.action import Action


Expand Down Expand Up @@ -122,32 +122,33 @@ def test_implemented_strategies():
"""
for strategy, dictionary in characteristics.items():
axelrod_class = dictionary["axelrod-python_class"]
player = Player(strategy)
if (axelrod_class is not None and
player.classifier["stochastic"] is False):
axl_player = axelrod_class()
stochastic = Player(strategy).classifier["stochastic"]
if axelrod_class is not None and not stochastic:
for opponent_strategy in basic_strategies:
player = Player(strategy)
opponent = opponent_strategy()
match = Match((player, opponent))
interactions = match.play()

axl_player = axelrod_class()
opponent = opponent_strategy()
axl_match = Match((axl_player, opponent))
assert interactions == axl_match.play(), (player, opponent)
axl_interactions = axl_match.play()
assert interactions == axl_interactions


def test_champion_v_alternator():
"""
Specific regression test for a bug.
Specific regression test for a bug. See:
https://github.com/Axelrod-Python/axelrod-fortran/issues/62
"""
player = Player("k61r")
opponent = Alternator()

match = Match((player, opponent))

seed(0)
seed = 3
match = Match((player, opponent), seed=seed)
interactions = match.play()
assert interactions[25:30] == [(C, D), (C, C), (C, D), (D, C), (C, D)]

seed(0)
match = Match((player, opponent), seed=seed)
assert interactions == match.play()


Expand All @@ -157,9 +158,7 @@ def test_warning_for_self_interaction(recwarn):
"""
player = Player("k42r")
opponent = player

match = Match((player, opponent))

interactions = match.play()
assert len(recwarn) == 1

Expand All @@ -168,13 +167,10 @@ def test_no_warning_for_normal_interaction(recwarn):
"""
Test that a warning is not given for a normal interaction
"""
player = Player("k42r")
opponent = Alternator()
for players in [(Player("k42r"), Alternator()),
(Player("k42r"), Player("k41r"))]:

match = Match(players)

interactions = match.play()
assert len(recwarn) == 0

Expand All @@ -185,3 +181,35 @@ def test_multiple_copies(recwarn):
mp = MoranProcess(players)
mp.play()
mp.populations_plot()


def test_match_reproducibility():
for _ in range(100):
rng = RandomGenerator()
seed = rng.random_seed_int()
strategies = rng.choice(all_strategies, size=2)
players1 = [Player(strategy) for strategy in strategies]
match1 = Match(players1, turns=200, noise=0.1, seed=seed)
results1 = match1.play()

players2 = [Player(strategy) for strategy in strategies]
match2 = Match(players2, turns=200, noise=0.1, seed=seed)
results2 = match2.play()

assert (results1 == results2)


def test_tournament_reproducibility():
rng = RandomGenerator()
seed = rng.random_seed_int()
strategies = rng.choice(all_strategies, size=10)
players1 = [Player(strategy) for strategy in strategies]
tournament1 = Tournament(players1, seed=seed, repetitions=2)
results1 = tournament1.play(processes=2)

players2 = [Player(strategy) for strategy in strategies]
tournament2 = Tournament(players2, seed=seed, repetitions=2)
results2 = tournament2.play(processes=2)

assert (results1.ranked_names == results2.ranked_names)